You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: guides/source/autoloading_and_reloading_constants.md
+64-50Lines changed: 64 additions & 50 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -327,76 +327,90 @@ During eager loading, Rails invokes `Zeitwerk::Loader.eager_load_all`. That ensu
327
327
Single Table Inheritance
328
328
------------------------
329
329
330
-
Single Table Inheritance is a feature that doesn't play well with lazy loading. The reason is that its API generally needs to be able to enumerate the STI hierarchy to work correctly, whereas lazy loading defers loading classes until they are referenced. You can't enumerate what you haven't referenced yet.
330
+
Single Table Inheritance doesn't play well with lazy loading: Active Record has to be aware of STI hierarchies to work correctly, but when lazy loading, classes are precisely loaded only on demand!
331
331
332
-
In a sense, applications need to eager load STI hierarchies regardless of the loading mode.
332
+
To address this fundamental mismatch we need to preload STIs. There are a few options to accomplish this, with different trade-offs. Let's see them.
333
333
334
-
Of course, if the application eager loads on boot, that is already accomplished. When it does not, it is in practice enough to instantiate the existing types in the database, which in development or test modes is usually fine. One way to do that is to include an STI preloading module in your `lib` directory:
334
+
### Option 1: Enable Eager Loading
335
+
336
+
The easiest way to preload STIs is to enable eager loading by setting:
# Constantizes all types present in the database. There might be more on
352
-
# disk, but that does not matter in practice as far as the STI API is
353
-
# concerned.
354
-
#
355
-
# Assumes store_full_sti_class is true, the default.
356
-
defpreload_sti
357
-
types_in_db = \
358
-
base_class.
359
-
unscoped.
360
-
select(inheritance_column).
361
-
distinct.
362
-
pluck(inheritance_column).
363
-
compact
364
-
365
-
types_in_db.each do |type|
366
-
logger.debug("Preloading STI type #{type}")
367
-
type.constantize
368
-
end
369
-
370
-
self.preloaded =true
371
-
end
372
-
end
373
-
end
374
-
end
339
+
config.eager_load =true
375
340
```
376
341
377
-
and then include it in the STI root classes of your project:
342
+
in `config/environments/development.rb` and `config/environments/test.rb`.
343
+
344
+
This is simple, but may be costly because it eager loads the entire application on boot and on every reload. The trade-off may be worthwhile for small applications, though.
345
+
346
+
### Option 2: Preload a Collapsed Directory
347
+
348
+
Store the files that define the hierarchy in a dedicated directory, which makes sense also conceptually. The directory is not meant to represent a namespace, its sole purpose is to group the STI:
349
+
350
+
```
351
+
app/models/shapes/shape.rb
352
+
app/models/shapes/circle.rb
353
+
app/models/shapes/square.rb
354
+
app/models/shapes/triangle.rb
355
+
```
356
+
357
+
In this example, we still want `app/models/shapes/circle.rb` to define `Circle`, not `Shapes::Circle`. This may be your personal preference to keep things simple, and also avoids refactors in existing code bases. The [collapsing](https://github.com/fxn/zeitwerk#collapsing-directories) feature of Zeitwerk allows us to do that:
378
358
379
359
```ruby
380
-
# app/models/shape.rb
381
-
require"sti_preload"
360
+
# config/initializers/preload_stis.rb
382
361
383
-
classShape < ApplicationRecord
384
-
includeStiPreload# Only in the root class.
362
+
unlessRails.application.config.eager_load
363
+
shapes ="#{Rails.root}/app/models/shapes"
364
+
Rails.autoloaders.main.collapse(shapes) # Not a namespace.
365
+
Rails.application.config.to_prepare do
366
+
Rails.autoloaders.main.eager_load_dir(shapes)
367
+
end
385
368
end
386
369
```
387
370
371
+
In this option, we eager load these few files on boot and reload even if the STI is not used. However, unless your application has a lot of STIs, this won't have any measurable impact.
372
+
373
+
INFO: The method `Zeitwerk::Loader#eager_load_dir` was added in Zeitwerk 2.6.2. For older versions, you can still list the `app/models/shapes` directory and invoke `require_dependency` on its contents.
374
+
375
+
WARNING: If models are added, modified, or deleted from the STI, reloading works as expected. However, if a new separate STI hierarchy is added to the application, you'll need to edit the initializer and restart the server.
376
+
377
+
### Option 3: Preload a Regular Directory
378
+
379
+
Similar to the previous one, but the directory is meant to be a namespace. That is, `app/models/shapes/circle.rb` is expected to define `Shapes::Circle`.
380
+
381
+
For this one, the initializer is the same except no collapsing is configured:
WARNING: The STI will work correctly even if the table does not have all the types, but methods like `subclasses` or `descendants` won't return the missing types.
411
+
412
+
WARNING: If models are added, modified, or deleted from the STI, reloading works as expected. However, if a new separate STI hierarchy is added to the application, you'll need to edit the initializer and restart the server.
0 commit comments