174 votes

belongs_to par le biais d'associations

Compte tenu des associations suivantes, j'ai besoin de référencer l'élément Question qu'un Choice est rattaché par l'intermédiaire de la Choice modèle. J'ai essayé d'utiliser belongs_to :question, through: :answer pour effectuer cette action.

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

Je reçois

NameError constante non initialisée User::Choice

lorsque j'essaie de faire current_user.choices

Cela fonctionne bien, si je n'inclus pas l'élément

belongs_to :question, :through => :answer

Mais je veux l'utiliser parce que je veux être en mesure de faire le validates_uniqueness_of

J'ai probablement oublié quelque chose de simple. Toute aide serait appréciée.

2 votes

Peut-être vaut-il la peine de remplacer la réponse acceptée par celle du délégué ?

428voto

Renra Points 1923

Vous pouvez également déléguer :

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

32 votes

+1, c'est la façon la plus propre de procéder. (du moins à ma connaissance)

16 votes

Existe-t-il un moyen de faire cela avec le JOIN afin de ne pas utiliser autant de requêtes ?

1 votes

J'aimerais me connaître moi-même. Tout ce que j'ai essayé a déclenché 3 sélections. Vous pouvez spécifier une lambda "-> { joins :something }" sur une association. La jointure est déclenchée, mais elle est suivie d'une autre sélection de toute façon. Je n'ai pas été en mesure de régler cela.

156voto

mrm Points 558

Il suffit d'utiliser has_one au lieu de belongs_to dans votre :through comme ceci :

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

Sans rapport, mais j'hésiterais à utiliser validates_uniqueness_of au lieu d'utiliser une contrainte unique appropriée dans votre base de données. Lorsque vous faites cela en Ruby, vous avez des conditions de course.

53 votes

Gros avertissement avec cette solution. Chaque fois que vous sauvegardez Choix, il sauvegardera toujours Question à moins que autosave: false est défini.

1 votes

@ChrisNicola pouvez-vous expliquer ce que vous avez voulu dire, je n'ai pas compris ce que vous avez voulu dire.

0 votes

Qu'est-ce que je voulais dire ? Si vous voulez parler d'une contrainte unique appropriée, je veux dire ajouter un index UNIQUE à la colonne/au champ qui doit être unique dans la base de données.

65voto

stephencelis Points 1294

A belongs_to L'association ne peut pas avoir de :through option. Il est préférable de mettre en cache le fichier question_id sur Choice et l'ajout d'un index unique à la table (en particulier parce que validates_uniqueness_of est sujet à des conditions de course).

Si vous êtes paranoïaque, ajoutez une validation personnalisée à Choice qui confirme que la réponse est question_id mais il semble que l'utilisateur final ne devrait jamais avoir la possibilité de soumettre des données susceptibles de créer ce type de discordance.

0 votes

Merci Stephen, je ne voulais vraiment pas avoir à l'associer directement à l'identifiant de la question, mais je suppose que c'est le moyen le plus simple. Je pensais à l'origine que, puisque "answer" appartient à "question", je pouvais toujours passer par "answer" pour arriver à "question". Mais pensez-vous que ce n'est pas facile à faire, ou pensez-vous que c'est juste un mauvais schéma ?

0 votes

Si vous souhaitez une contrainte/validation unique, les champs concernés doivent exister dans la même table. N'oubliez pas qu'il existe des conditions de concurrence.

2 votes

>> il semble que l'utilisateur final ne devrait jamais avoir la possibilité de soumettre des données qui créeraient ce type d'incohérence. -- Vous ne pouvez jamais garantir que l'utilisateur "n'a pas la possibilité de faire quelque chose" à moins que vous ne fassiez une vérification explicite côté serveur.

10voto

Abramodj Points 1771

Vous pouvez simplement utiliser has_one à la place de belongs_to :

has_one :question, :through => :answer

3voto

Eric Hu Points 7388

Mon approche a consisté à créer un attribut virtuel au lieu d'ajouter des colonnes à la base de données.

class Choice
  belongs_to :user
  belongs_to :answer

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

  # extra sugar
  def question_id
    answer.question_id
  end
end

Cette approche est assez simple, mais elle s'accompagne de compromis. Elle nécessite que Rails charge answer de la base de données, puis question . Cela peut être optimisé ultérieurement en chargeant rapidement les associations dont vous avez besoin (c'est-à-dire c = Choice.first(include: {answer: :question}) ), mais si cette optimisation est nécessaire, la réponse de stephencelis est probablement une meilleure décision en termes de performance.

Il y a un temps et un lieu pour certains choix, et je pense que ce choix est préférable pour le prototypage. Je ne l'utiliserais pas pour du code de production à moins de savoir que c'est pour un cas d'utilisation peu fréquent.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X