在我的Rails应用程序中,类Project定义如下:
has_many :spins
has_one :video
我想为Project创建一个范围,它返回满足以下条件的所有项目:
project.video.present? || project.spins.count > 0 && project.spins.first.default_video.present?
我已经阅读了如何在范围中使用multiple conditions,但我不确定如何将其与嵌套属性的条件结合(在本例中为spins)。
如何为符合这些条件的Project创建范围?
修改的: 为了澄清, default_video 是Spin的一个类方法:
def default_video
self.videos.where("has_audio IS NULL").first
end
答案 0 :(得分:1)
据我所知,ActiveRecord不提供将范围与OR合并的功能(目前)。 我认为有两种方法可以处理这个功能:
1 - 创建一个范围,该范围返回包含视频的项目或包含默认视频的第一个旋转:
scope :with_video, -> { joins(
'LEFT JOIN spins
ON spins.id = (
select p.id
from spins as p WHERE projects.id = p.project_id ORDER BY p.id LIMIT 1
)
JOIN videos on videos.project_id = projects.id
WHERE
spins.default_video IS NOT NULL
OR
videos.project_id IS NOT NULL
')
}
我认为这个解决方案难以理解,难以维护。我没有对这个查询进行一些性能测试,也许这个必须改进
2 - 创建2个范围并合并两个结果
scope :with_one_video, -> { joins(:videos) }
scope :with_spin_video, -> { joins('JOIN spins ON
spins.id = (
SELECT S.id
FROM spins as S
WHERE projects.id = S.project_id
ORDER BY S.id LIMIT 1
)').where('spins.default_video IS NOT NULL')}
def with_video
(with_one_video.to_a + with_spin_video.to_a).uniq
end
对于可重复使用的范围,这个更好,但第二个范围仍然难以阅读。使用此解决方案结果已作为数组返回,根据具体情况,它可能是一个缺点。
编辑:我对所有答案感兴趣,不同的解决方案,sql查询的改进,甚至用activerecord / arel取代它们
答案 1 :(得分:0)
第一个条件相当容易,因为rails joins
是内部联接,所以它排除了不存在的关联:
Project.joins(:video)
第二个条件可以这样写(假设default_video是spins表的列):
Project.joins(:spins).
group("spins.project_id").
having("count(spins.default_video) > 0")
不幸的是,他们不能很好地协同工作。
相反,我建议2个左外连接(保留所有项目),然后group by project.id
。那"压缩"在一行中的所有连接,从那里我们通过计算一些连接的列来检查空值。
Project.joins("left join videos on videos.project_id = project.id").
joins("left join spins on spins.project_id = project.id").
group("projects.id").
having("count(videos.project_id) > 0 OR count(spins.default_video) > 0")
修改:根据其他信息,我们还需要一个加入:将videos
与spin
绑定的关键字(对于作为关联的default_video
)
Project.joins("left join videos on videos.project_id = project.id").
joins("left join spins on spins.project_id = project.id").
joins("inner join videos s_vid on s_vid.spin_id = spins.id").
group("projects.id").
having("count(videos.project_id) > 0 OR count(s_vid.spin_id) > 0").
select("projects.*)