Почему объявленный делегированный метод объявляется в частном разделе?

Я могу сделать 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, создать проблему для них.