pertenece a través de asociaciones

141

Dadas las siguientes asociaciones, necesito hacer referencia a la Questionque Choicese adjunta a desde el Choicemodelo. He estado intentando usar belongs_to :question, through: :answerpara realizar esta acción.

class User
  has_many :questions
  has_many :choices
end

class Question
  belongs_to :user
  has_many :answers
  has_one :choice, :through => :answer
end

class Answer
  belongs_to :question
end

class Choice
  belongs_to :user
  belongs_to :answer
  belongs_to :question, :through => :answer

  validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
end

estoy obteniendo

NameError constante no inicializada User::Choice

cuando trato de hacer current_user.choices

Funciona bien, si no incluyo el

belongs_to :question, :through => :answer

Pero quiero usar eso porque quiero poder hacer el validates_uniqueness_of

Probablemente estoy pasando por alto algo simple. Cualquier ayuda sería apreciada.

vinhboy
fuente
1
¿Quizás valga la pena cambiar la respuesta aceptada a la del delegado?
23inhouse

Respuestas:

60

Una belongs_toasociación no puede tener una :throughopción. Es mejor que el almacenamiento en caché question_iden Choicey la adición de un índice único a la tabla (sobre todo porque validates_uniqueness_ofes propenso a las condiciones de carrera).

Si es paranoico, agregue una validación personalizada Choiceque confirme que las respuestas question_idcoinciden, pero parece que el usuario final nunca debería tener la oportunidad de enviar datos que crearían este tipo de desajuste.

stephencelis
fuente
Gracias Stephen, realmente no quería tener que asociarme directamente con question_id, pero supongo que es la forma más fácil. Mi pensamiento original era que, como "respuesta" pertenece a "pregunta", siempre puedo pasar por "respuesta" para llegar a la "pregunta". ¿Pero crees que eso no es fácil de hacer, o crees que es solo un mal esquema?
vinhboy
Si desea una restricción / validación única, los campos de ámbito deben existir en la misma tabla. Recuerda, hay condiciones de carrera.
stephencelis
1
>> parece que el usuario final nunca debería tener la oportunidad de enviar datos que crearían este tipo de desajuste. - Nunca puede garantizar que el usuario "no tenga la oportunidad de hacer algo" a menos que haga una verificación explícita del lado del servidor para eso.
Konstantin
376

También puedes delegar:

class Company < ActiveRecord::Base
  has_many :employees
  has_many :dogs, :through => :employees
end

class Employee < ActiveRescord::Base
  belongs_to :company
  has_many :dogs
end

class Dog < ActiveRecord::Base
  belongs_to :employee

  delegate :company, :to => :employee, :allow_nil => true
end
Renra
fuente
27
+1, esta es la forma más limpia de hacer esto. (al menos eso puedo pensar)
Orlando
9
¿Hay alguna manera de hacer esto con JOIN para que no use tantas consultas?
Tallboy
1
Me gustaría conocerme a mí mismo. Todo lo que probé disparó 3 selecciones. Puede especificar un "-> {une: algo}" lambda en una asociación. La unión se dispara pero, posteriormente, otra selección de todos modos. No pude ajustar esto.
Renra
2
@Tallboy Algunas consultas de selección perfectamente indexadas en las claves principales son casi siempre mejores que cualquier consulta JOIN. Las uniones hacen que la base de datos trabaje duro.
Ryan McGeary
1
¿Qué hace el allow_nil? ¿No debería un empleado tener siempre una empresa?
codificación aaron
115

Simplemente use en has_onelugar de belongs_toen su :through, así:

class Choice
  belongs_to :user
  belongs_to :answer
  has_one :question, :through => :answer
end

Sin relación, pero dudaría en usar validates_uniqueness_of en lugar de usar una restricción única adecuada en su base de datos. Cuando haces esto en rubí tienes condiciones de carrera.

mrm
fuente
38
Gran advertencia con esta solución. Siempre que guarde Elección, siempre guardará la Pregunta a menos que autosave: falseesté configurada.
Chris Nicola
@ChrisNicola, ¿puedes explicar a qué te referías? No entendí lo que querías decir.
Aks
¿Qué quise decir dónde? Si quiere decir una restricción única adecuada, me refiero a agregar un índice ÚNICO a la columna / campo que debe ser único en la base de datos.
Chris Nicola
4

Mi enfoque era hacer un atributo virtual en lugar de agregar columnas de base de datos.

class Choice
  belongs_to :user
  belongs_to :answer

  # ------- Helpers -------
  def question
    answer.question
  end

  # extra sugar
  def question_id
    answer.question_id
  end
end

Este enfoque es bastante simple, pero viene con compensaciones. Requiere que Rails se cargue answerdesde la base de datos, y luego question. Esto puede optimizarse más tarde cargando ansiosamente las asociaciones que necesita (es decir c = Choice.first(include: {answer: :question})), sin embargo, si esta optimización es necesaria, entonces la respuesta de stephencelis es probablemente una mejor decisión de rendimiento.

Hay un momento y un lugar para ciertas opciones, y creo que esta opción es mejor cuando se realizan prototipos. No lo usaría para el código de producción a menos que supiera que era para un caso de uso poco frecuente.

Eric Hu
fuente
1

Parece que lo que quieres es un usuario que tenga muchas preguntas.
La pregunta tiene muchas respuestas, una de las cuales es la elección del usuario.

¿Es esto lo que buscas?

Modelaría algo así en este sentido:

class User
  has_many :questions
end

class Question
  belongs_to :user
  has_many   :answers
  has_one    :choice, :class_name => "Answer"

  validates_inclusion_of :choice, :in => lambda { answers }
end

class Answer
  belongs_to :question
end
Adam Tanner
fuente
1

Por lo tanto, no puede tener el comportamiento que desea, pero puede hacer algo que se le parezca. Quieres poder hacerChoice.first.question

lo que he hecho en el pasado es algo como esto

class Choice
  belongs_to :user
  belongs_to :answer
  validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
  ...
  def question
    answer.question
  end
end

de esta manera, ahora puede llamar a la pregunta sobre Choice

MZaragoza
fuente
-1

El has_many :choicescrea una asociación llamada choices, no choice. Intenta usar en su current_user.choiceslugar.

Consulte la documentación de ActiveRecord :: Associations para obtener información sobre la has_manymagia.

Michael Melanson
fuente
1
Gracias por tu ayuda Michael, sin embargo, eso es un error tipográfico de mi parte. Ya estoy haciendo current_user.choices. Este error tiene algo que ver conmigo al querer asignar pertenece_a al usuario y a la pregunta.
vinhboy