Rails:基于子属性的ActiveRecord范围

时间:2013-08-27 11:07:20

标签: ruby-on-rails activerecord scope named-scope

我有三个模型ComponentVersionUser相互关联,如下所示:

class Component < ActiveRecord::Base
    has_many :versions, class_name 'ComponentVersion'
end

class ComponentVersion < ActiveRecord::Base
    belongs_to :component
    belongs_to :approver, class_name: 'User'
end

class User < ActiveRecord::Base
end

我想在Component上创建两个范围: approved unapproved 。如果某个组件至少有一个已批准的version,则该组件已获得“已批准”,并且当其approver_id不是nil时,该组件将获得批准。因此Component.approved应返回所有已批准的组件,Component.unapproved应返回所有未批准的版本。

不幸的是,我不知道该怎么做。

我可以很容易地在ComponentVersion上定义范围:

class ComponentVersion < ActiveRecord::Base
    belongs_to :component
    belongs_to :approver, class_name: 'User'
    scope :approved,   -> { where('approver_id IS NOT NULL') }
    scope :unapproved, -> { where('approver_id IS NULL') }
end 

然后在控制台Component.joins(:version).merge(ComponentVersion.approved)给了我一些接近我想要Component.approved的东西,除了它包含重复项(如果一个组件有多个已批准的版本,它会被多次返回。)

并且Component.joins(:version).merge(ComponentVersion.unapproved)对于相反的情况并不好,因为当我只想要具有未经批准的版本的组件(或根本没有版本)时,它会返回具有未批准和已批准版本的组件)。

我也尝试将SQL查询传递给包含joins而不是INNER的OUTER JOIN,但他们没有给我我想要的东西,而且他们对SQL的理解有限。< / p>

如何获得我需要的查询?

(我正在使用Postgres和Rails 3.2.13,如果重要的话。)

1 个答案:

答案 0 :(得分:3)

使用查询生成器DSL似乎无法获得您想要的内容。我通常使用相关的子查询。

class Component
  scope :approved, lambda{ where('EXISTS (SELECT 1 FROM component_versions WHERE component_versions.component_id = components.id AND component_versions.approver_id IS NOT NULL)') }
end

另一个版本

class Component
  scope :approved, lambda { where('id IN (SELECT c.id FROM components c JOIN component_versions cv ON c.id = cv.component_id AND cv.approver_id IS NOT NULL)' }
end

使用Postgres,你可以使用哪一个并不重要,它有一个非常好的查询规划器。