Модель Rails с внешним_ключом и таблицей ссылок

Я пытаюсь создать модель для проекта 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

person sdfx    schedule 08.03.2009    source источник


Ответы (5)


В общем, если ваша ассоциация имеет такие суффиксы, как 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
person Sarah Mei    schedule 08.03.2009
comment
Автоматическое создание дополнительной ссылки действительно полезно для сохранения действительных соединений. Хотя я согласен с тем, что использовать числа в качестве суффиксов — нехороший стиль, замена их на word_id и synonymous_word_id — это просто семантика, не так ли? Спасибо за подробный ответ. - person sdfx; 09.03.2009
comment
Вы можете оставить его как word_1 и word_2 в таблице ссылок, если хотите - я имел в виду, что если у вас есть две ассоциации с числовыми суффиксами, это обычно означает, что вы должны провести рефакторинг. Мой код переводит вас из двух наборов ассоциаций в один. - person Sarah Mei; 09.03.2009
comment
has_many :synonyms не будет работать, потому что в вашей модели ссылок вы не определяете какую-либо ассоциацию с именем :synonym. Пожалуйста, также разбейте разные модели на разные разделы кода. - person Ryan Bigg; 09.03.2009
comment
Это хороший способ сделать это, вероятно, имеет некоторые преимущества перед моим. +1 - person Tilendor; 09.03.2009
comment
Хорошая зацепка в названии ассоциации, Радар. Исправлено, и читается тоже лучше. - person Sarah Mei; 09.03.2009

Хм, это непростой вопрос. Это потому, что синонимы могут быть либо из идентификатора 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

||объединение результатов объединит массивы и устранит дубликаты между ними.

*Этот код не проверен.

person Tilendor    schedule 08.03.2009
comment
Спасибо, это действительно элегантное решение проблемы синонимов. - person sdfx; 08.03.2009
comment
Элегантность теряется для меня... Вы создаете две ссылки, когда вам нужна только одна. Проверьте мой ответ. - person Ryan Bigg; 09.03.2009
comment
хорошо, может быть, не элегантно, но это легко реализовать с существующими данными. Но я согласен, что решение Сары — правильный путь. - person sdfx; 09.03.2009

Модель слова:

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"))
person Ryan Bigg    schedule 09.03.2009
comment
Я думаю, что у вашей задачи есть одна проблема: word = Word.find_by_name(feline) word.synonyms ‹‹ Word.find_by_name(cat) Делает кошку синонимом кошачьего, но не наоборот. Вам нужно либо проверить оба столбца в таблице ссылок, как у меня, либо создать комплимент, как Сара. - person Tilendor; 09.03.2009
comment
@Tilendor тоже это видел. Если добавить создание комплимента, то это решение должно быть точно таким же, как исправленная версия Сары. - person sdfx; 09.03.2009
comment
Теперь, чтобы связать два слова вместе, используйте метод link_to, определенный в модели слова. - person Ryan Bigg; 10.03.2009

Я бы посмотрел на это под другим углом; поскольку все слова являются синонимами, вы не должны продвигать какое-либо из них как «лучшее». Попробуйте что-то вроде этого:

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
person Daniel Schierbeck    schedule 08.03.2009
comment
Хотя это может быть концептуально самый чистый способ сделать это, вы столкнетесь с проблемами, когда будете связывать существующие слова. Например. если слово A и B являются синонимами, а также слово C и слово D, и теперь вы хотите соединить слово A и слово D. У вас есть две разные концепции, которые вам нужно объединить. - person sdfx; 09.03.2009
comment
Если A == B, C == D и A == D, то A == B == C == D. Равенство транзитивно, поэтому все они должны иметь единую концепцию. Это идеализированный пример, поскольку слова могут иметь более одного значения, но это просто означает, что вам нужна связь «многие ко многим» между словами и понятиями. - person Daniel Schierbeck; 10.03.2009
comment
Я подозреваю, что подход с взаимными ссылками лучше подходит для большинства ситуаций, но это интересный подход, спасибо за публикацию. - person Jason Watkins; 02.07.2009

Пытаясь реализовать решение Сары, я столкнулся с двумя проблемами:

Во-первых, решение не работает, если вы хотите назначить синонимы, выполнив

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

Также косвенное удаление синонимов не работает должным образом. Это связано с тем, что Rails не запускает обратные вызовы after_save_on_create и after_destroy при автоматическом создании или удалении записей Link. По крайней мере, не в Rails 2.3.5, где я это пробовал.

Это можно исправить с помощью обратных вызовов :after_add и :after_remove в модели Word:

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 
person Nico    schedule 30.04.2011