appartiene_to attraverso le associazioni


141

Date le seguenti associazioni, ho bisogno di fare riferimento a Questionche a Choiceè collegata attraverso il Choicemodello. Ho tentato di utilizzare belongs_to :question, through: :answerper eseguire questa azione.

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

Sto ottenendo

Nome Errore costante non inizializzata User::Choice

quando provo a fare current_user.choices

Funziona bene, se non includo il file

belongs_to :question, :through => :answer

Ma voglio usarlo perché voglio essere in grado di fare il validates_uniqueness_of

Probabilmente sto trascurando qualcosa di semplice. Qualsiasi aiuto sarebbe apprezzato.


1
Forse vale la pena cambiare la risposta accettata a quella delegata?
23

Risposte:


60

belongs_toUn'associazione non può avere una :throughpossibilità. È meglio la memorizzazione nella cache il question_idsu Choicee l'aggiunta di un indice univoco nella tabella (soprattutto perché validates_uniqueness_ofè soggetta a condizioni di gara).

Se sei paranoico, aggiungi una convalida personalizzata a Choiceconferma della question_idcorrispondenza della risposta , ma sembra che l'utente finale non debba mai avere l'opportunità di inviare dati che creerebbero questo tipo di mancata corrispondenza.


Grazie Stephen, non volevo davvero associarmi direttamente a question_id, ma credo sia il modo più semplice. Il mio pensiero originale era che, poiché la "risposta" appartiene alla "domanda", posso sempre passare attraverso la "risposta" per arrivare alla "domanda". Ma pensi che non sia facile da fare o pensi che sia solo un cattivo schema?
Vinhboy,

Se si desidera un vincolo / convalide univoci, i campi con ambito devono esistere nella stessa tabella. Ricorda, ci sono condizioni di gara.
stephencelis,

1
>> sembra che l'utente finale non debba mai avere l'opportunità di inviare dati che creerebbero questo tipo di mancata corrispondenza. - Non è mai possibile garantire che l'utente "non abbia l'opportunità di fare qualcosa" a meno che non si esegua un controllo esplicito sul lato server.
Konstantin,

376

Puoi anche delegare:

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

27
+1, questo è il modo più pulito per farlo. (almeno quello che posso pensare)
Orlando,

9
C'è un modo per farlo con JOIN in modo che non usi così tante query?
Tallboy,

1
Mi piacerebbe conoscere me stesso Tutto ciò che ho provato ha sparato 3 selezioni. È possibile specificare un "-> {join: qualcosa}" lambda su un'associazione. L'unione viene attivata ma successivamente un'altra selezione comunque. Non sono riuscito a sintonizzarlo.
Renra,

2
@Tallboy Alcune query selezionate perfettamente indicizzate sulle chiavi primarie sono quasi sempre migliori di qualsiasi singola query JOIN. I join rendono il database un duro lavoro.
Ryan McGeary,

1
Cosa fa allow_nil? Un dipendente non dovrebbe avere sempre un'azienda?
codifica aaron il

115

Basta usare has_oneinvece che belongs_tonel tuo :through, in questo modo:

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

Non correlato, ma esiterei a utilizzare validates_uniqueness_of invece di utilizzare un vincolo univoco corretto nel database. Quando lo fai in rubino hai le condizioni di gara.


38
Grande avvertimento con questa soluzione. Ogni volta che salvi Choice, salverà sempre la domanda a meno che non autosave: falsesia impostato.
Chris Nicola,

@ChrisNicola puoi per favore spiegare cosa intendevi, non ho capito cosa intendevi.
aprile

Cosa intendevo dire dove? Se intendi un vincolo univoco adeguato, intendo aggiungere un indice UNIQUE alla colonna / campo che deve essere univoco nel database.
Chris Nicola,

4

Il mio approccio era di creare un attributo virtuale invece di aggiungere colonne di database.

class Choice
  belongs_to :user
  belongs_to :answer

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

  # extra sugar
  def question_id
    answer.question_id
  end
end

Questo approccio è piuttosto semplice, ma presenta dei compromessi. Richiede che Rails si carichi answerdal db e quindi question. Questo può essere ottimizzato in seguito caricando con impazienza le associazioni necessarie (ad es. c = Choice.first(include: {answer: :question})), Tuttavia, se questa ottimizzazione è necessaria, la risposta di stephencelis è probabilmente una migliore decisione in termini di prestazioni.

C'è tempo e spazio per determinate scelte e penso che questa scelta sia migliore durante la prototipazione. Non lo userei per il codice di produzione se non sapessi che era per un caso d'uso poco frequente.


1

Sembra che quello che vuoi sia un utente che abbia molte domande.
La domanda ha molte risposte, una delle quali è la scelta dell'utente.

È questo ciò che cerchi?

Modellerei qualcosa del genere in questo modo:

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

1

Quindi non puoi avere il comportamento che desideri ma puoi fare qualcosa che ti sembra. Vuoi essere in grado di fareChoice.first.question

quello che ho fatto in passato è qualcosa di simile

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

in questo modo ora puoi chiamare la domanda su Scelta


-1

Il has_many :choicescrea un'associazione di nome choices, non è choice. Prova ad usare current_user.choicesinvece.

Consulta la documentazione di ActiveRecord :: Associazioni per informazioni sulla has_manymagia.


1
Grazie per il tuo aiuto Michael, tuttavia, è un errore di battitura da parte mia. Sto già facendo current_user.choices. Questo errore ha a che fare con me che desidero assegnare appartiene_ a utente e domanda.
Vinhboy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.