Rails model with foreign_key and link

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 โ€œConnectionsโ€ between two words indicate that they can be used synonymously. My DB looks something like this:

Words ---- id Links ----- id word1_id word2_id 

How to create a link between two words using a link table. I tried to create a model, but was not sure how to get the link table:

 class Word < ActiveRecord::Base has_many :synonyms, :class_name => 'Word', :foreign_key => 'word1_id' end 
+1
ruby-on-rails foreign-keys model
source share
5 answers

In general, if your association has suffixes such as 1 and 2, it is not configured 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 
+4
source share

Hmm, this is complicated. This is due to the fact that synonyms can be either with the word1 symbol, or with the identifier word2, or both.

In any case, when using the model for the link table, you should use the: through option in models using 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 have to check two places to get all synonyms. I would add a method that combined them inside the Word class.

 def synonyms return synonyms1 || synonyms2 end 

The results, combined together, will join the arrays and eliminate duplicates between them.

* This code is not verified.

+2
source share

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 to has_many :links will delete all links associated with this word before destroy the word record.

Link Model:

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

Assuming you are using the latest Rails, you do not need to specify a foreign key for belongs_to :synonym . If I remember correctly, this was introduced as standard in Rails 2.

Word table:

 name 

Link Table:

 word_id synonym_id 

To associate an existing word as a synonym with another word:

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

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

 word = Word.find_by_name("canine") word.link_to(Word.create(:name => "dog")) 
+2
source share

I would look at it from a different angle; since all words are synonyms, you should not promote any of them in order to be "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 
+1
source share

Trying to implement Sarah's solution, I ran into two problems:

Firstly, the solution does not work if you want to assign synonyms by doing

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

Also, removing indirect links does not work properly. This is because Rails does not start the after_save_on_create and after_destroy callbacks when it automatically creates or deletes Link entries. At least not in Rails 2.3.5, where I tried it.

This can be fixed 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 

If 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 problem with Sarahโ€™s solution is that synonyms that other words already have when connecting to 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 associated with all other synonyms in this 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 
0
source share

All Articles