为什么在私有部分中声明委托方法是公开的?

时间:2015-05-20 03:05:02

标签: ruby-on-rails ruby delegates metaprogramming

我可以通过将声明放在attr_reader部分中,将attr_writer(以及相关的attr_accessorprivate)方法设为私有:

class Foo
private
  attr_reader :b
end

Foo.new.b # => NoMethodError: private method `b' called for #<Foo:>

但是,Rails'delegate和Ruby标准库的def_delegate不能以这种方式工作。这些委派的方法总是公开的。

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

但我预计上面的NoMethodError会出现Bar.new.b错误。为什么代表团不私有?

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部分中调用它。为什么呢?

3 个答案:

答案 0 :(得分:5)

是的,问题出在module_eval,因为它在评估传递的字符串之前明确设置了公开可见性。 它在CRuby和JRuby中的行为方式相同。例如,CRuby的有罪代码在eval_under函数中。

正如您所知,当您将def_delegate传递给private方法时,它会变为私密。 def_delegate首先将传递的方法定义为public(由底层module_eval),然后由private重置定义为私有可见性。

如果当前Module.module_eval的行为正确或Forwardable.def_instance_delegator中存在错误,则不是100%清楚。文档指南中的module_eval示例在相关的类/模块之外使用它并且它不期望可见性参数,因此它似乎合乎逻辑地设置了方法对公众的可见性。

解决方案是Module.module_eval处理可选的可见性参数在发送到隐式或显式self时可以看到当前的可见性(如果可能,可以怀疑)或修复{{1}实现以使用更合适的Forwardable.def_instance_delegator而不是Module.define_method来定义方法。 无论如何,这是填写http://bugs.ruby-lang.org的错误报告的好选择。

答案 1 :(得分:0)

我认为这是应该有效的。我一直认为可见性修饰符正在影响它们所写的范围,而不是任何来自那里的调用。从这个意义上来说,对module_eval的调用并不知道它在私有部分内(可能吗?)。

答案 2 :(得分:0)

看起来第二个选项有效,而第一个选项不起作用,因为Rails说“module_eval这个特定的行”。因此,当您在同一行上拥有私有时,它理解它应该开始将其视为私有方法定义。这似乎应该由Rails维护者修复,为他们创建一个问题。