Skip to content

Commit cb0c746

Browse files
committed
Revises the autoloading guide [skip ci]
1 parent cabe311 commit cb0c746

File tree

1 file changed

+44
-45
lines changed

1 file changed

+44
-45
lines changed

guides/source/autoloading_and_reloading_constants.md

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Please, check the [Zeitwerk documentation](https://github.com/fxn/zeitwerk#file-
6969
config.autoload_paths
7070
---------------------
7171

72-
We refer to the list of application directories whose contents are to be autoloaded as _autoload paths_. For example, `app/models`. Such directories represent the root namespace: `Object`.
72+
We refer to the list of application directories whose contents are to be autoloaded and (optionally) reloaded as _autoload paths_. For example, `app/models`. Such directories represent the root namespace: `Object`.
7373

7474
INFO. Autoload paths are called _root directories_ in Zeitwerk documentation, but we'll stay with "autoload path" in this guide.
7575

@@ -84,20 +84,46 @@ $ bin/rails runner 'p UsersHelper'
8484
UsersHelper
8585
```
8686

87-
Autoload paths automatically pick up any custom directories under `app`. For example, if your application has `app/presenters`, or `app/services`, etc., they are added to autoload paths.
87+
Rails adds custom directories under `app` to the autoload paths automatically. For example, if your application has `app/presenters`, you don't need to configure anything in order to autoload presenters, it works out of the box.
8888

89-
The array of autoload paths can be extended by pushing to `config.autoload_paths`, in `config/application.rb` or `config/environments/*.rb`.
89+
The array of default autoload paths can be extended by pushing to `config.autoload_paths`, in `config/application.rb` or `config/environments/*.rb`. For example:
90+
91+
```ruby
92+
module MyApplication
93+
class Application < Rails::Application
94+
config.autoload_paths << "#{root}/extras"
95+
end
96+
end
97+
```
98+
99+
Also, engines can push in body of the engine class and in their own `config/environments/*.rb`.
90100

91101
WARNING. Please do not mutate `ActiveSupport::Dependencies.autoload_paths`; the public interface to change autoload paths is `config.autoload_paths`.
92102

93-
These paths are managed by the `Rails.autoloaders.main` autoloader.
103+
WARNING: You cannot autoload code in the autoload paths while the application boots. It particular, directly in `config/initializers/*.rb`. Please check [_Autoloading when the application boots_](#autoloading-when-the-application-boots) down below for valid ways to do that.
94104

95-
WARNING: The classes and modules defined in the autoload paths are reloadable. Therefore, you cannot autoload them in initializers. Please check [_Autoloading when the application boots_](#autoloading-when-the-application-boots) down below for valid ways to do that.
105+
The autoload paths are managed by the `Rails.autoloaders.main` autoloader.
96106

97107
config.autoload_once_paths
98108
--------------------------
99109

100-
Occasionally, you may want to be able to autoload classes and modules without reloading them. This is key for classes and modules that are cached somewhere.
110+
You may want to be able to autoload classes and modules without reloading them. The autoload once paths store code that can be autoloaded, but won't be reloaded.
111+
112+
By default, this collection is empty, but you can extend it pushing to `config.autoload_once_paths`. You can do so in `config/application.rb` or `config/environments/*.rb`. For example:
113+
114+
```ruby
115+
module MyApplication
116+
class Application < Rails::Application
117+
config.autoload_once_paths << "#{root}/app/serializers"
118+
end
119+
end
120+
```
121+
122+
Also, engines can push in body of the engine class and in their own `config/environments/*.rb`.
123+
124+
INFO. If `app/serializers` is pushed to `config.autoload_once_paths`, Rails no longer considers this an autoload path, despite being a custom directory under `app`. This setting overrides that rule.
125+
126+
This is key for classes and modules that are cached in places that survive reloads, like the Rails framework itself.
101127

102128
For example, Active Job serializers are stored inside Active Job:
103129

@@ -106,6 +132,8 @@ For example, Active Job serializers are stored inside Active Job:
106132
Rails.application.config.active_job.custom_serializers << MoneySerializer
107133
```
108134

135+
and Active Job itself is not reloaded when there's a reload, only application and engines code in the autoload paths is.
136+
109137
Making `MoneySerializer` reloadable would be confusing, because reloading an edited version would have no effect on that class object stored in Active Job. Indeed, if `MoneySerializer` was reloadable, starting with Rails 7 such initializer would raise a `NameError`.
110138

111139
Another use case are engines decorating framework classes:
@@ -120,26 +148,16 @@ end
120148

121149
There, the module object stored in `MyDecoration` by the time the initializer runs becomes an ancestor of `ActionController::Base`, and reloading `MyDecoration` is pointless, it won't affect that ancestor chain.
122150

123-
The directories in `config.autoload_once_paths` are managed by `Rails.autoloaders.once` and cover those use cases by allowing you to autoload classes and modules that won't be reloaded.
124-
125-
The array of autoload once paths can be extended by pushing to `config.autoload_once_paths`, in `config/application.rb` or `config/environments/*.rb`. For example:
126-
127-
```ruby
128-
module MyApplication
129-
class Application < Rails::Application
130-
config.autoload_once_paths << "#{root}/app/serializers"
131-
end
132-
end
133-
```
134-
135151
Classes and modules from the autoload once paths can be autoloaded in `config/initializers`. So, with that configuration this works:
136152

137153
```ruby
138154
# config/initializer/custom_serializers.rb
139155
Rails.application.config.active_job.custom_serializers << MoneySerializer
140156
```
141157

142-
INFO: Technically, you can autoload classes and modules managed by the `once` autoloader in any initializer that runs after `:bootstrap_hook`.
158+
INFO: Technically, you can autoload classes and modules managed by the `once` autoloader in any initializer that runs after `:bootstrap_hook`.
159+
160+
The autoload once paths are managed by `Rails.autoloaders.once`.
143161

144162
$LOAD_PATH
145163
----------
@@ -156,7 +174,7 @@ That may speed up legitimate `require` calls a bit since there are fewer lookups
156174
Reloading
157175
---------
158176

159-
Rails automatically reloads classes and modules if application files change.
177+
Rails automatically reloads classes and modules if application files in the autoload paths change.
160178

161179
More precisely, if the web server is running and application files have been modified, Rails unloads all autoloaded constants just before the next request is processed. That way, application classes or modules used during that request will be autoloaded again, thus picking up their current implementation in the file system.
162180

@@ -184,26 +202,7 @@ As you can see, the class object stored in the `User` constant is different afte
184202

185203
It is very important to understand that Ruby does not have a way to truly reload classes and modules in memory, and have that reflected everywhere they are already used. Technically, "unloading" the `User` class means removing the `User` constant via `Object.send(:remove_const, "User")`.
186204

187-
Therefore, code that references a reloadable class or module, but that is not executed again on reload, becomes stale. Let's see an example next.
188-
189-
Let's consider this initializer:
190-
191-
```ruby
192-
# config/initializers/configure_payment_gateway.rb
193-
# DO NOT DO THIS.
194-
$PAYMENT_GATEWAY = Rails.env.production? ? RealGateway : MockedGateway
195-
# DO NOT DO THIS.
196-
```
197-
198-
The idea would be to use `$PAYMENT_GATEWAY` in the code, and let the initializer set that to the actual implementation depending on the environment.
199-
200-
On reload, `MockedGateway` is reloaded, but `$PAYMENT_GATEWAY` is not updated because initializers only run on boot. Therefore, it won't reflect the changes.
201-
202-
There are several ways to do this safely. For instance, the application could define a class method `PaymentGateway.impl` whose definition depends on the environment; or could define `PaymentGateway` to have a parent class or mixin that depends on the environment; or use the same global variable trick, but in a reloader callback, as explained below.
203-
204-
Let's see other situations that involve stale class or module objects.
205-
206-
Check out this Rails console session:
205+
For example, check out this Rails console session:
207206

208207
```irb
209208
irb> joe = User.new
@@ -232,8 +231,8 @@ Bottom line: **do not cache reloadable classes or modules**.
232231
Applications can safely autoload constants during boot using a reloader callback:
233232

234233
```ruby
235-
Rails.application.reloader.to_prepare do
236-
$PAYMENT_GATEWAY = Rails.env.production? ? RealGateway : MockedGateway
234+
Rails.application.config.to_prepare do
235+
ApiGateway.endpoint = "https://example.com"
237236
end
238237
```
239238

@@ -253,6 +252,8 @@ config.middleware.use MyApp::Middleware::Foo
253252

254253
To have changes in that middleware reflected, you need to restart the server.
255254

255+
Another possibility is to autoload from the autoload once paths. Please check the section [`config.autoload_once_paths`](#config-autoload-once-paths) above.
256+
256257

257258
Eager Loading
258259
-------------
@@ -451,12 +452,10 @@ Rails.autoloaders.main
451452
Rails.autoloaders.once
452453
```
453454

454-
The former is the main one. The latter is there mostly for backwards compatibility reasons, in case the application has something in `config.autoload_once_paths` (this is discouraged nowadays).
455-
456455
The predicate
457456

458457
```ruby
459458
Rails.autoloaders.zeitwerk_enabled?
460459
```
461460

462-
is still available in Rails 7 applications, for compatibility with engines may want to support Rails 6.x. It just returns `true`.
461+
is still available in Rails 7 applications, and returns `true`.

0 commit comments

Comments
 (0)