Подтвердить что ты не робот

Автозагрузка классов в Ruby без `autoload`

Я люблю функциональность автозагрузки Ruby; однако уходит в будущих версиях Ruby, поскольку он никогда не был потокобезопасным.

Итак, прямо сейчас я хотел бы притвориться, что он уже ушел, и написать свой код без него, реализовать механизм ленивой загрузки. Я хотел бы реализовать его самым простым способом (сейчас я не забочусь о безопасности потоков). Ruby должен позволить нам это сделать.

Пусть начнется добавление класса 'const_missing:

class Dummy
  def self.const_missing(const)
    puts "const_missing(#{const.inspect})"
    super(const)
  end
end

Ruby вызовет этот специальный метод, когда мы попытаемся ссылаться на константу в "Dummy", которая отсутствует, например, если мы попытаемся ссылаться на "Dummy:: Hello", она вызовет const_missing с символом :Hello. Это именно то, что нам нужно, поэтому давайте возьмем его дальше:

class Dummy
  def self.const_missing(const)
    if :OAuth == const
      require 'dummy/oauth'
      const_get(const)      # warning: possible endless loop!
    else
      super(const)
    end
  end
end

Теперь, если мы ссылаемся на "Dummy:: OAuth", для этого потребуется файл "dummy/oauth.rb", который, как ожидается, определит константу "Dummy:: OAuth". Там существует возможность бесконечного цикла, когда мы вызываем const_get (поскольку он может вызывать const_missing внутренне), но защита от этого выходит за рамки этого вопроса.

Большая проблема заключается в том, что все это решение разрушается, если в пространстве имен верхнего уровня существует модуль с именем "OAuth". Ссылка "Dummy:: OAuth" пропустит его const_missing и просто верните "OAuth" с верхнего уровня. Большинство реализаций Ruby также предупреждают об этом:

warning: toplevel constant OAuth referenced by Dummy::OAuth

Это было сообщено как проблема еще в 2003 году, но я не мог найти доказательств того, что основная команда Ruby когда-либо беспокоилась об этом. Сегодня большинство популярных реализаций Ruby несут одинаковое поведение.

Проблема заключается в том, что const_missing молча пропускается в пользу константы в пространстве имен верхнего уровня. Этого не произойдет, если объявить "Dummy:: OAuth" с помощью функции Ruby autoload. Любые идеи, как обойти это?

4b9b3361

Ответ 1

Это было поднято в билете Rails некоторое время назад, и когда я исследовал его, похоже, не было никакого способа обойти его. Проблема в том, что Ruby будет искать предков перед вызовом const_missing, и поскольку все классы имеют Object как предок, тогда всегда будут найдены константы верхнего уровня. Если вы можете ограничить себя использованием модулей для пространства имен, то он будет работать, поскольку у них нет Object в качестве предка, например:

>> class A; end
>> class B; end
>> B::A
(irb):3: warning: toplevel constant A referenced by B::A

>> B.ancestors
=> [B, Object, Kernel, BasicObject]

>> module C; end
>> module D; end
>> D::C
NameError: uninitialized constant D::C

>> D.ancestors
=> [D]

Ответ 2

Я использую const_get внутри const_missing, но не использую ::. Мне не нравится использовать eval, но он работает здесь:

class Dummy
  def self.const_missing(const)
    if :OAuth == const
      require 'dummy/oauth'
      eval "self::#{const}"
    else
      super(const)
    end
  end
end

module Hello
end

Dummy.const_get :Hello # => ::Hello
Dummy::Hello           # => Dummy::Hello

Желаю Module иметь метод ::, чтобы вы могли сделать self.send :"::", const.

Ответ 3

Ленивая загрузка - очень общий шаблон дизайна, вы можете реализовать его разными способами. например:

class Object
  def bind(key, &block)
    @hooks ||= Hash.new{|h,k|h[k]=[]}
    @hooks[key.to_sym] << [self,block]
  end

  def trigger(key)
    @hooks[key.to_sym].each { |context,block| block.call(context) }
  end
end

Тогда вы можете

 bind :json do
   require 'json'
 end

 begin
   JSON.parse("[1,2]")
 rescue
  trigger :json
  retry
 end