gehört_zu durch Assoziationen

141

In Anbetracht der folgenden Assoziationen muss ich auf das verweisen Question, Choicedurch das a aus dem ChoiceModell angehängt wird . Ich habe versucht zu verwendenbelongs_to :question, through: :answer , diese Aktion auszuführen.

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

ich bekomme

NameError nicht initialisierte Konstante User::Choice

wenn ich es versuche current_user.choices

Es funktioniert gut, wenn ich das nicht einbeziehe

belongs_to :question, :through => :answer

Aber ich möchte das nutzen, weil ich das tun möchte validates_uniqueness_of

Ich übersehen wahrscheinlich etwas Einfaches. Jede Hilfe wäre dankbar.

Vinhboy
quelle
1
Vielleicht lohnt es sich, die akzeptierte Antwort an die des Delegierten zu ändern?
23inhouse

Antworten:

60

Ein belongs_toVerein kann keine :throughOption haben. Du bist besser dran das Caching question_idauf Choiceund das Hinzufügen eines eindeutigen Index der Tabelle (vor allem , weilvalidates_uniqueness_of zu Rennbedingungen anfällig).

Wenn Sie paranoid sind, fügen Sie eine benutzerdefinierte Validierung hinzu, Choicedie bestätigt, dass die Antwort question_idübereinstimmt. Es scheint jedoch , dass dem Endbenutzer niemals die Möglichkeit gegeben werden sollte, Daten zu übermitteln, die zu einer solchen Nichtübereinstimmung führen würden.

stephencelis
quelle
Danke Stephen, ich wollte wirklich nicht direkt mit question_id in Verbindung treten müssen, aber ich denke, es ist der einfachste Weg. Mein ursprünglicher Gedanke war, da "Antwort" zu "Frage" gehört, kann ich immer "Antwort" durchgehen, um zur "Frage" zu gelangen. Aber denkst du, dass das nicht einfach ist, oder denkst du, dass das nur ein schlechtes Schema ist?
Vinhboy
Wenn Sie eine eindeutige Einschränkung / Validierung wünschen, müssen die Felder mit Gültigkeitsbereich in derselben Tabelle vorhanden sein. Denken Sie daran, es gibt Rennbedingungen.
Stephencelis
1
>> Es hört sich so an, als ob dem Endbenutzer niemals die Möglichkeit gegeben werden sollte, Daten einzureichen, die zu einer solchen Nichtübereinstimmung führen würden. - Sie können niemals garantieren, dass der Benutzer "keine Gelegenheit hat, etwas zu tun", es sei denn, Sie führen eine explizite serverseitige Überprüfung durch.
Konstantin
376

Sie können auch delegieren:

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
quelle
27
+1, dies ist der sauberste Weg, dies zu tun. (Zumindest kann ich denken)
Orlando
9
Gibt es eine Möglichkeit, dies mit JOIN zu tun, damit nicht so viele Abfragen verwendet werden?
Tallboy
1
Ich würde mich gerne kennenlernen. Alles, was ich versucht habe, feuerte 3 Auswahlen ab. Sie können ein Lambda "-> {joins: Something}" für eine Zuordnung angeben. Der Join wird ausgelöst, anschließend jedoch trotzdem eine andere Auswahl. Ich konnte das nicht einstellen.
Renra
2
@Tallboy Einige perfekt indizierte Auswahlabfragen für Primärschlüssel sind fast immer besser als jede einzelne JOIN-Abfrage. Joins sorgen dafür, dass die Datenbank hart arbeitet.
Ryan McGeary
1
Was macht der allow_nil? Sollte ein Mitarbeiter nicht immer eine Firma haben?
Aaron-Codierung
115

Verwenden Sie einfach has_onestatt belongs_toin Ihrem :through, wie folgt:

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

Unabhängig, aber ich würde zögern, validates_uniqueness_of zu verwenden, anstatt eine geeignete eindeutige Einschränkung in Ihrer Datenbank zu verwenden. Wenn Sie dies in Rubin tun, haben Sie Rennbedingungen.

mrm
quelle
38
Große Warnung bei dieser Lösung. Wenn Sie Choice speichern, wird die Frage immer gespeichert, sofern sie nicht festgelegt autosave: falseist.
Chris Nicola
@ ChrisNicola kannst du bitte erklären was du meinst, ich habe nicht verstanden was du meinst.
Aks
Was ich meinte wo? Wenn Sie eine richtige eindeutige Einschränkung meinen, meine ich das Hinzufügen eines EINZIGARTIGEN Index zu der Spalte / dem Feld, die / das in der Datenbank eindeutig sein muss.
Chris Nicola
4

Mein Ansatz war es, ein virtuelles Attribut zu erstellen, anstatt Datenbankspalten hinzuzufügen.

class Choice
  belongs_to :user
  belongs_to :answer

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

  # extra sugar
  def question_id
    answer.question_id
  end
end

Dieser Ansatz ist ziemlich einfach, bringt jedoch Kompromisse mit sich. Es erfordert, dass Rails answervon der Datenbank geladen wird , und dann question. Dies kann später optimiert werden, indem Sie die benötigten Assoziationen eifrig laden (zc = Choice.first(include: {answer: :question}) geladen werden ). Wenn diese Optimierung jedoch erforderlich ist, ist die Antwort von stephencelis wahrscheinlich eine bessere Leistungsentscheidung.

Es gibt eine Zeit und einen Ort für bestimmte Entscheidungen, und ich denke, diese Wahl ist beim Prototyping besser. Ich würde es nicht für Produktionscode verwenden, wenn ich nicht wüsste, dass es sich um einen seltenen Anwendungsfall handelt.

Eric Hu
quelle
1

Es hört sich so an, als ob Sie einen Benutzer haben möchten, der viele Fragen hat.
Die Frage hat viele Antworten, von denen eine die Wahl des Benutzers ist.

Ist es das, wonach du suchst?

Ich würde so etwas in diese Richtung modellieren:

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
quelle
1

Sie können also nicht das Verhalten haben, das Sie wollen, aber Sie können etwas tun, das sich so anfühlt. Sie möchten in der Lage sein zu tunChoice.first.question

Was ich in der Vergangenheit getan habe, ist so etwas

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

Auf diese Weise können Sie jetzt eine Frage zur Auswahl stellen

MZaragoza
quelle
-1

Das has_many :choiceserstellt eine Assoziation mit dem Namen choices, nicht choice. Versuchen Sie es current_user.choicesstattdessen.

Siehe die Active :: Verbände Dokumentation für Informationen über über die has_manyMagie.

Michael Melanson
quelle
1
Vielen Dank für Ihre Hilfe Michael, das ist jedoch ein Tippfehler von meiner Seite. Ich mache bereits current_user.choices. Dieser Fehler hat etwas damit zu tun, dass ich dem Benutzer und der Frage Gehör_zuweisen möchte.
Vinhboy