通过关联的belongs_to

Posted

技术标签:

【中文标题】通过关联的belongs_to【英文标题】:belongs_to through associations 【发布时间】:2011-04-30 14:19:10 【问题描述】:

鉴于以下关联,我需要从Choice 模型中引用Choice 附加的Question。我一直在尝试使用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】:

您也可以委托:

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

【讨论】:

+1,这是最干净的方法。 (至少我能想到) 有没有办法用 JOIN 做到这一点,这样它就不会使用这么多的查询? 我想了解自己。我尝试的一切都触发了 3 次选择。您可以在关联上指定“-> joins :something ” lambda。连接被触发,但随后还是另一个选择。我无法对此进行调整。 @Tallboy 一些关于主键的完美索引选择查询几乎总是优于任何单个 JOIN 查询。联接使数据库工作更加努力。 @affinities23 :这取决于您的特定要求。您可能想要没有任何公司的“不稳定”员工。然而,这里的allow_nil 意味着如果dog.employee 为nil,那么对dog.company 的调用将不会失败并且也只会返回nil。即使你总是希望这些东西是必需的,拥有它也很好,因为你总是可以去 Rails 控制台并输入dog = Dog.new。那时您的记录不存在,您仍然可以执行dog.company 而不会失败。【参考方案2】:

只需在您的:through 中使用has_one 而不是belongs_to,如下所示:

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

不相关,但我会犹豫是否使用 validates_uniqueness_of 而不是在您的数据库中使用适当的唯一约束。当您在 ruby​​ 中执行此操作时,您会遇到竞争条件。

【讨论】:

这个解决方案的大警告。每当您保存选择时,它将始终保存问题,除非设置了autosave: false @ChrisNicola 你能解释一下你的意思吗,我不明白你的意思。 我的意思是在哪里?如果您的意思是适当的唯一约束,我的意思是向数据库中必须唯一的列/字段添加唯一索引。 实际上是聪明的答案 另外delegate(在另一个问题中建议)似乎先加载answer,然后再加载questionhas_one 使用活动记录并创建连接查询,因此它使用的查询少。【参考方案3】:

belongs_to 关联不能有:through 选项。您最好在 Choice 上缓存 question_id 并向表中添加唯一索引(尤其是因为 validates_uniqueness_of 容易出现竞争条件)。

如果您偏执,请向Choice 添加自定义验证,以确认答案的question_id 匹配,但听起来最终用户永远不应该有机会提交会造成这种不匹配的数据.

【讨论】:

谢谢斯蒂芬,我真的不想直接与 question_id 关联,但我想这是最简单的方法。我最初的想法是,既然“答案”属于“问题”,我总是可以通过“答案”到达“问题”。但是您认为这不容易做到吗,还是您认为这只是一个糟糕的架构? 如果你想要一个唯一的约束/验证,范围字段必须存在于同一个表中。请记住,存在竞争条件。 >> 听起来最终用户永远不应该有机会提交会造成这种不匹配的数据。 -- 你永远不能保证用户“没有机会做某事”,除非你做一个明确的服务器端检查。【参考方案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 的答案可能是一个更好的性能决策。

某些选择是有时间和地点的,我认为这种选择在制作原型时会更好。除非我知道它用于不常见的用例,否则我不会将它用于生产代码。

【讨论】:

【参考方案5】:

所以你不能有你想要的行为,但你可以做一些感觉像的事情。你希望能够做到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上提出问题了

【讨论】:

【参考方案6】:

听起来您想要的是一个有很多问题的用户。 这个问题有很多答案,其中之一是用户的选择。

这就是你所追求的吗?

我会按照这些思路建模类似的东西:

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

【讨论】:

【参考方案7】:

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

有关has_many 魔法的信息,请参阅ActiveRecord::Associations 文档。

【讨论】:

感谢您的帮助 Michael,但是,这是我的错字。我已经在做 current_user.choices。此错误与我想将 belongs_to 分配给用户和问题有关。

以上是关于通过关联的belongs_to的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式获取 Rails 4 中的 belongs_to 关联的类

回调belongs_to关联rails

尽管我在模型中包含“belongs_to”,但未找到关联

Rails 3 中的Belongs_to 关联缓慢

Rails ActiveRecord - 获取与锁定的belongs_to关联

ActiveRecord 在保存主对象时是不是保存了belongs_to 关联?