Автозагрузка классов в 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
. Любые идеи, как обойти это?
Ответы
Ответ 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