Modèle Rails avec foreign_key et table de liens
-
05-07-2019 - |
Question
J'essaie de créer un modèle pour un projet Ruby on rails qui établit des relations entre différents mots. Considérez-le comme un dictionnaire où le lien " Liens " entre deux mots montre qu'ils peuvent être utilisés comme synonymes. Ma base de données ressemble à ceci:
Words
----
id
Links
-----
id
word1_id
word2_id
Comment créer une relation entre deux mots en utilisant la table des liens. J'ai essayé de créer le modèle mais je ne savais pas comment faire jouer la table des liens:
class Word < ActiveRecord::Base
has_many :synonyms, :class_name => 'Word', :foreign_key => 'word1_id'
end
La solution
En général, si votre association a des suffixes tels que 1 et 2, elle n'est pas configurée correctement. Essayez ceci pour le modèle Word:
class Word < ActiveRecord::Base
has_many :links, :dependent => :destroy
has_many :synonyms, :through => :links
end
Modèle de lien:
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
Tableaux:
Words
----
id
Links
-----
id
word_id
synonym_id
Autres conseils
Hmm, c'est une question délicate. En effet, les synonymes peuvent provenir soit de l’id word1, soit de l’id word2, ou des deux.
Quoi qu'il en soit, lorsque vous utilisez un modèle pour la table de liens, vous devez utiliser l'option: through des modèles utilisant la table de liens
.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
Cela devrait le faire, mais maintenant vous devez vérifier deux endroits pour obtenir tous les synonymes. Je voudrais ajouter une méthode qui a rejoint ces derniers, à l'intérieur de la classe Word.
def synonyms
return synonyms1 || synonyms2
end
|| Les résultats combinés réuniront les tableaux et élimineront les doublons.
* Ce code n'a pas été testé.
Modèle Word:
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
Définition de : depend = = gt; : destroy
sur le has_many: links
supprimera tous les liens associés à ce mot avant que détruise
le mot record.
Modèle de lien:
class Link < ActiveRecord::Base
belongs_to :word
belongs_to :synonym, :class_name => "Word"
end
En supposant que vous utilisez les derniers Rails, vous n'avez pas besoin de spécifier la clé étrangère pour le appartient_à: synonyme
. Si je me souviens bien, cela a été introduit en tant que norme dans Rails 2.
Tableau de mots:
name
Table de liens:
word_id
synonym_id
Pour lier un mot existant en tant que synonyme à un autre mot:
word = Word.find_by_name("feline")
word.link_to(Word.find_by_name("cat"))
Pour créer un nouveau mot comme synonyme d'un autre mot:
word = Word.find_by_name("canine")
word.link_to(Word.create(:name => "dog"))
Je le regarderais sous un angle différent; étant donné que tous les mots sont synonymes, vous ne devez en aucun cas promouvoir le mot "meilleur". Essayez quelque chose comme ça:
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
Maintenant, vous pouvez faire
word = Word.find_by_text("epiphany")
word.synonyms
En essayant de mettre en œuvre la solution de Sarah, je suis tombé sur deux problèmes:
Premièrement, la solution ne fonctionne pas si vous souhaitez attribuer des synonymes en procédant de la sorte
word.synonyms << s1 or word.synonyms = [s1,s2]
De même, supprimer des synonymes indirectement ne fonctionne pas correctement. En effet, Rails ne déclenche pas les rappels after_save_on_create et after_destroy lorsqu'il crée ou supprime automatiquement les enregistrements Link. Du moins pas dans Rails 2.3.5 où je l’ai essayé.
Ceci peut être corrigé en utilisant: after_add et: after_remove les rappels dans le modèle Word:
has_many :synonyms, :through => :links,
:after_add => :after_add_synonym,
:after_remove => :after_remove_synonym
Où les rappels sont les méthodes de Sarah, légèrement ajustées:
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
Le deuxième problème de la solution de Sarah est que les synonymes que d’autres mots ont déjà lorsqu'ils sont liés à un nouveau mot ne sont pas ajoutés au nouveau mot et inversement. Voici une petite modification qui résout ce problème et garantit que tous les synonymes d'un groupe sont toujours liés à tous les autres synonymes de ce groupe:
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