Модель Rails с foreign_key и таблицей ссылок
-
05-07-2019 - |
Вопрос
Я пытаюсь создать модель для проекта ruby on rails, которая строит отношения между разными словами.Думайте об этом как о словаре, где "Связи" между двумя словами показывают, что они могут использоваться как синонимы.Моя база данных выглядит примерно так:
Words
----
id
Links
-----
id
word1_id
word2_id
Как мне создать связь между двумя словами, используя таблицу ссылок?Я пытался создать модель, но не был уверен, как запустить таблицу ссылок в действие:
class Word < ActiveRecord::Base
has_many :synonyms, :class_name => 'Word', :foreign_key => 'word1_id'
end
Решение
В общем случае, если ваша ассоциация содержит такие суффиксы, как 1 и 2, она настроена неправильно.Попробуйте это для слова model:
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, либо из идентификатора word2, либо из обоих.
В любом случае, при использовании Модели для таблицы ссылок вы должны использовать опцию :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
Этого должно хватить, но теперь вы должны проверить два места, чтобы получить все синонимы.Я бы добавил метод, который объединил бы их внутри класса Word.
def synonyms
return synonyms1 || synonyms2
end
|| объединение результатов воедино объединит массивы и устранит дубликаты между ними.
* Этот код не тестировался.
Модель слова:
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 => :destroy
на has_many :links
удалит все ссылки, связанные с этим словом ранее destroy
вводя слово "запись".
Модель связи:
class Link < ActiveRecord::Base
belongs_to :word
belongs_to :synonym, :class_name => "Word"
end
Предполагая, что вы используете последнюю версию Rails, вам не нужно будет указывать внешний ключ для belongs_to :synonym
.Если я правильно помню, это было введено в качестве стандарта в Rails 2.
Таблица слов:
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
Пытаясь реализовать решение Сары, я столкнулся с двумя проблемами:
Во-первых, решение не работает, когда вы хотите назначить синонимы, выполнив
word.synonyms << s1 or word.synonyms = [s1,s2]
Кроме того, косвенное удаление синонимов не работает должным образом.Это связано с тем, что Rails не запускает обратные вызовы after_save_on_create и after_destroy, когда он автоматически создает или удаляет записи ссылок.По крайней мере, не в Rails 2.3.5, где я это опробовал.
Это можно исправить, используя обратные вызовы:after_add и:after_remove в Word model:
has_many :synonyms, :through => :links,
:after_add => :after_add_synonym,
:after_remove => :after_remove_synonym
Где обратные вызовы - это методы Сары, слегка скорректированные:
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
Вторая проблема решения Сары заключается в том, что синонимы, которые уже есть у других слов, когда они связаны вместе с новым словом, не добавляются к новому слову и наоборот.Вот небольшая модификация, которая устраняет эту проблему и гарантирует, что все синонимы группы всегда связаны со всеми другими синонимами в этой группе:
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