2012-03-14 81 views
1

我有三个模型Company,Deal和Slot。它们与Company has_many交易和Deal has_many槽位相关联。如果所有交易都已过期,所有A公司都可以过期。当所有插槽都过期时,交易即告失效。ActiveRecord加入和在哪里

我写了一个范围..

scope :expired, 
    lambda { |within| 
    self.select(
     'DISTINCT companies.*' 
    ).latest(within).joins(
     :user =>{ :deals => :slots } 
    ).where(
     "companies.spam = false AND deals.deleted_at IS NULL 
     AND deals.spam = false AND slots.state = 1 
     OR slots.begin_at <= :time", 
     :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes 
    ) 
    } 

以上范围似乎并没有从我想实现我的权利。我需要所有交易的所有插槽的公司都处于状态1或者begin_at小于:使其过期的时间。

感谢您提前看看。

+1

在一条线上阅读令人头晕目眩,所以我把它分开了。 – tadman 2012-03-14 19:18:21

+0

谢谢塔德曼.. – Vikram 2012-03-14 19:23:19

回答

1

,并具有比或SQL更高的优先级,以便您where实际上被解析如下:

(
     companies.spam = false 
    and deals.deleted_at is null 
    and deals.spam = false 
    and slots.state = 1 
) 
or slots.begin_at <= :time 

例如(修剪位为简洁起见):

mysql> select 1 = 2 and 3 = 4 or 5 = 5; 
+---+ 
| 1 | 
+---+ 

mysql> select (1 = 2 and 3 = 4) or 5 = 5; 
+---+ 
| 1 | 
+---+ 

mysql> select 1 = 2 and (3 = 4 or 5 = 5); 
+---+ 
| 0 | 
+---+ 

此外,您可能希望在SQL中使用占位符而不是文字false,如果要切换数据库,应该使事情变得更容易(但当然,数据库可移植性在很大程度上是一个神话,因此这只是一个建议);你也可以在SQL中使用not。此外,using a class method is the preferred way to accept arguments for scopes。使用scoped而不是self也是一个好主意,如果其他范围已经在使用,但如果使用类方法,则不必在意。

如果我们修复了一些括号您的SQL分组,使用占位符为false,并切换到一个类的方法:

def self.expired(within) 
    select('distinct companies.*'). 
    latest(within). 
    joins(:user => { :deals => :slots }). 
    where(%q{ 
     not companies.spam 
    and not deals.spam 
    and deals.deleted_at is null 
    and (slots.state = 1 or slots.begin_at <= :time) 
    }, :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes) 
end 

你也可以写像这样,如果你喜欢SQL的小斑点而不是一个大的:

def self.expired(within) 
    select('distinct companies.*'). 
    latest(within). 
    joins(:user => { :deals => :slots }). 
    where('not companies.spam'). 
    where('not deals.spam'). 
    where('deals.deleted_at is null'). 
    where('slots.state = 1 or slots.begin_at <= :time', :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes) 
end 

这一个也整齐地回避你的“缺少括号”的问题。


UPDATE:基于在评论的讨论,我觉得你这样的事情之后是:

def self.expired(within) 
    select('distinct companies.*'). 
    latest(within). 
    joins(:user => :deals). 
    where('not companies.spam'). 
    where('not deals.spam'). 
    where('deals.deleted_at is null'). 
    where(%q{ 
     companies.id not in (
      select company_id 
      from slots 
      where state  = 1 
      and begin_at <= :time 
      group by company_id 
      having count(*) >= 10 
    ) 
    }, :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes 
end 

污秽的底部这一点抓住具有的所有公司的ID十个或更多过期或已使用的槽,然后companies.id not in (...)将它们从最终结果集中排除。

+0

它总是很好地了解更多最佳实践。但是我的问题还没有解决。该查询还提取了混合槽已过期(满足上述条件)且未到期的公司。我很抱歉我在Mysql上非常糟糕。请看看这个日志。 [链接](http://s1079.photobucket.com/albums/w518/vikramkohli87/?action=view¤t=Screenshotat2012-03-15022221.png) – Vikram 2012-03-14 21:00:54

+0

@Vikram:你真的想'slots.state = 1和插槽。那么begin_at <=:time'?我不知道你的模式,所以我正在猜测。 – 2012-03-14 21:21:01

+0

当所有插槽销售(状态= 1)或插槽不再有效(slots.begin_at <=:时间)时,公司已过期。如果一家公司甚至有一个没有过期或无效的插槽,该公司没有过期。从上面的方案,它也返回公司只有1个时隙过期/无效10。我想要的是,它应该只返回公司只有当所有10个插槽过期/无效。 – Vikram 2012-03-15 03:53:14