Неймспейсы в Рейлс

В Рейлс очень удобно организовывать код по неймспейсам.
Допустим есть файл app/components/api/command.rb
, со следующим содержанием:
# app/components/api/command.rb
class Api::Command
# ...
end
Рейлс сам создаст промежуточный модуль Api
, который в обычном руби приходится объявлять так:
module Api
class Command
# ...
end
end
Как же это работает?
Каждое Рейлс приложение - это Rails::Engine
, который при инициализации вызывает
серию хуков, один из которых устанавливает пути автозагрузки
:
module Rails
class Engine
# ...
def paths
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
paths.add "app", eager_load: true, glob: "*"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
# ...
Строчка paths.add "app", ..., glob: "*"
ищет все папки из app
(в нашем
случае это app/components
).
Далее пути добавляются в ActiveSupport::Dependencies
:
module Rails
class Engine < Railtie
# ...
initializer :set_autoload_paths, before: :bootstrap_hook do
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
# ...
ActiveSupport::Dependencies
расширяет все модули с помощью специального внутреннего модуля ModuleConstMissing
:
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
# ...
def hook!
Object.class_eval { include Loadable }
Module.class_eval { include ModuleConstMissing }
Exception.class_eval { include Blamable }
end
# ...
ModuleConstMissing
в свою очередь, используя const_missing
, перехватает все обращения к неизвестным константам и загружает
необходимые файлы , считая что раз в путях поиска (app/*
в нашем примере) есть файл api/command.rb
, то класс Api::Command
должен находится именно там.
Ах да, модуль Api
, с которого мы начали наш разговор, создается в методе ActiveSupport::Dependencies#autoload_module!
) при первой попытке
разрезолвить полное имя класса Api::Command
:
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
# ...
def autoload_module!(into, const_name, qualified_name, path_suffix)
return nil unless base_path = autoloadable_module?(path_suffix)
mod = Module.new
into.const_set const_name, mod
# ...