Rails 3在范围内编写IN子句的最佳方法是什么?

时间:2012-03-27 19:23:51

标签: ruby-on-rails ruby ruby-on-rails-3 activerecord

这有效:

scope :archived, :conditions => "day_id IN (#{Day.where("year_id != #{DateTime.now.year}").collect{ |d| d.id }.join(",")})"

是否有一种更难用的方式来编写IN子句?

3 个答案:

答案 0 :(得分:3)

原始范围和amitamb的解决方案中存在隐藏的错误。 scope是一种类方法,所以为什么这么说:

scope :blahblah, arguments

在解析和加载类时评估arguments表达式。特别是,当将类加载到Rails环境中时,将评估DateTime.now.year。因此,如果课程在2012-12-31加载,则where将为:

where('days.year_id != 2012')

如果您在2013-01-01几小时后使用示波器,它仍将使用2012年作为年份。这个问题有两种解决方案:

  1. 使用类方法或lambda作为范围:

    scope :archived, -> { joins(:day).where('days.year_id != ?', DateTime.now.year) }
    # or
    def self.archived
        joins(:day).where('days.year_id != ?', DateTime.now.year)
    end
    
  2. 将当前年度计算下推到数据库中:

    scope :archived, joins(:day).where('days.year_id != extract(year from current_date)')
    
  3. 某些数据库需要一些而不是extract(year from current_date),因此您可能希望使用(1)来避免可能的可移植性和时区问题。

    此外,您的原始方法在Day.where(...)部分遇到类似问题,在您的类加载时执行该查询,因此如果days表在您的应用程序运行时发生更改,那么您将会检查错误的清单。

答案 1 :(得分:0)

你能做这样的事吗?

your_logic = "year_id != #{DateTime.now.year}".collect{ |d| d.id }.join(",")}
scope :archived, :conditions => Day.where(your_logic)["day_id"]

我刚刚首先访问了对象,然后取出day_id IN来替换day_id

答案 2 :(得分:0)

对于简单的重写,您可以执行以下操作

scope :archived, where( :day_id => Day.where("year_id != #{DateTime.now.year}").collect{ |d| d.id }

但是这将生成两个查询和来自rails代码的不必要的连接。理想情况下,您应该通过SQL进行此连接,并仅在一个查询中执行检查。

因此产生的模型应该类似于

class Model

  belongs_to :day

  scope :archieved, joins(:day).where("days.year_id != #{DateTime.now.year}")

end

有关详细信息,请参阅以下内容

http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-the-joined-tables