Modelo de rieles con Foreign_key y tabla de enlaces.
-
05-07-2019 - |
Pregunta
Estoy tratando de crear un modelo para un proyecto de ruby ??on rails que construye relaciones entre diferentes palabras. Piense en ello como un diccionario en el que " Enlaces " entre dos palabras muestra que se pueden usar como sinónimos. Mi DB se ve algo como esto:
Words
----
id
Links
-----
id
word1_id
word2_id
¿Cómo creo una relación entre dos palabras, usando la tabla de enlaces? He intentado crear el modelo pero no estaba seguro de cómo poner en funcionamiento la tabla de enlaces:
class Word < ActiveRecord::Base
has_many :synonyms, :class_name => 'Word', :foreign_key => 'word1_id'
end
Solución
En general, si su asociación tiene sufijos como 1 y 2, no está configurada correctamente. Prueba esto para el modelo de Word:
class Word < ActiveRecord::Base
has_many :links, :dependent => :destroy
has_many :synonyms, :through => :links
end
Modelo de enlace:
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
Tablas:
Words
----
id
Links
-----
id
word_id
synonym_id
Otros consejos
Hmm, esta es una pregunta difícil. Esto se debe a que los sinónimos pueden ser del ID de word1 o del ID de word2 o ambos.
De todos modos, cuando use un modelo para la tabla de enlaces, debe usar la opción: through en los modelos que usan la tabla de enlaces
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
Eso debería hacerlo, pero ahora debes marcar dos lugares para obtener todos los sinónimos. Yo agregaría un método que se uniera a estos, dentro de la clase Word.
def synonyms
return synonyms1 || synonyms2
end
|| juntando los resultados se unirán a las matrices y eliminarán los duplicados entre ellos.
* Este código no está probado.
Modelo de palabra:
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
Configuración : dependiente = > : destroy
en el has_many: links
eliminará todos los enlaces asociados con esa palabra antes de que destroy
ingrese el registro de la palabra.
Modelo de enlace:
class Link < ActiveRecord::Base
belongs_to :word
belongs_to :synonym, :class_name => "Word"
end
Suponiendo que está utilizando los últimos Rails, no tendrá que especificar la clave foránea para el belongs_to: synonym
. Si recuerdo correctamente, esto se introdujo como estándar en Rails 2.
Tabla de palabras:
name
Tabla de enlaces:
word_id
synonym_id
Para vincular una palabra existente como sinónimo de otra palabra:
word = Word.find_by_name("feline")
word.link_to(Word.find_by_name("cat"))
Para crear una nueva palabra como sinónimo de otra palabra:
word = Word.find_by_name("canine")
word.link_to(Word.create(:name => "dog"))
Lo vería desde un ángulo diferente; ya que todas las palabras son sinónimas, no debes promocionar ninguna de ellas para que sea la "mejor". Intenta algo como esto:
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
Ahora puedes hacerlo
word = Word.find_by_text("epiphany")
word.synonyms
Tratando de implementar la solución de Sarah, encontré 2 problemas:
En primer lugar, la solución no funciona cuando se desean asignar sinónimos haciendo
word.synonyms << s1 or word.synonyms = [s1,s2]
También eliminar sinónimos indirectamente no funciona correctamente. Esto se debe a que Rails no activa las devoluciones de llamada after_save_on_create y after_destroy cuando crea o elimina automáticamente los registros de enlace. Al menos no en Rails 2.3.5 donde lo probé.
Esto se puede solucionar usando: after_add y: after_remove callbacks en el modelo de Word:
has_many :synonyms, :through => :links,
:after_add => :after_add_synonym,
:after_remove => :after_remove_synonym
Donde las devoluciones de llamada son los métodos de Sarah, ligeramente ajustados:
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
El segundo problema de la solución de Sarah es que los sinónimos que otras palabras ya tienen cuando están vinculados con una nueva palabra no se agregan a la nueva palabra y viceversa. Aquí hay una pequeña modificación que soluciona este problema y garantiza que todos los sinónimos de un grupo estén siempre vinculados a todos los demás sinónimos de ese grupo:
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