Skip to content

Commit 2ec2ee6

Browse files
committed
Revert STI samples in autoloading guide
In general, you cannot do that due to what is documented here https://github.com/fxn/zeitwerk#beware-of-circular-dependencies Back to square one. By now.
1 parent 5e1a039 commit 2ec2ee6

File tree

1 file changed

+52
-25
lines changed

1 file changed

+52
-25
lines changed

guides/source/autoloading_and_reloading_constants.md

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -314,44 +314,71 @@ Single Table Inheritance is a feature that doesn't play well with lazy loading.
314314

315315
In a sense, applications need to eager load STI hierarchies regardless of the loading mode.
316316

317-
Of course, if the application eager loads on boot, that is already accomplished. When it does not, there are two options.
318-
319-
### Enumerate the leaves of the hierarchy
320-
321-
Since the leaves of the hierarchy connect all the hierarchy nodes upwards, following super classes, as soon as the root of the hierarchy is loaded, you can force loading them all:
317+
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:
322318

323319
```ruby
324-
unless Rails.application.config.eager_load
325-
Rails.autoloaders.main.on_load("RootSTIModel") do
326-
Leaf1
327-
Leaf2
328-
Leaf3
320+
module StiPreload
321+
unless Rails.application.config.eager_load
322+
extend ActiveSupport::Concern
323+
324+
included do
325+
cattr_accessor :preloaded, instance_accessor: false
326+
end
327+
328+
class_methods do
329+
def descendants
330+
preload_sti unless preloaded
331+
super
332+
end
333+
334+
# Constantizes all types present in the database. There might be more on
335+
# disk, but that does not matter in practice as far as the STI API is
336+
# concerned.
337+
#
338+
# Assumes store_full_sti_class is true, the default.
339+
def preload_sti
340+
types_in_db = \
341+
base_class.
342+
unscoped.
343+
select(inheritance_column).
344+
distinct.
345+
pluck(inheritance_column).
346+
compact
347+
348+
types_in_db.each do |type|
349+
logger.debug("Preloading STI type #{type}")
350+
type.constantize
351+
end
352+
353+
self.preloaded = true
354+
end
355+
end
329356
end
330357
end
331358
```
332359

333-
This approach is easy and loads the entire STI hierarchy, but you need to maintain this list by hand. This may be OK.
360+
and then include it in the STI root classes of your project:
334361

335-
### Load what's in the database
362+
```ruby
363+
# app/models/shape.rb
364+
require "sti_preload"
336365

337-
As far as Active Record is concerned, in practice it may be enough to load what's in the database. Normally, in the environments where eager load is disabled, you can afford this query:
366+
class Shape < ApplicationRecord
367+
include StiPreload # Only in the root class.
368+
end
369+
```
338370

339371
```ruby
340-
# config/initializers/preload_stis.rb
341-
unless Rails.application.config.eager_load
342-
Rails.autoloaders.main.on_load("RootSTIModel") do |klass|
343-
klass.connection.select_values(<<~SQL).each(&:constantize)
344-
SELECT DISTINCT("#{klass.inheritance_column}")
345-
FROM "#{klass.table_name}"
346-
WHERE "#{klass.inheritance_column}" IS NOT NULL
347-
SQL
348-
end
372+
# app/models/polygon.rb
373+
class Polygon < Shape
349374
end
350375
```
351376

352-
This approach does not need manual maintenance. However, if you need an exhaustive enumeration to fill a dropdown or something, and the database does not have rows for all types, you'll miss some.
353-
354-
Both approaches have compromises.
377+
```ruby
378+
# app/models/triangle.rb
379+
class Triangle < Polygon
380+
end
381+
```
355382

356383
Customizing Inflections
357384
-----------------------

0 commit comments

Comments
 (0)