通过关联归属


141

考虑下面的协会,我需要引用Question一个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
也许值得更改代表的公认答案?
6

Answers:


60

一个belongs_to协会不能有:through选择。最好不要缓存question_idon Choice并在表中添加唯一索引(尤其是因为validates_uniqueness_of容易出现竞争条件)。

如果您偏执狂,请向其添加自定义验证,以Choice确认答案是否question_id匹配,但听起来最终用户不应再有机会提交会导致这种不匹配的数据。


谢谢Stephen,我真的不想直接与question_id相关联,但是我想这是最简单的方法。我最初的想法是,由于“答案”属于“问题”,所以我总是可以通过“答案”来获得“问题”。但是,您认为那不容易做到,还是只是一个不好的模式?
vinhboy

如果要唯一约束/验证,则作用域字段必须存在于同一表中。记住,有比赛条件。
stephencelis

1
>>听起来,最终用户永远不应被给予机会提交可能导致这种不匹配的数据。-除非您进行明确的服务器端检查,否则您永远不能保证用户“没有机会做某事”。
康斯坦丁

376

您还可以委托:

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,这是最干净的方法。(至少我能想到)
奥兰多

9
有没有办法用JOIN做到这一点,以使其不使用太多查询?
高个子

1
我想认识自己。我尝试过的所有内容都激发了3个选择。您可以在关联上指定一个“-> {joins:something}” lambda。联接被触发,但随后无论如何都要进行另一个选择。我无法对此进行调整。
伦拉

2
@Tallboy在主键上一些完全索引的选择查询几乎总是比任何单个JOIN查询都要好。连接使数据库难以工作。
Ryan McGeary 2014年

1
allow_nil有什么作用?员工不应该总是拥有公司吗?
aaron-coding 2014年

115

只需使用 has_one的,而不是belongs_to你的:through,是这样的:

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

无关,但我不愿意在数据库中使用validates_uniqueness_of而不是使用适当的唯一约束。当您在红宝石中执行此操作时,您就有比赛条件。


38
此解决方案的重大警告。每当您保存选择时,除非autosave: false已设置,否则它将始终保存问题。
克里斯·尼古拉

@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从db 加载,然后再从加载question。以后可以通过急于加载您需要的关联(即c = Choice.first(include: {answer: :question}))进行优化,但是,如果有必要进行优化,则stephencelis的答案可能是更好的性能决定。

有一些选择的时间和地点,我认为在进行原型制作时,这种选择会更好。除非我知道它只用于很少的用例,否则我不会将其用于生产代码。


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

因此,您无法拥有想要的行为,但是您可以做一些自己喜欢的事情。你想做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

这样,您现在可以在Choice上致电提问


-1

has_many :choices创建一个名为关联choices,不是choice。尝试current_user.choices改用。

有关该魔术的信息,请参见ActiveRecord :: Associations文档has_many


1
谢谢您的帮助,迈克尔,不过,这就是我的错字。我已经在做current_user.choices。这个错误与我想要将belongs_to分配给用户和问题有关。
vinhboy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.