Rails model with foreign_key and link table

女生的网名这么多〃 提交于 2019-12-05 09:28:58

问题


I am trying to create a model for a ruby on rails project that builds relationships between different words. Think of it as a dictionary where the "Links" between two words shows that they can be used synonymously. My DB looks something like this:

Words
----
id

Links
-----
id
word1_id
word2_id

How do I create a relationship between two words, using the link-table. I've tried to create the model but was not sure how to get the link-table into play:

class Word < ActiveRecord::Base
  has_many :synonyms, :class_name => 'Word', :foreign_key => 'word1_id'
end

回答1:


In general, if your association has suffixes such as 1 and 2, it's not set up properly. Try this for the Word model:

class Word < ActiveRecord::Base
  has_many :links, :dependent => :destroy
  has_many :synonyms, :through => :links
end

Link model:

class Link < ActiveRecord::Base
  belongs_to :word
  belongs_to :synonym, :class_name => 'Word'

  # Creates the complementary link automatically - this means all synonymous
  # relationships are represented in @word.synonyms
  def after_save_on_create
    if find_complement.nil?
      Link.new(:word => synonym, :synonym => word).save
    end
  end

  # Deletes the complementary link automatically.
  def after_destroy
    if complement = find_complement
      complement.destroy
    end
  end

  protected

  def find_complement
    Link.find(:first, :conditions => 
      ["word_id = ? and synonym_id = ?", synonym.id, word.id])
  end
end

Tables:

Words
----
id

Links
-----
id
word_id
synonym_id



回答2:


Hmm, this is a tricky one. That is because synonyms can be from either the word1 id or the word2 id or both.

Anyway, when using a Model for the link table, you must use the :through option on the Models that use the Link Table

class Word < ActiveRecord::Base
  has_many :links1, :class_name => 'Link', :foreign_key => 'word1_id'
  has_many :synonyms1, :through => :links1, :source => :word
  has_many :links2, :class_name => 'Link', :foreign_key => 'word2_id'
  has_many :synonyms2, :through => :links2, :source => :word
end

That should do it, but now you must check two places to get all the synonyms. I would add a method that joined these, inside class Word.

def synonyms
  return synonyms1 || synonyms2
end

||ing the results together will join the arrays and eliminate duplicates between them.

*This code is untested.




回答3:


Word model:

class Word < ActiveRecord::Base
  has_many :links, :dependent => :destroy
  has_many :synonyms, :through => :links

  def link_to(word)
    synonyms << word
    word.synonyms << self
  end
end

Setting :dependent => :destroy on the has_many :links will remove all the links associated with that word before destroying the word record.

Link Model:

class Link < ActiveRecord::Base
  belongs_to :word
  belongs_to :synonym, :class_name => "Word"
end

Assuming you're using the latest Rails, you won't have to specify the foreign key for the belongs_to :synonym. If I recall correctly, this was introduced as a standard in Rails 2.

Word table:

name

Link table:

word_id
synonym_id

To link an existing word as a synonym to another word:

word = Word.find_by_name("feline")
word.link_to(Word.find_by_name("cat"))

To create a new word as a synonym to another word:

word = Word.find_by_name("canine")
word.link_to(Word.create(:name => "dog"))



回答4:


I'd view it from a different angle; since all the words are synonymous, you shouldn't promote any one of them to be the "best". Try something like this:

class Concept < ActiveRecord::Base
  has_many :words
end

class Word < ActiveRecord::Base
  belongs_to :concept

  validates_presence_of :text
  validates_uniqueness_of :text, :scope => :concept_id

  # A sophisticated association would be better than this.
  def synonyms
    concept.words - [self]
  end
end

Now you can do

word = Word.find_by_text("epiphany")
word.synonyms



回答5:


Trying to implement Sarah's solution I came across 2 issues:

Firstly, the solution doesn't work when wanting to assign synonyms by doing

word.synonyms << s1 or word.synonyms = [s1,s2]

Also deleting synonyms indirectly doesn't work properly. This is because Rails doesn't trigger the after_save_on_create and after_destroy callbacks when it automatically creates or deletes the Link records. At least not in Rails 2.3.5 where I tried it on.

This can be fixed by using :after_add and :after_remove callbacks in the Word model:

has_many :synonyms, :through => :links,
                    :after_add => :after_add_synonym,
                    :after_remove => :after_remove_synonym

Where the callbacks are Sarah's methods, slightly adjusted:

def after_add_synonym synonym
  if find_synonym_complement(synonym).nil?
    Link.new(:word => synonym, :synonym => self).save
  end
end

def after_remove_synonym synonym
  if complement = find_synonym_complement(synonym)
    complement.destroy
  end
end

protected

def find_synonym_complement synonym
  Link.find(:first, :conditions => ["word_id = ? and synonym_id = ?", synonym.id, self.id])
end

The second issue of Sarah's solution is that synonyms that other words already have when linked together with a new word are not added to the new word and vice versa. Here is a small modification that fixes this problem and ensures that all synonyms of a group are always linked to all other synonyms in that group:

def after_add_synonym synonym
  for other_synonym in self.synonyms
    synonym.synonyms << other_synonym if other_synonym != synonym and !synonym.synonyms.include?(other_synonym)
  end
  if find_synonym_complement(synonym).nil?
    Link.new(:word => synonym, :synonym => self).save
  end
end 


来源:https://stackoverflow.com/questions/623909/rails-model-with-foreign-key-and-link-table

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!