After researching this on SO and a very similar issue in Rails' GitHub issues, I'm still unclear what's wrong. My namespaced model subclasses are not eager-loaded, but I believe they are declared correctly and in the right place.
They do seem to be autoloaded and are accessible, but each one does not show up in subclasses
of the parent class until they are instantiated.
The parent model:
# /app/models/queued_email.rb
class QueuedEmail < ApplicationRecord
end
My namespaced subclass models (there are a dozen):
# /app/models/queued_email/comment_notification.rb
class QueuedEmail::CommentNotification < QueuedEmail
end
# or alternatively (this also doesn't eager load):
module QueuedEmail
class CommentNotification < QueuedEmail
end
end
The relevant message from Rails.autoloaders.log!
(in config/application.rb
)
[email protected]: autoload set for QueuedEmail, to be autovivified from /vagrant/rails_app/app/models/queued_email
[email protected]: earlier autoload for QueuedEmail discarded, it is actually an explicit namespace defined in /vagrant/rails_app/app/models/queued_email.rb
[email protected]: autoload set for QueuedEmail, to be loaded from /vagrant/rails_app/app/models/queued_email.rb
If I open rails console
and call subclasses, i get nothing:
> QueuedEmail
=> QueuedEmail (call 'QueuedEmail.connection' to establish a connection)
> QueuedEmail.subclasses
[]
But then... the subclass is accessible.
> QueuedEmail::CommentNotification
=> QueuedEmail::CommentNotification(id: integer...)
> QueuedEmail::CommentNotification.superclass
=> QueuedEmail(id: integer...)
> QueuedEmail.subclasses
=> [QueuedEmail::CommentNotification(id: integer...)]
I get nothing in subclasses until each one is instantiated in the code. Is my app/models
folder incorrectly organized, or my subclasses incorrectly named?
CodePudding user response:
Let me first explain the log messages.
Zeitwerk scans the project, and found a directory called queued_email
before finding queued_email.rb
. So, as a working hypothesis it assumed QueuedEmail
was an implicit namespace with the information that it had. This hypothesis got later invalidated when it saw queued_email.rb
, and said "wait, this is actually an explicit namespace". So it undid the implicit setup, and redefined it to load an explicit namespace.
Now, let's go for the subclasses.
When an application does not eager load, files are only loaded on demand. For example, if you load QueuedEmail
, and app/models/queued_email
has 24 files recursively, none of them are loaded until they are used.
When a class is subclassed, the collection returned by subclasses
is populated. But you don't know a class is subclassed until the subclass is loaded. Therefore, in a lazy loading environment subclasses
is empty at the start. If you load 1 subclass it will have that one, but not the rest, until they are all eventually loaded.
If you need the subclasses to be there for the application to function properly, starting with Zeitwerk 2.6.2 you can throw this to an initializer
# config/initializers/eager_load_queued_email.rb
Rails.application.config.to_preprare do
Rails.autoloaders.main.eager_load_dir("#{Rails.root}/app/models/queued_email")
end