Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a delegated method public when declared in a private section?

I can make attr_reader (and the related attr_writer and attr_accessor) method(s) private by putting the declaration in a private section:

class Foo
private
  attr_reader :b
end

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

However, Rails' delegate and the Ruby standard library's def_delegate do not work this way. These delegated methods are always 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"

Making the delegation private is easily done by changing it to:

private def_delegator :foo, :b

but I expected a NoMethodError error for Bar.new.b above. Why isn't the delegation private?

The method definition of def_delegator (alias for def_instance_delegator) is just rescue (blocks removed):

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

That means module_eval does not respect that it was called in a private section. Why?

like image 391
spickermann Avatar asked May 20 '15 03:05

spickermann


1 Answers

Yes, the issue is with module_eval because it explicitly sets public visibility before evaling passed string. It behaves in the same way in CRuby and JRuby. For example, incriminated code for CRuby is in eval_under function.

As you've figured out, when you pass def_delegate to private method it becomes private. def_delegate first defines passed method as public (by the underlying module_eval), then is reset by private to private visibility.

It's not 100% clear if current Module.module_eval's behavior is correct one or there is a bug in Forwardable.def_instance_delegator. module_eval examples in documentation guide to use it outside concerned class/module and it does not expect visibility argument so it seems logical it sets method's visibility to public.

The solution would be either Module.module_eval handle optional visibility argument and respect current visibility when sent to implicit or explicit self (doubt if possible) or fix Forwardable.def_instance_delegator implementation to define method with more appropriate Module.define_method instead of module_eval. In any case this is a good candidate for filling a bug report on http://bugs.ruby-lang.org .

like image 61
David Unric Avatar answered Oct 11 '22 19:10

David Unric