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?
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 .
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With