0

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

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

假设 id=1 的签到有placement_id=2:

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

因为我更喜欢 lambdas 而不是 procs,所以我试图用 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. 为什么那个参数总是保持为零?
4

1 回答 1

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实例。

Lambda 做出响应,to_proc因此if满足条件。所以真正发生的事情是这样的:

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

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

于 2013-08-15T20:43:45.870 回答