Home > Software design >  Zeitwerk Rails 6.1 with model and namespaced subclasses, "earlier autoload discarded"
Zeitwerk Rails 6.1 with model and namespaced subclasses, "earlier autoload discarded"

Time:12-08

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
  • Related