为什么在私有部分中声明委托方法时 public?
Why is a delegated method public when declared in a private section?
我可以通过将声明放在 private
部分来使 attr_reader
(以及相关的 attr_writer
和 attr_accessor
)方法私有化:
class Foo
private
attr_reader :b
end
Foo.new.b # => NoMethodError: private method `b' called for #<Foo:>
但是,Rails' delegate
and the Ruby standard library's def_delegate
不是这样工作的。这些委托方法总是 public.
class Foo
attr_reader :b
def initialize
@b = 'b'
end
end
require 'forwardable'
class Bar
attr_reader :foo
def initialize
@foo = Foo.new
end
extend Forwardable
private
def_delegator :foo, :b
end
Bar.new.b # => "b"
将委托改为私有很容易,只需将其更改为:
private def_delegator :foo, :b
但我预计上面的 Bar.new.b
会出现 NoMethodError
错误。为什么委托不是私有的?
def_delegator
(def_instance_delegator
的别名)的方法定义只是 rescue
(删除块):
def def_instance_delegator(accessor, method, ali = method)
line_no = __LINE__; str = %Q{
def #{ali}(*args, &block)
#{accessor}.__send__(:#{method}, *args, &block)
end
}
module_eval(str, __FILE__, line_no)
end
这意味着 module_eval
不尊重它在 private
部分中的调用。为什么?
是的,问题出在 module_eval
上,因为它在评估传递的字符串之前明确设置了 public 可见性。
它在 CRuby 和 JRuby 中的行为方式相同。例如,CRuby 的相关代码在 eval_under 函数中。
如您所知,当您将 def_delegate
传递给 private
方法时,它会变为私有。 def_delegate
首先将传递的方法定义为 public(通过底层 module_eval),然后通过 private
将 重置 为私有可见性。
当前 Module.module_eval
的行为是正确的还是 Forwardable.def_instance_delegator
中存在错误,目前还不是 100% 清楚。 module_eval
文档指南中的示例在相关 class/module 之外使用它,并且它不期望可见性参数,因此将方法的可见性设置为 public.
似乎合乎逻辑
解决方案是 Module.module_eval
处理可选的可见性参数 和 在发送到隐式或显式时尊重当前可见性 self
(如果可能,请怀疑)或修复 Forwardable.def_instance_delegator
实现以使用更合适的 Module.define_method
而不是 module_eval
来定义方法。
在任何情况下,这都是在 http://bugs.ruby-lang.org .
上填写错误报告的好选择
我认为这是应该的。我一直认为可见性修饰符影响的是它们写入的范围,而不是来自那里的任何调用。从这个意义上讲,对 module_eval 的调用不知道它在私有部分中(可能吗?)。
看起来第二个选项有效,而第一个选项无效,因为 Rails 说的是 "module_eval this specific line"。因此,当您在同一行上使用 private 时,它知道它应该开始将其视为私有方法定义。这似乎应该由 Rails 维护者修复,为他们创建一个问题。
我可以通过将声明放在 private
部分来使 attr_reader
(以及相关的 attr_writer
和 attr_accessor
)方法私有化:
class Foo
private
attr_reader :b
end
Foo.new.b # => NoMethodError: private method `b' called for #<Foo:>
但是,Rails' delegate
and the Ruby standard library's def_delegate
不是这样工作的。这些委托方法总是 public.
class Foo
attr_reader :b
def initialize
@b = 'b'
end
end
require 'forwardable'
class Bar
attr_reader :foo
def initialize
@foo = Foo.new
end
extend Forwardable
private
def_delegator :foo, :b
end
Bar.new.b # => "b"
将委托改为私有很容易,只需将其更改为:
private def_delegator :foo, :b
但我预计上面的 Bar.new.b
会出现 NoMethodError
错误。为什么委托不是私有的?
def_delegator
(def_instance_delegator
的别名)的方法定义只是 rescue
(删除块):
def def_instance_delegator(accessor, method, ali = method)
line_no = __LINE__; str = %Q{
def #{ali}(*args, &block)
#{accessor}.__send__(:#{method}, *args, &block)
end
}
module_eval(str, __FILE__, line_no)
end
这意味着 module_eval
不尊重它在 private
部分中的调用。为什么?
是的,问题出在 module_eval
上,因为它在评估传递的字符串之前明确设置了 public 可见性。
它在 CRuby 和 JRuby 中的行为方式相同。例如,CRuby 的相关代码在 eval_under 函数中。
如您所知,当您将 def_delegate
传递给 private
方法时,它会变为私有。 def_delegate
首先将传递的方法定义为 public(通过底层 module_eval),然后通过 private
将 重置 为私有可见性。
当前 Module.module_eval
的行为是正确的还是 Forwardable.def_instance_delegator
中存在错误,目前还不是 100% 清楚。 module_eval
文档指南中的示例在相关 class/module 之外使用它,并且它不期望可见性参数,因此将方法的可见性设置为 public.
解决方案是 Module.module_eval
处理可选的可见性参数 和 在发送到隐式或显式时尊重当前可见性 self
(如果可能,请怀疑)或修复 Forwardable.def_instance_delegator
实现以使用更合适的 Module.define_method
而不是 module_eval
来定义方法。
在任何情况下,这都是在 http://bugs.ruby-lang.org .
我认为这是应该的。我一直认为可见性修饰符影响的是它们写入的范围,而不是来自那里的任何调用。从这个意义上讲,对 module_eval 的调用不知道它在私有部分中(可能吗?)。
看起来第二个选项有效,而第一个选项无效,因为 Rails 说的是 "module_eval this specific line"。因此,当您在同一行上使用 private 时,它知道它应该开始将其视为私有方法定义。这似乎应该由 Rails 维护者修复,为他们创建一个问题。