own_to через ассоциации

145

Учитывая следующие ассоциации, мне нужно сослаться на Questionто, что a Choiceприкреплено к Choiceмодели. Я пытался использовать belongs_to :question, through: :answerдля выполнения этого действия.

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

я осознаю

NameError неинициализированная константа User::Choice

когда я пытаюсь сделать current_user.choices

Он отлично работает, если я не включу

belongs_to :question, :through => :answer

Но я хочу использовать это, потому что хочу иметь возможность validates_uniqueness_of

Я, наверное, упускаю из виду что-то простое. Любая помощь будет оценена.

Винхбой
источник
1
Может стоит поменять принятый ответ на делегатский?
23 внутри дома,

Ответы:

61

У belongs_toассоциации не может быть :throughвыбора. Вы лучше кэширование question_idна Choiceи добавления уникального индекса к таблице (особенно потому , что validates_uniqueness_ofсклонна к условиям гонки).

Если вы параноик, добавьте настраиваемую проверку Choice, подтверждающую question_idсовпадение ответа , но похоже, что конечному пользователю никогда не следует предоставлять возможность отправлять данные, которые могут создать такое несоответствие.

Stephencelis
источник
Спасибо, Стивен, мне действительно не хотелось напрямую связываться с question_id, но я думаю, что это самый простой способ. Моя первоначальная мысль заключалась в том, что поскольку «ответ» принадлежит «вопросу», я всегда могу пройти через «ответ», чтобы перейти к «вопросу». Но вы думаете, что это нелегко, или вы думаете, что это просто плохая схема?
vinhboy
Если вам нужно уникальное ограничение / проверки, поля с заданной областью должны существовать в одной таблице. Помните, что есть условия гонки.
stephencelis
2
>> Похоже, конечному пользователю никогда не следует давать возможность отправлять данные, которые могут создать такое несоответствие. - Вы никогда не можете гарантировать, что у пользователя «нет возможности что-то сделать», если вы не выполните явную проверку на стороне сервера.
Константин
380

Вы также можете делегировать:

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
источник
28
+1, это самый чистый способ сделать это. (по крайней мере, я могу думать)
Орландо
9
Есть ли способ сделать это с помощью JOIN, чтобы не использовать так много запросов?
Tallboy
1
Я хотел бы знать себя. Все, что я пробовал, дало 3 выбора. Вы можете указать лямбду "-> {joins: something}" для ассоциации. Соединение запускается, но впоследствии все равно будет другой выбор. Я не смог это настроить.
Renra
2
@Tallboy Несколько идеально проиндексированных запросов на выборку по первичным ключам почти всегда лучше, чем любой отдельный запрос JOIN. Присоединения усложняют работу базы данных.
Райан МакГири
1
Что делает allow_nil? Разве у Сотрудника не всегда должна быть компания?
aaron-coding
116

Просто используйте has_oneвместо belongs_toвашего :through, например:

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

Не связано, но я бы не решился использовать validates_uniqueness_of вместо использования правильного уникального ограничения в вашей базе данных. Когда вы делаете это в рубине, у вас возникают условия гонки.

mrm
источник
40
Большое предупреждение с этим решением. Всякий раз, когда вы сохраняете выбор, всегда будет сохраняться вопрос, если он autosave: falseне установлен.
Крис Никола
1
@ChrisNicola, не могли бы вы объяснить, что вы имели в виду, я не понял, что вы имели в виду.
aks
Что я имел в виду где? Если вы имеете в виду правильное ограничение уникальности, я имею в виду добавление индекса UNIQUE к столбцу / полю, который должен быть уникальным в базе данных.
Крис Никола
Умный ответ на самом деле
сетхи
4

Мой подход заключался в создании виртуального атрибута вместо добавления столбцов базы данных.

class Choice
  belongs_to :user
  belongs_to :answer

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

  # extra sugar
  def question_id
    answer.question_id
  end
end

Этот подход довольно прост, но требует компромиссов. Требуется загрузка Rails answerиз базы данных, а затем question. Это можно оптимизировать позже, загружая нужные вам ассоциации (т.е. c = Choice.first(include: {answer: :question})), однако, если такая оптимизация необходима, то ответ stephencelis, вероятно, будет лучшим решением для производительности.

Есть время и место для определенного выбора, и я думаю, что этот выбор лучше при прототипировании. Я бы не стал использовать его для производственного кода, если бы не знал, что он предназначен для нечастого использования.

Эрик Ху
источник
2

Таким образом, вы не можете вести себя так, как хотите, но можете делать то, что вам нравится. Вы хотите уметь делатьChoice.first.question

то, что я делал в прошлом, примерно так

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

таким образом теперь вы можете задать вопрос по выбору

MZaragoza
источник
1

Похоже, что вам нужен Пользователь, у которого много вопросов.
У вопроса много ответов, один из которых - выбор пользователя.

Это то, что вам нужно?

Я бы смоделировал что-то подобное в следующих строках:

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

has_many :choicesСоздает объединение имени choices, а не choice. Попробуйте использовать current_user.choicesвместо этого.

См. Документацию ActiveRecord :: Associates для получения информации о has_manyмагии.

Майкл Мелансон
источник
1
Спасибо за помощь, Майкл, однако с моей стороны это опечатка. Я уже делаю current_user.choices. Эта ошибка как-то связана с тем, что я хочу назначить own_to пользователю и question.
vinhboy