通过协会确定范围

时间:2013-10-15 21:35:33

标签: sql ruby-on-rails ruby activerecord

考虑以下

class Engineer < ActiveRecord::Base
  has_many :ideas
  scope :masters, -> { where(educational_level: ["master", "candidate"]) }
end

class Idea < ActiveRecord::Base
  belongs_to :engineer
  scope :popular, -> { where("rating > ?", 4) }
end

现在,假设我要检索由工程师age订购的工程师设想的所有流行创意。为简单起见,假设age只是engineers表中的整数列。

这样做的一种方法是首先获取工程师。

popular_ideas_by_educated_engineers = []
Engineer.masters.order("age").each do |engineer|
  popular_ideas_by_educated_engineers += engineer.ideas.popular
end

这段代码有一个n + 1问题,因为想法并没有急切加载,整体感觉很笨拙。

我想以一种允许通过编写类似

的方式获得所需结果的方式编写代码
Idea.popular.by_masters.order_by_engineer_age

这可能 - 如果是的话,怎么样?

我知道我可以将SQL硬编码到加入Idea的{​​{1}}类中并施加一些限制,但我正在寻找的是利用已经在engineers类上定义的范围

2 个答案:

答案 0 :(得分:1)

你可能想尝试翻转它,就像你建议的那样:

Idea.popular.includes(:engineer).by_masters.order('engineers.age')

您的by_masters范围看起来像:

scope :by_masters, -> { where: { engineers: { educational_level: 'master' } } }

includes调用迫使JOIN使这成为可能。

答案 1 :(得分:0)

您可以在此处使用联接和合并:

class Engineer < ActiveRecord::Base
  scope :masters, -> { where(educational_level: ["master", "candidate"]) }
  scope :ordered_by_age, -> { order(:age) } 
end

class Idea < ActiveRecord::Base
  belongs_to :engineer

  scope :popular, -> { where("rating > ?", 4) }
  scope :by_master, -> { joins(:engineer).merge(Engineer.masters) }
  scope :order_by_engineer_age, -> { joins(:engineer).merge(Engineer.order_by_age) }
end