pertence_a através de associações

141

Dadas as associações a seguir, preciso fazer referência a Questionque a Choiceé anexada através do Choicemodelo. Eu tenho tentado usar belongs_to :question, through: :answerpara executar esta ação.

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

estou obtendo

Constante não inicializada NameError User::Choice

quando eu tento fazer current_user.choices

Funciona bem, se eu não incluir o

belongs_to :question, :through => :answer

Mas quero usar isso porque quero poder fazer o validates_uniqueness_of

Provavelmente estou ignorando algo simples. Qualquer ajuda seria apreciada.

vinhboy
fonte
1
Talvez valha a pena mudar a resposta aceita para a delegada?
23

Respostas:

60

Uma belongs_toassociação não pode ter uma :throughopção. Você é melhor fora de cache do question_idon Choicee adicionar um índice exclusivo para a tabela (especialmente porque validates_uniqueness_ofé propenso a condições de corrida).

Se você é paranóico, adicione uma validação personalizada para Choiceconfirmar que a resposta question_idcorresponde, mas parece que o usuário final nunca deve ter a oportunidade de enviar dados que criariam esse tipo de incompatibilidade.

stephencelis
fonte
Obrigado Stephen, eu realmente não queria ter que me associar diretamente ao question_id, mas acho que é a maneira mais fácil. Meu pensamento original era: como "resposta" pertence a "pergunta", sempre posso passar por "resposta" para chegar à "pergunta". Mas você acha que isso não é fácil, ou você acha que isso é apenas um esquema ruim?
vinhboy
Se você deseja uma restrição / validações exclusivas, os campos com escopo definido devem existir na mesma tabela. Lembre-se, existem condições de corrida.
stephencelis
1
>> parece que o usuário final nunca deve ter a oportunidade de enviar dados que criariam esse tipo de incompatibilidade. - Você nunca pode garantir que o usuário "não tenha a oportunidade de fazer algo", a menos que você faça uma verificação explícita no servidor.
Konstantin
376

Você também pode 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
fonte
27
+1, essa é a maneira mais limpa de fazer isso. (pelo menos que eu possa pensar)
Orlando
9
Existe uma maneira de fazer isso com o JOIN para que ele não use tantas consultas?
quer
1
Eu gostaria de me conhecer. Tudo o que eu tentei demitido 3 seleciona. Você pode especificar um lambda "-> {joins: something}" em uma associação. A junção é acionada, mas subsequentemente outra seleção de qualquer maneira. Eu não fui capaz de ajustar isso.
Re
2
@Tallboy Algumas consultas de seleção perfeitamente indexadas nas chaves primárias são quase sempre melhores do que qualquer consulta JOIN. Associações fazem com que o banco de dados trabalhe duro.
Ryan McGeary
1
O que o allow_nil faz? Um funcionário não deve sempre ter uma empresa?
aaron-coding
115

Basta usar em has_onevez de belongs_tono seu :through, assim:

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

Não relacionado, mas eu hesitaria em usar validates_uniqueness_of em vez de usar uma restrição exclusiva adequada em seu banco de dados. Quando você faz isso em rubi, você tem condições de corrida.

srm
fonte
38
Grande aviso com esta solução. Sempre que você salvar o Choice, ele sempre salvará a pergunta, a menos que autosave: falseesteja definido.
Chris Nicola
@ ChrisNicola, por favor, você pode explicar o que você quis dizer? Eu não entendi o que você quis dizer.
21418
O que eu quis dizer onde? Se você quer dizer uma restrição exclusiva adequada, quero dizer adicionar um índice UNIQUE à coluna / campo que deve ser exclusivo no banco de dados.
22618 Chris Nicola
4

Minha abordagem foi criar um atributo virtual em vez de adicionar colunas do banco de dados.

class Choice
  belongs_to :user
  belongs_to :answer

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

  # extra sugar
  def question_id
    answer.question_id
  end
end

Essa abordagem é bastante simples, mas vem com vantagens e desvantagens. Requer o Rails para carregar answerdo banco de dados e, em seguida question. Isso pode ser otimizado posteriormente, carregando as associações de que você precisa (ie c = Choice.first(include: {answer: :question})), no entanto, se essa otimização for necessária, a resposta do stephencelis provavelmente será uma melhor decisão de desempenho.

Há um tempo e um lugar para certas escolhas, e acho que essa opção é melhor na criação de protótipos. Eu não o usaria para código de produção, a menos que soubesse que era para um caso de uso pouco frequente.

Eric Hu
fonte
1

Parece que o que você deseja é um usuário com muitas perguntas.
A pergunta tem muitas respostas, uma das quais é a escolha do usuário.

É isso que você procura?

Eu modelaria algo assim ao longo destas linhas:

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

Então você não pode ter o comportamento que deseja, mas pode fazer algo que lhe pareça. Você quer poder fazerChoice.first.question

o que eu fiz no passado é algo assim

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

Desta forma, agora você pode fazer uma pergunta sobre a Choice

MZaragoza
fonte
-1

O has_many :choicescria uma associação chamada choices, não choice. Tente usar em seu current_user.choiceslugar.

Consulte a documentação do ActiveRecord :: Associations para obter informações sobre a has_manymágica.

Michael Melanson
fonte
1
Obrigado pela ajuda. Michael, no entanto, é um erro de digitação da minha parte. Eu já estou fazendo o current_user.choices. Esse erro tem algo a ver comigo, querendo atribuir perten_to ao usuário e à pergunta.
vinhboy