将lambda用于动态条件

时间:2013-08-15 08:15:26

标签: ruby ruby-on-rails-3 associations conditional-statements

我用这样的动态条件定义has_many关联:

class Checkin < ActiveRecord::Base
  has_many :evaluations, :conditions => proc {"evaluations.placement_id = #{self.placement_id}"}
end

假设id = 1的checkin具有placement_id = 2:

> Checkin.find(1).evaluations.to_sql
=> "SELECT \"evaluations\".* FROM \"evaluations\"  WHERE \"evaluations\".\"checkin_id\" = 1 AND (evaluations.placement_id = 2)"

由于我更喜欢​​使用lambdas而不是proc,我正在尝试用lambda替换关联条件,但这种尝试会导致错误:

:conditions => lambda {"evaluations.placement_id = #{self.placement_id}"}

> Checkin.find(1).evaluations.to_sql
ArgumentError: wrong number of arguments (1 for 0)

通过为lambda块提供参数可以很容易地解决错误:

:conditions => lambda {|a| "evaluations.placement_id = #{self.placement_id}"}

> Checkin.find(1).evaluations.to_sql # a is nil inside of this lambda call!
=> "SELECT \"evaluations\".* FROM \"evaluations\"  WHERE \"evaluations\".\"checkin_id\" = 1 AND (evaluations.placement_id = 2)"

传递参数没有区别,lambda的参数总是为零:

:conditions => lambda {|a| puts "a: #{a || 'undefined'}"; "evaluations.placement_id = #{self.placement_id}"}

Checkin.find(1).evaluations(5) # => has no effect on produced sql
a: undefined
# and here are returned evaluations
  1. lambda块的必需参数是什么?
  2. 为什么要在那里?
  3. 为什么该参数始终保持为零?

1 个答案:

答案 0 :(得分:4)

此行为是由ActiveRecord::Associations::Association#interpolate引起的,当Rails确定关联范围与条件时调用此行为。电话看起来像:

scope = scope.where(interpolate(condition))

以下是interpolate方法的全文(来自3.2来源:https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/associations/association.rb):

def interpolate(sql, record = nil)
  if sql.respond_to?(:to_proc)
    owner.send(:instance_exec, record, &sql)
  else
    sql
  end
end

在此方法中,sql是您的lambda条件,owner计算Checkin实例。

Lambdas回复to_proc,以满足if条件。所以真正发生的事情是:

Checkin.find(1).instance_exec(nil) {"evaluations.placement_id ... "}

这不起作用,因为nil是一个参数,所以块应该有一个参数(你已经知道lambdas检查arity)。当您执行lambda {|a| ... }时,anil,因为recordnil(因为其值未传递给interpolate方法并且nil是默认值。)