Это руководство документирует, как работает автозагрузка и перезагрузка в режиме zeitwerk
.
После его прочтения, вы узнаете:
Это руководство документирует автоматическую загрузку, перезагрузку и нетерпеливую загрузку в приложении Rails.
В обычных программах на Ruby, вы явно загружаете файл, определяющие классы и модули, которые вы хотите использовать. Например, следующий контроллер ссылается на ApplicationController
и Post
, и обычно необходимо для них добавить вызовы require
:
# НЕ ДЕЛАЙТЕ ЭТОГО.
require 'application_controller'
require 'post'
# НЕ ДЕЛАЙТЕ ЭТОГО.
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Но это не в случае приложений Rails, когда классы и модули приложения доступны везде без вызовов require
:
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Rails автоматически загружает их за вас, если это необходимо. Это возможно благодаря нескольким загрузчикам Zeitwerk, которые Rails настраивает для вас, обеспечивающих автоматическую загрузку, перезагрузку и нетерпеливую загрузку.
С другой стороны, эти загрузчики не управляют ничем другим. В частности, они не управляют стандартной библиотекой Ruby, зависимостями от гемов, самими компонентами Rails или даже (по умолчанию) каталогом приложения lib
. Этот код необходимо загружать обычным способом.
В приложении Rails имена файлов должны соответствовать константам, которые они определяют, а директории выступают как пространства имен.
Например, файл app/helpers/users_helper.rb
должен определять UsersHelper
, а файл app/controllers/admin/payments_controller.rb
должен определять Admin::PaymentsController
.
По умолчанию Rails настраивает Zeitwerk, чтобы преобразовывать имена файлов с помощью String#camelize
. Например, он ожидает, что app/controllers/users_controller.rb
определяет константу UsersController
, так как это то, что возвращает "users_controller".camelize
Раздел Настройка словообразования ниже документирует способы переопределения этих умолчаний.
Подробности в документации Zeitwerk.
Мы ссылаемся на список директорий приложения, содержимое которых должно быть автоматически загружено и (опционально) перезагружено, как пути автозагрузки. Например, app/models
. Эти директории представляют корневое пространство имен: Object
.
Пути автоматической загрузки называются корневыми директориями в документации Zeitwerk, но в этом руководстве мы будем называть их "путь автозагрузки".
В пределах пути автозагрузки, имена файлов должны соответствовать константам, которые они определяют, как документировано тут.
По умолчанию, пути автозагрузки приложения состоят из всех поддиректорий app
, существующих во время загрузки приложения — за исключением assets
, javascript
и views
, — плюс пути автозагрузки engine-ов, от которых оно может зависеть.
К примеру, если UsersHelper
реализован в app/helpers/users_helper.rb
, этот модуль автоматически загружаемый, и вам не нужно писать вызов require
для него:
$ bin/rails runner 'p UsersHelper'
UsersHelper
Rails автоматически добавит пользовательские директории внутри app
в пути автозагрузки. Например, если в вашем приложении есть app/presenters
, не нужно ничего настраивать, чтобы автоматически загружать презентеры; они будут работать "из коробки".
Массив путей автозагрузки может быть расширен с помощью добавления в config.autoload_paths
в config/application.rb
или config/environments/*.rb
. Например:
module MyApplication
class Application < Rails::Application
config.autoload_paths << "#{root}/extras"
end
end
Также engine может добавлять в коде класса engine и в свой config/environments/*.rb
.
Пожалуйста, не изменяйте ActiveSupport::Dependencies.autoload_paths
, публичный интерфейс для изменения путей автозагрузки — это config.autoload_paths
.
Вы не можете автоматически загружать код в путях автозагрузки, пока приложение загружается. В частности, непосредственно в config/initializers/*.rb
. Пожалуйста, обратитесь к Автозагрузка при запуске приложения ниже за правильными способами сделать это.
Пути автозагрузки управляются автозагрузчиком Rails.autoloaders.main
.
По умолчанию каталог lib
не входит в пути автозагрузки для приложений или engine.
Метод конфигурации config.autoload_lib
добавляет каталог lib
в config.autoload_paths
и config.eager_load_paths
. Его необходимо вызывать из config/application.rb
или config/environments/*.rb
, и он недоступен для engine.
Обычно в каталоге lib
есть подкаталоги, которыми не должны управлять автозагрузчики. Пожалуйста, укажите их имена относительно lib
в обязательном ключевом аргументе ignore
. Например:
config.autoload_lib(ignore: %w(assets tasks))
Почему? Хотя assets
и tasks
совместно используют каталог lib
с обычным Ruby-кодом, их содержимое не предназначено для перезагрузки или нетерпеливой загрузки.
Список ignore
должен содержать все подкаталоги lib
, которые не содержат файлов с расширением .rb
или которые не должны быть перезагружены или нетерпеливо загружены. Например:
config.autoload_lib(ignore: %w(assets tasks templates generators middleware))
config.autoload_lib
недоступен в версиях до 7.1, но вы по-прежнему можете имитировать его работу, если ваше приложение использует Zeitwerk:
# config/application.rb
module MyApp
class Application < Rails::Application
lib = root.join("lib")
config.autoload_paths << lib
config.eager_load_paths << lib
Rails.autoloaders.main.ignore(
lib.join("assets"),
lib.join("tasks"),
lib.join("generators")
)
# ...
end
end
Также возможно автоматически загружать классы и модули без их перезагрузки. autoload_once_paths
хранит код, который может быть автоматически загружен, но не будет перезагружен.
По умолчанию эта коллекция пустая, но ее можно расширить, добавив к config.autoload_once_paths
. Это можно сделать в config/application.rb
или config/environments/*.rb
. Например:
module MyApplication
class Application < Rails::Application
config.autoload_once_paths << "#{root}/app/serializers"
end
end
Также engine может добавлять в коде класса engine и в свой config/environments/*.rb
.
Если app/serializers
добавлен config.autoload_once_paths
, Rails больше не будет рассматривать его как путь автозагрузки, не смотря на то, что это пользовательская директория внутри app
. Эта настройка переопределяет то правило.
Это важно для классов и модулей, которые кэшируются в местах, не являющихся перезагружаемыми, например в самом фреймворке Rails.
Например, сериализаторы Active Job сохраняются внутри Active Job:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
и сам Active Job не перезагружается при перезагрузке, это происходит только для кода приложения и engine-ов в путях автозагрузки.
Если сделать MoneySerializer
перезагружаемым, это может быть озадачиваемым, так как перезагрузка отредактированной версии не будет влиять на объект этого класса, сохраненного в Active Job. Разумеется, если бы MoneySerializer
был перезагружаемым, начиная с Rails 7 такой инициализатор выдал бы NameError
.
Еще один пример, когда engine декорирует классы фреймворка:
initializer "decorate ActionController::Base" do
ActiveSupport.on_load(:action_controller_base) do
include MyDecoration
end
end
Тут объект модуля, хранящегося в MyDecoration
, во время запуска инициализатора становится предком ActionController::Base
, и перезагрузка MyDecoration
бесполезна, она не повлияет на цепочку наследования.
Классы и модули из путей однократной автозагрузки могут быть автоматически загружены в config/initializers
. Таким образом, с такой настройкой это будет работать:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
Технически можно автоматически загружать классы и модули, управляемые автозагрузчиком once
, в любом инициализаторе, который запускается после :bootstrap_hook
.
Пути однократной автозагрузки управляются Rails.autoloaders.once
.
Метод config.autoload_lib_once
похож на config.autoload_lib
, за исключением того, что он вместо него добавляет каталог lib
в config.autoload_once_paths
. Его также необходимо вызывать из config/application.rb
или config/environments/*.rb
, и он недоступен для engine.
Вызывая config.autoload_lib_once
, вы добиваетесь автоматической загрузки классов и модулей из lib
, даже из инициализаторов приложения. Однако при этом они не будут перезагружаться.
Метод config.autoload_lib_once
недоступен в версиях до 7.1, но вы по-прежнему можете имитировать его работу, если ваше приложение использует Zeitwerk:
# config/application.rb
module MyApp
class Application < Rails::Application
lib = root.join("lib")
config.autoload_once_paths << lib
config.eager_load_paths << lib
Rails.autoloaders.once.ignore(
lib.join("assets"),
lib.join("tasks"),
lib.join("generators")
)
# ...
end
end
Rails автоматически перезагружает классы и модули, если файлы приложения в путях автозагрузки изменяются.
Если точнее, когда запущен веб сервер, и файлы приложения изменились, Rails выгружает все автоматически загруженные константы, управляемые автозагрузчиком main
, непосредственно перед тем, как обрабатывать следующий запрос. Таким образом, классы или модули приложения, используемые в течение этого запроса, будут снова автоматически загружены, следовательно, будет взята их текущая реализация из файловой системы.
Перезагрузка может быть включена или отключена. Настройкой, контролирующей это поведение, является config.enable_reloading
, которая по умолчанию true
в режиме development
и false
по умолчанию в режиме production
. Для обратной совместимости Rails также поддерживает config.cache_classes
, которая эквивалентна !config.enable_reloading
.
Rails по умолчанию использует событийный монитор файлов для обнаружения изменений файлов. Вместо этого можно настроить обнаружение изменений файлов, проходя по путям автозагрузки. Это контролируется настройкой config.file_watcher
.
В консоли Rails нет активного наблюдателя файлов, вне зависимости от значения config.enable_reloading
. Это потому, что обычно будет сбивать с толку, если код перезагрузится посреди консольной сессии. Как и в отдельном запросе, мы обычно хотим, чтобы сессия консоли обслуживалась постоянным, неизменным набором классов и модулей приложения.
Однако, можно принудительно перезагрузить, выполнив в консоли reload!
:
irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Reloading...
=> true
irb(main):003:0> User.object_id
=> 70136284426020
Как видите, объект класса, хранимый в константе User
, отличается после перезагрузки.
Очень важно понимать, что в Ruby нет способа настоящей перезагрузки классов и методов в памяти, и это отражается везде, где она используется. Технически "выгрузка" класса User
означает удаление константы User
с помощью Object.send(:remove_const, "User")
.
Например, вот сессия консоли Rails:
irb> joe = User.new
irb> reload!
irb> alice = User.new
irb> joe.class == alice.class
=> false
joe
это экземпляр первоначального класса User
. При перезагрузке константа User
вычисляется как другой, перезагруженный класс. alice
это экземпляр текущего класса, но не joe
- его класс устарел. Можно снова определить joe
, запустить подсессию IRB или просто запустить новую консоль вместо вызова reload!
.
Другой ситуацией, в которой можно обнаружить эту особенность, является создание подкласса перезагружаемого класса в месте, которое не перезагружается:
# lib/vip_user.rb
class VipUser < User
end
если User
перезагружается, то, так как VipUser
нет, суперклассом VipUser
является оригинальный устаревший объект класса.
Вывод: не кэшируйте перезагружаемые классы или модули.
Во время запуска приложения могут автоматически загружать из путей однократной автозагрузки, которые управляются автоматическим загрузчиком once
. Обратитесь к разделу config.autoload_once_paths
выше.
Однако, нельзя автоматически загружать из путей автозагрузки, управляемых автоматическим загрузчиком main
. Это применимо к коду инциализаторов в config/initializers
как приложения, так и engine-ов.
Почему? Они не запускаются повторно при перезагрузках. Если бы инициализатор использовал перезагружаемый класс или модуль, изменения в них не отразились бы на изначальном коде, сделав его устаревшим. Поэтому использование перезагружаемых констант во время инициализации запрещено.
Посмотрим, что можно сделать вместо этого.
Давайте представим, что ApiGateway
это перезагружаемый класс, и вам необходимо настроить его узел при запуске приложения:
# config/initializers/api_gateway_setup.rb
ApiGateway.endpoint = "https://example.com" # NameError
Инициализаторы не могут ссылаться на перезагружаемые константы, нужно обернуть нужный код блоком to_prepare
. Этот блок выполняется при запуске приложения, а также после каждой его перезагрузки:
# config/initializers/api_gateway_setup.rb
Rails.application.config.to_prepare do
ApiGateway.endpoint = "https://example.com" # ПРАВИЛЬНО
end
по историческим причинам этот колбэк может быть запущен дважды. Запускаемый код обязан быть идемпотентным.
Перезагружаемые классы и модули также могут быть перезагружены в блоках after_initialize
. Это выполняется при запуске, но не выполняется снова при перезагрузке. В некоторых исключительных случаях это может быть тем, что вам нужно.
Случаем использования для этого являются предполетные проверки:
# config/initializers/check_admin_presence.rb
Rails.application.config.after_initialize do
unless Role.where(name: "admin").exists?
abort "The admin role is not present, please seed the database."
end
end
Некоторые конфигурации принимают объект класса или модуля, и они хранят его в месте, не являющемся перезагружаемым. Важно, чтобы эти объекты не были перезагружаемыми, так как изменения в коде не повлияют на эти закэшированные устаревшие объекты.
Например, промежуточная программа:
config.middleware.use MyApp::Middleware::Foo
При перезагрузке стек промежуточных программ не затрагивается, таким образом, перезагрузка MyApp::Middleware::Foo
была бы бессмысленной. Изменения в его реализации никак не повлияет на его работу.
Другим примером являются сериализаторы Active Job:
# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer
Тот MoneySerializer
, вычисленный во время инициализации, добавляется к пользовательским сериализаторам, и этот объект остается там при перезагрузке.
Еще одним примером являются railties или engine, декорирующие классы фреймворка, добавляя модули. Например, turbo-rails
декорирует ActiveRecord::Base
следующим образом:
initializer "turbo.broadcastable" do
ActiveSupport.on_load(:active_record) do
include Turbo::Broadcastable
end
end
Это добавляет объект модуля в цепочку наследования для ActiveRecord::Base
. Изменения в Turbo::Broadcastable
не несут эффекта при перезагрузке, цепочка наследования все еще будет изначальной.
Вывод: Эти классы или модули нельзя перезагружать.
Стандартный способ организации таких файлов - разместить их в каталоге lib
и загружать их с помощью require
по мере необходимости. Например, если приложение имеет пользовательскую промежуточную программу в lib/middleware
, выполните обычный вызов require
перед ее настройкой:
require "middleware/my_middleware"
config.middleware.use MyMiddleware
Дополнительно, если lib
находится в путях автозагрузки, настройте автозагрузчик так, чтобы он игнорировал этот подкаталог:
# config/application.rb
config.autoload_lib(ignore: %w(assets tasks ... middleware))
так как вы сами загружаете эти файлы.
Как уже отмечено, другой опцией является нахождение их в директории, которая определена в путях однократной автозагрузки, и их автоматическая загрузка. За подробностями обратитесь к разделу о config.autoload_once_paths.
Представим, что engine работает с перезагружаемым классом приложения, который моделирует пользователей, и имеет для него точку конфигурации:
# config/initializers/my_engine.rb
MyEngine.configure do |config|
config.user_model = User # NameError
end
Для того, чтобы корректно работать с перезагружаемым кодом приложения, от engine требуется, чтобы приложения настраивали имя этого класса.
# config/initializers/my_engine.rb
MyEngine.configure do |config|
config.user_model = "User" # OK
end
Тогда во время выполнения config.user_model.constantize
предоставляет вам актуальный объект класса.
В средах, подобных production, обычно лучше загрузить весь код приложения при запуске приложения. Нетерпеливая загрузка помещает все в память для немедленного обслуживания запросов, это также сочетается с механизмом копирования при записи.
Нетерпеливая загрузка контролируется флажком config.eager_load
, который по умолчанию отключен во всех режимах, кроме production
. Когда выполняется задача Rake, config.eager_load
перезаписывается на config.rake_eager_load
, которое по умолчанию равно false
. Таким образом, по умолчанию в средах production задачи Rake не выполняют нетерпеливую загрузку приложения.
Порядок, в котором файлы нетерпеливо загружаются, не определен.
В течение нетерпеливой загрузки, Rails вызывает Zeitwerk::Loader.eager_load_all
. Это обеспечивает, что все зависимости от гемов, контролируемые Zeitwerk, будут также нетерпеливо загружены.
Наследование с единой таблицей не очень хорошо сочетается с ленивой загрузкой: Active Record должен знать об иерархии STI, чтобы работать корректно, но при ленивой загрузке классы загружаются только по требованию!
Чтобы справиться с этим фундаментальным несоответствием, нам нужно предварительно загрузить STI. Есть несколько вариантов достижения этого, с разными компромиссами. Давайте рассмотрим их.
Самый простой способ предварительно загрузить STI - это включить нетерпеливую загрузку, установив:
config.eager_load = true
в config/environments/development.rb
и config/environments/test.rb
.
Это просто, но дорого стоит, так как он нетерпеливо загружает все приложение при запуске и каждой перезагрузки. Хотя это может быть компромиссом для маленьких приложений.
Сохраните файлы, определяющие иерархию, в выделенную директорию, что также имеет смысл концептуально. Эта директория не предназначена для представления пространства имен, ее единственное назначение - группировка для STI:
app/models/shapes/shape.rb
app/models/shapes/circle.rb
app/models/shapes/square.rb
app/models/shapes/triangle.rb
В этом примере мы хотим, чтобы app/models/shapes/circle.rb
определял Circle
, а не Shapes::Circle
. Это может быть вашим личным предпочтением, чтобы упростить вещи, а также избежать рефакторинг в существующем коде. Особенность сворачивания в Zeitwerk позволяет нам делать так:
# config/initializers/preload_stis.rb
shapes = "#{Rails.root}/app/models/shapes"
Rails.autoloaders.main.collapse(shapes) # Не пространство имен.
unless Rails.application.config.eager_load
Rails.application.config.to_prepare do
Rails.autoloaders.main.eager_load_dir(shapes)
end
end
При этой опции мы нетерпеливо загружаем эти несколько файлов при запуске и перезагрузке, даже если STI не используется. Однако, если в вашем приложении не множество STI, это не принесет какого-либо измеримого воздействия.
Метод Zeitwerk::Loader#eager_load_dir
был добавлен в Zeitwerk 2.6.2. Для более старых версий, все еще можно перечислить директорию app/models/shapes
и вызвать require_dependency
на ее содержимом.
Если модели добавляются, изменяются или удаляются из STI, перезагрузка работает, как ожидается. Однако, если в приложение добавляется отдельная иерархия STI, необходимо отредактировать инициализатор и перезагрузить сервер.
Похожа на предыдущую, но директория будет пространством имен. То есть от app/models/shapes/circle.rb
ожидается определение Shapes::Circle
.
Для нее нужен такой же инициализатор, кроме настройки сворачивания:
# config/initializers/preload_stis.rb
unless Rails.application.config.eager_load
Rails.application.config.to_prepare do
Rails.autoloaders.main.eager_load_dir("#{Rails.root}/app/models/shapes")
end
end
Те же самые компромиссы.
Для этой опции нам не нужно организовывать файлы каким-то образом, но нужно запросить базу данных:
# config/initializers/preload_stis.rb
unless Rails.application.config.eager_load
Rails.application.config.to_prepare do
types = Shape.unscoped.select(:type).distinct.pluck(:type)
types.compact.each(&:constantize)
end
end
STI будет работать корректно, даже если в таблице нет всех типов, но методы, такие как subclasses
или descendants
не вернут отсутствующие типы.
Если модели добавляются, изменяются или удаляются из STI, перезагрузка работает, как ожидается. Однако, если в приложение добавляется отдельная иерархия STI, необходимо отредактировать инициализатор и перезагрузить сервер.
По умолчанию Rails использует String#camelize
, чтобы узнать, какую константу должны определять данный файл или директория. Например, posts_controller.rb
должен определять PostsController
, так как это то, что возвращает "posts_controller".camelize
.
Возможны случаи, когда имя определенного файла или директории не преобразуется в то, что вы хотите. Например, по умолчанию от html_parser.rb
ожидается определение HtmlParser
. Но что, если вы предпочитаете класс HTMLParser
? Есть несколько способов настроить это.
Самым простым способом является определение аббревиатур:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "HTML"
inflect.acronym "SSL"
end
Это глобально влияет на словообразование Active Support. Для некоторых приложений это отлично, но можно также настроить, как camelize отдельные базовые имена, независимо от Active Support, предоставив коллекцию переопределений в преобразователь по умолчанию:
Rails.autoloaders.each do |autoloader|
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
Эта техника все еще зависит от String#camelize
, хотя, так как преобразователь по умолчанию использует его как резервный. Если предпочитаете вообще не зависеть от словообразований Active Support, и получить полный контроль над словообразованием, настройте преобразователи быть экземплярами Zeitwerk::Inflector
:
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
"ssl_error" => "SSLError"
)
end
Нет какой-либо глобальной конфигурации, которая может повлиять на указанные экземпляры; они детерминированы.
Можно даже определить собственный преобразователь для полной гибкости. Пожалуйста, обратитесь за подробностями к документации Zeitwerk.
Если приложение не использует автозагрузчик once
, вышеприведенный код можно поместить в config/initializers
. Например, config/initializers/inflections.rb
для Active Support или config/initializers/zeitwerk.rb
для остальных.
Приложения, использующие автозагрузчик once
, должны переместить или загрузить эту конфигурацию в теле класса приложения в config/application.rb
, так как автозагрузчик once
использует словообразование рано в процессе запуска.
Как мы видели ранее, пути автозагрузки представляют собой корневое пространство имен: Object
.
Рассмотрим, например, app/services
. По умолчанию этот каталог не создается, но если он существует, Rails автоматически добавляет его в пути автозагрузки.
По умолчанию ожидается, что файл app/services/users/signup.rb
определит класс Users::Signup
, но что делать, если вы хотите, чтобы все это поддерево находилось под пространством имен Services
? С настройками по умолчанию этого можно добиться, создав подкаталог app/services/services
.
Однако, в зависимости от ваших предпочтений, это может вам не понравиться. Возможно, вам бы хотелось, чтобы app/services/users/signup.rb
просто определял класс Services::Users::Signup
.
Zeitwerk поддерживает пользовательские корневые пространства имен для решения этой задачи, и вы можете настроить автозагрузчик main
для достижения этого:
# config/initializers/autoloading.rb
# Пространство имен должно существовать.
#
# В этом примере мы определяем модуль на месте. Его также можно было бы создать
# в другом месте и загрузить его определение здесь с помощью обычного `require`.
# В любом случае, `push_dir` ожидает объект класса или модуля.
module Services; end
Rails.autoloaders.main.push_dir("#{Rails.root}/app/services", namespace: Services)
Rails < 7.1 не поддерживает эту функцию, но вы по-прежнему можете добавить следующий код в тот же файл, чтобы она заработала:
# Дополнительный код для приложений, работающих на Rails < 7.1.
app_services_dir = "#{Rails.root}/app/services" # должна быть строкой
ActiveSupport::Dependencies.autoload_paths.delete(app_services_dir)
Rails.application.config.watchable_dirs[app_services_dir] = [:rb]
Пользовательские пространства имен также поддерживаются для автозагрузчика once
. Однако, поскольку он настраивается на более ранней стадии загрузки приложения, его конфигурирование нельзя выполнять в инициализаторе приложения. Вместо этого разместите его, например, в config/application.rb
.
Engine запускаются в контексте родительского приложения, и их код автоматически загружается, перезагружается и нетерпеливо загружается родительским приложением. Если приложение запускается в режиме zeitwerk
, код engine загружается режимом zeitwerk
. Если приложение запускается в режиме classic
, код engine загружается режимом classic
.
При запуске Rails, директории engine добавляются в пути автозагрузки, и, с точки зрения автозагрузчика, они не отличаются. Основные входные данные автозагрузчиков это пути автозагрузки, а принадлежат ли они дереву исходного кода приложения или дереву исходного кода engine, это не важно.
Например, приложение использует Devise:
$ bin/rails runner 'pp ActiveSupport::Dependencies.autoload_paths'
[".../app/controllers",
".../app/controllers/concerns",
".../app/helpers",
".../app/models",
".../app/models/concerns",
".../gems/devise-4.8.0/app/controllers",
".../gems/devise-4.8.0/app/helpers",
".../gems/devise-4.8.0/app/mailers"]
Если engine контролирует режим автозагрузки родительского приложения, можно писать engine как обычно.
Однако, если engine поддерживает Rails 6 или Rails 6.1 и не контролирует его родительское приложение, ему необходимо быть готовым запускаться либо в режиме classic
, либо в режиме zeitwerk
. Нужно принять во внимание:
Если режиму classic
может понадобиться вызов require_dependency
, чтобы убедиться, что некоторая константа загружена в некотором месте, напишите его. Хотя он и не нужен в режиме zeitwerk
, ничего страшного, он будет работать и в режиме zeitwerk
.
Режим classic
подчеркивает имена констант ("User" -> "user.rb"), а режим zeitwerk
озаглавливает имена файлов ("user.rb" -> "User"). Они совпадают в большинстве случаев, но не всегда, когда есть ряд последовательных заглавных букв, как в "HTMLParser". Простейшим способом обеспечить совместимость является избегание таких имен. В данном случае выберите "HtmlParser".
В режиме classic
, файлу app/model/concerns/foo.rb
можно определять и Foo
, и Concerns::Foo
. В режиме zeitwerk
имеется лишь одна опция: он должен определять Foo
. Для совместимости определяйте Foo
.
Задание zeitwerk:check
проверяет, что дерево проекта следует ожидаемым соглашениям по именованию, и это удобно для ручных проверок. Например, если вы мигрируете с режима classic
на zeitwerk
, или чините что-то:
$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!
Может быть дополнительный вывод, в зависимости от конфигурации приложения, но последний "All is good!" это то, что вам нужно.
Хорошей практикой является проверка в тестовом наборе, что проект корректно нетерпеливо загружается.
Это покрывает соглашения по именованию Zeitwerk и другие возможные условия ошибки. Обратитесь к разделу о тестировании нетерпеливой загрузки в руководстве Тестирование приложений на Rails.
Лучшим способом понять, что делают загрузчики, является наблюдение за их активностью.
Простейший способ для этого - включить
Rails.autoloaders.log!
в config/application.rb
после загрузки умолчаний для фреймворка. Это напечатает трейсы в стандартный вывод.
Если предпочитаете логирование в файл, настройте так:
Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")
Логгер Rails пока еще не доступен при запуске config/application.rb
. Если предпочитаете использовать логгер Rails, сконфигурируйте эту настройку в инициализаторе:
# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger
Экземпляры Zeitwerk, управляющие вашим приложением, доступны в
Rails.autoloaders.main
Rails.autoloaders.once
Предикат
Rails.autoloaders.zeitwerk_enabled?
все еще доступен в приложениях Rails 7, и возвращает true
.