Почему объявленный делегированный метод объявляется в частном разделе?
Я могу сделать attr_reader
(и связанные с ним attr_writer
и attr_accessor
) методы private, поместив объявление в раздел private
:
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
. Почему?
Ответы
Ответ 1
Да, проблема связана с module_eval
, поскольку она явно устанавливает видимость общественности перед обходом прошедшей строки.
Он ведет себя так же, как в CRuby и JRuby. Например, инкриминируемый код для CRuby находится в eval_under.
Как вы поняли, при передаче метода def_delegate
to private
он становится закрытым. def_delegate
сначала определяет передаваемый метод как открытый (базовым модулем_eval), а затем reset на private
для частной видимости.
Это не 100%, если текущее поведение Module.module_eval
верное, или есть ошибка в Forwardable.def_instance_delegator
. module_eval
примеры в руководстве по документации, чтобы использовать его вне соответствующего класса/модуля, и он не ожидает аргумента видимости, поэтому кажется логичным, что он устанавливает видимость метода для публики.
Решение будет либо Module.module_eval
обрабатывать необязательный аргумент видимости, и учитывать текущую видимость при отправке в неявный или явный self
(если возможно, сомнение) или исправить реализацию Forwardable.def_instance_delegator
, чтобы определить метод с более подходящим Module.define_method
вместо module_eval
.
В любом случае это хороший кандидат для заполнения отчета об ошибке http://bugs.ruby-lang.org.
Ответ 2
Я думаю, что это так, как должно работать. Я всегда считаю, что модификаторы видимости влияют на область, в которой они записаны, а не на любые вызовы, происходящие оттуда. В этом смысле вызов module_eval не знает, что он находится в частном разделе (не так ли?).
Ответ 3
Похоже, второй вариант работает, а первый - не потому, что Rails говорит "module_eval эту конкретную строку". Таким образом, когда у вас есть личный адрес в той же строке, он понимает, что он должен начинать рассматривать его как определение частного метода. Это похоже на то, что должно быть исправлено разработчиками Rails, создать проблему для них.