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

October 16, 2014

В Рейлс очень удобно организовывать код по неймспейсам.

Допустим есть файл 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
# ...
comments powered by Disqus