如何在保留预先加载的情况下为活动记录中的includes
生成的ON子句添加条件?
假设我有这些课程:
class Car
has_many :inspections
end
class Inspection
belongs_to :car
end
现在我能做到:
Car.includes(:inspections)
Select * from cars LEFT OUTER JOIN inspections ON cars.id = inspections.car_id
但我想生成这个sql:
Select * from cars LEFT OUTER JOIN inspections ON cars.id = inspections.car_id
AND inspections.month = '2013-04-01'
(这不起作用):
Car.includes(:inspections).where("inspections.month = 2013-04-01")
Select * from cars LEFT OUTER JOIN inspections ON cars.id = inspections.car_id
WHERE inspections.month = '2013-04-01'
答案 0 :(得分:1)
我并不完全清楚这一点,但你可能不建议你做什么即违反Rails'约定。根据此answer in a related question,此类查询的默认行为是使用两个查询,例如:
SELECT "cars".* FROM "cars";
SELECT "inspections".* FROM "inspections" WHERE "inspections"."car_id" IN (1, 2, 3, 4, 5);
此决定是出于性能原因而作出的。这让我猜测确切的查询类型(JOIN或多个查询)是一个你不能指望的实现细节。沿着这条思路,ActiveRecord :: Relation可能不是针对您的用例设计的,可能无法在查询中添加ON
条件。
按照这一系列猜测,如果你真的相信你的用例是唯一的,那么最好的办法就是你可以按如下方式制作自己的SQL查询:
Car.joins(sanitize_sql_array(["LEFT OUTER JOIN inspections ON inspections.car_id = cars.id AND inspections.month = ?", "2013-04-01"])
(更新:这是asked last year并没有得到一个好的答案。)
正如Carlos Drew建议的那样,
@cars = Cars.all
car_ids = @cars.map(&:id)
@inspections = Inspection.where(inspections: {month: '2013-04-01', car_id: car_ids})
# with scopes: Inspection.for_month('2013-04-01').where(car_id: car_ids)
但是,为了防止car.inspections
触发不必要的SQL调用,您还需要执行
# app/models/car.rb
has_many :inspections, inverse_of: :car
# app/models/inspection.rb
belongs_to :car, inverse_of: :inspections
也许您可以找到一种方法来缓存当前月份的检查,然后不用担心急切加载。这可能是最好的解决方案,因为缓存可以在不同的地方重用。
@cars = Cars.all
@cars.each do |car|
car.inspections.where(month: '2013-04-01')
end
答案 1 :(得分:0)
这应该有效:
Car.includes(:inspections).where( inspections: { month: '2013-04-01' })
答案 2 :(得分:0)
我更广泛地重新思考了你的问题。我认为您正面临代码设计问题以及(而不是?)ActiveRecord查询问题。
您要求返回已重新定义.inspections
的汽车关系,以表示与特定日期匹配的检验。 ActiveRecord不允许您根据查询动态重新定义模型关联。
如果您在检查日期没有要求动态条件,我会告诉您使用has_many :through
和:condition
。
has_many :passed_inspections, :through => :inspections, :conditions => {:passed => true}
@cars = Cars.includes(:passed_inspections)
显然,如果你需要动态提供检查日期,这将不起作用。
所以,最后,我会告诉你做这样的事情:
@cars = Cars.all
@inspections = Inspection.where(inspections: {month: '2013-04-01', car_id: @cars.pluck(:id)})
(条件有争议的car_id
的确切,最佳实施。然后,您需要将@inspections
分组为car_id
以获得给定条件的正确子集时刻。)
或者,在生产环境中,您可能可以依赖一些相当好/聪明的ActiveRecord缓存。我不确定这一点。
def inspections_dated(month)
inspections.where(month: month)
end
Car.includes(:inspections).each{|car| car.inspections_dated(month).each.etc. }
或者,或者
您可以通过手动SQL操作ActiveRecord,为您提供具有不明确界面的扩展Car对象:
@cars_with_insp = Car.join("LEFT OUTER JOIN inspections ON inspections.car_id = cars.id AND inspections.month = '2013-04-01'").select("cars.*, inspections.*")
@cars_with_insp.each{|c| puts c.name; puts c.inspection_month}
您将在.each
中看到,您可以直接在car
上获得检查的属性,因为您已通过连接说服ActiveRecord将一个类的两个记录作为单个返回行。 Rails会告诉你它的类是Car,但它不仅仅是一辆Car。您将获得每辆车一次,没有匹配的检查,或者每次匹配检查多次。
答案 3 :(得分:0)
Rails的作者没有将这个功能构建到ActiveRecord中,大概是因为使用WHERE返回相同的结果集,他们觉得没有必要替代。
在文档和代码中,我们找到了为包含模型添加条件的两种“官方”方法。
在实际的源代码中:https://github.com/rails/rails/blob/5245648812733d2c31f251de3e05e78e68bfa3a5/activerecord/lib/active_record/relation/query_methods.rb我们发现它们使用WHERE来完成此任务:
我引用:“
#
# If you want to add conditions to your included models you'll have
# to explicitly reference them. For example:
#
# User.includes(:posts).where('posts.name = ?', 'example')
#
# Will throw an error, but this will work:
#
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
_END_QUOTE _
文档提到另一种方法:标题“Eager loading of associations”
下的http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.htmlQUOTE:
如果您确实只想加载一个关联的某些成员,通常更自然地包含一个在其上定义条件的关联:
class Post<的ActiveRecord :: Base的 has_many:approved_comments, - > {where approved:true},class_name:'Comment' 端
Post.includes(:approved_comments)
这将加载帖子并急切加载approved_comments关联,该关联仅包含已批准的评论。
结束语录
您可以在技术上使用这种方法,但在您的情况下,如果您使用动态月值,它可能不会那么有用。
这些是唯一的选项,在任何情况下都会返回与基于AND的查询相同的结果。