Rails自身具有多个条件的范围和嵌套属性

时间:2015-11-24 06:02:27

标签: ruby-on-rails activerecord

在我的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

2 个答案:

答案 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")

修改:根据其他信息,我们还需要一个加入:将videosspin绑定的关键字(对于作为关联的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.*)