2013-08-27 41 views
1

的财产ActiveRecord的范围,我有三个型号Component,这是与对方喜欢这样关联VersionUserRails的:根据孩子

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创建两个范围:approvedunapproved。一个组件在其至少有一个批准的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)是没有良好的相反,因为它返回它既有未经批准和认可的版本中,当我只希望这只有具有u组件组件未批准的版本(或根本没有版本)。

我也试过将SQL查询传递给joins,其中包括OUTER JOIN而不是INNER,但他们没有给我我想要的,他们正在扩展我对SQL的有限理解。

如何获得我需要的查询?

(我使用的是Postgres和Rails 3.2.13,在它的问题的情况下。)

回答

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它可能并不重要,你会用哪一个,它有一个相当不错的查询规划。

+0

作品魅力!虽然第二个例子没有关闭''''。但是,我怎样才能定义'未批准'范围呢? – GMA

+0

使用“NOT EXISTS”或“NOT IN”。看来PostgreSQL优化器不如我想的那么好,所以坚持使用第二版。作为奖励,它更容易理解。顺便说一句,你应该考虑学习一点SQL,通过子查询,你将准备好解决98%的Web开发中出现的数据库访问任务。 – synapse