foreign_keyとリンクテーブルを使用したRailsモデル
-
05-07-2019 - |
質問
私は、異なる単語間の関係を構築するruby on railsプロジェクトのモデルを作成しようとしています。これは、「リンク」が存在する辞書と考えてください。 2つの単語の間にある同義語として使用できることを示しています。私のDBは次のようになります:
Words
----
id
Links
-----
id
word1_id
word2_id
リンクテーブルを使用して、2つの単語間の関係を作成するにはどうすればよいですか。モデルを作成しようとしましたが、リンクテーブルを再生する方法がわかりませんでした:
class Word < ActiveRecord::Base
has_many :synonyms, :class_name => 'Word', :foreign_key => 'word1_id'
end
解決
通常、関連付けに1や2などのサフィックスがある場合、適切に設定されていません。 Wordモデルでこれを試してください:
class Word < ActiveRecord::Base
has_many :links, :dependent => :destroy
has_many :synonyms, :through => :links
end
リンクモデル:
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
テーブル:
Words
----
id
Links
-----
id
word_id
synonym_id
他のヒント
うーん、これはトリッキーなものです。これは、同義語がword1 idまたはword2 idのいずれか、または両方に由来する可能性があるためです。
とにかく、リンクテーブルにモデルを使用する場合、リンクテーブルを使用するモデルで:throughオプションを使用する必要があります
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
それでうまくいきますが、今では2つの場所をチェックしてすべての同義語を取得する必要があります。これらを結合するメソッドをクラスWord内に追加します。
def synonyms
return synonyms1 || synonyms2
end
||結果を結合すると、配列が結合され、配列間の重複が排除されます。
*このコードはテストされていません。
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
設定:dependent =&gt;
は、単語レコードを has_many:links
の:destroy destroy
する前に、その単語に関連付けられているすべてのリンクを削除します。
リンクモデル:
class Link < ActiveRecord::Base
belongs_to :word
belongs_to :synonym, :class_name => "Word"
end
最新のRailsを使用している場合、 belongs_to:synonym
に外部キーを指定する必要はありません。正しく思い出せば、これはRails 2で標準として導入されました。
Wordテーブル:
name
リンクテーブル:
word_id
synonym_id
既存の単語を同義語として別の単語にリンクするには:
word = Word.find_by_name("feline")
word.link_to(Word.find_by_name("cat"))
新しい単語を別の単語の同義語として作成するには:
word = Word.find_by_name("canine")
word.link_to(Word.create(:name => "dog"))
別の角度から表示します。すべての単語は同義語であるため、いずれかの単語を「最高」に昇格させないでください。次のようなものを試してください:
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
今できること
word = Word.find_by_text("epiphany")
word.synonyms
サラのソリューションを実装しようとすると、2つの問題に遭遇しました:
まず、次の方法で類義語を割り当てたい場合、ソリューションは機能しません
word.synonyms << s1 or word.synonyms = [s1,s2]
また、シノニムを間接的に削除することも正しく機能しません。これは、Railsが自動的にLinkレコードを作成または削除するときにafter_save_on_createおよびafter_destroyコールバックをトリガーしないためです。少なくとも私が試してみたRails 2.3.5ではそうではありません。
これは、Wordモデルで:after_addおよび:after_removeコールバックを使用して修正できます。
has_many :synonyms, :through => :links,
:after_add => :after_add_synonym,
:after_remove => :after_remove_synonym
コールバックがSarahのメソッドである場合、わずかに調整します:
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
サラのソリューションの2番目の問題は、新しい単語とリンクされたときに他の単語がすでに持っている同義語が新しい単語に追加されず、その逆も同じであるということです。 この問題を修正し、グループのすべての同義語がそのグループ内の他のすべての同義語に常にリンクされるようにする小さな変更を次に示します。
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