3

在 Rails 中(至少 3.2 版;我没有 4 可以在那里尝试),如果给定 a ,即使它委托的对象可以正常工作,也会ActiveRecord::Base#find窒息。SimpleDelegator

这样做的原因是在创建 SQL 语句时AR::Base#find将值传递给AR::ConnectionAdapters::Quoting#quote,并且由于它不知道如何处理SimpleDelegator,它试图将其传递给YAML.dump,这会引发异常。AR确定如何通过类的声明来引用case(即String === value,等)。

现在,当然,即使 aSimpleDelegator包含 a String,它的类也是SimpleDelegator,所以上面的检查会失败。但是,SimpleDelegator有一个__getobj__方法可以访问被委托给的实际对象:

> s = SimpleDelegator.new("test")
#=> "test"
> String === s
#=> false
> String === s.__getobj__
#=> true

为了解决这个问题,我可以重写Class#===SimpleDelegator考虑:

class Class
  def ===(other)
    return super(other.__getobj__) if other.is_a?(SimpleDelegator)
    super
  end
end

> String === s
#=> true

但是,这显然看起来不是一种安全的方法(我不知道这是否会对任何事情产生负面影响,但至少SimpleDelegator会破坏类平等)。另一方面,这使得处理其他代码实例变得更容易,比如AR::ConnectionAdapters::Quoting#quote我还不知道的代码(例如,与专门的猴子补丁相反quoteSimpleDelegator

Module#===是 MRI 中的原生 C 方法,并使用了一种名为rb_obj_is_kind_of. 我曾希望覆盖SimpleDelegator#kind_of?可以让我以更安全的方式在这里做我想做的事情,但它似乎没有任何影响(我想rb_obj_is_kind_of与 没有任何关系Object#kind_of?)。

有没有办法以“安全”的方式做到这一点,或者我只是在个别案例出现时被猴子修补?

4

1 回答 1

0

看起来你可能会四处乱窜并欺骗AR. 它试图YAML.dump?好吧,我们会帮助它:

class A < SimpleDelegate
  def initialize *args
    @s = 'voilá'
  end
  # required by YAML (Psych)
  def encode_with coder
    coder.tag = nil
    coder.represent_scalar(nil, @s)
  end
end

require 'yaml'

puts YAML.dump(A.new)
# ⇒ --- voilá

现在AR应该能够倾倒它,瞧。该encode_with方法可能会CheatModule包含在您需要作弊的任何地方AR

module DelegateYamler
  def encode_with coder
    coder.tag = nil
    coder.represent_scalar(nil, __getobj__)
  end
end  

希望能帮助到你。

于 2013-10-05T14:02:32.647 回答