【发布时间】:2013-10-12 03:01:07
【问题描述】:
在 Rails 中(至少 3.2 版;我没有 4 个可以在那里尝试),如果给定 SimpleDelegator,ActiveRecord::Base#find 会窒息,即使它委托的对象可以正常工作。
原因是AR::Base#find在创建SQL语句时将值传递给AR::ConnectionAdapters::Quoting#quote,并且由于它不知道如何处理SimpleDelegator,它试图将它传递给YAML.dump,这引发异常。 AR 确定如何通过 case 声明的类(即 String === value 等)来引用。
当然,现在即使SimpleDelegator 包含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 中我还不知道的其他代码实例变得更容易(例如,与专门猴子修补quote 以了解SimpleDelegator 不同)。
Module#=== 是 MRI 中的原生 C 方法,并利用了称为 rb_obj_is_kind_of 的方法。我曾希望覆盖SimpleDelegator#kind_of? 可以让我以更安全的方式在这里做我想做的事,但它似乎没有影响(我猜rb_obj_is_kind_of 与Object#kind_of? 没有任何关系)。
有没有办法以“安全”的方式做到这一点,或者我只是在个别案例出现时被猴子修补?
【问题讨论】:
-
比起猴子补丁
Class,我至少会实现SimpleDelegator#class ; String ; end。虽然它似乎仍然不是一个完美的解决方案。 -
不幸的是,这似乎也不起作用;而
s.class变成String,String === s仍然是假的。 -
请见谅;看起来我只检查了类名。但我终于为你找到了奇怪的 hack,请在几分钟内看到答案。
标签: ruby delegates monkeypatching