Search code examples
ruby-on-railszeitwerk

Zeitwerk vs list of classes in a variable


I'm trying to cache a class list and I'm wondering how it is supposed to be done with zeitwerk.

I obtain the class list like:

    def models
      return @models if @models

      Zeitwerk::Loader.eager_load_all # this is to see all models in dev env, otherwise some may not be yet loaded, hence not present in the list
      @models = ActiveRecord::Base.descendants.select { |model| model.include?(SomeModule) }
    end

But then any moment zeitwerk may reload the classes and invalidate content of @model. This is my understanding.

So how am I supposed to have a cache of classes in a Rails dev environment?

Bonus question, how do I eager load a single directory programatically? That is to load only what is needed to be loaded instead of the whole app.

P.S. This class list will only be used in specific rake tasks and in tests, not in production runtime code. Just saying to avoid complaints that this should not be done.

Update (just illustration of how it's done):

class ModelFoo
  annotated
  ...
end
class ModelBar
  annotated
  ...
end
module Annotating
  extend ActiveSupport::Concern

  module Model
    extend ActiveSupport::Concern

    included do
      has_many :annotations, as: :annotated, dependent: :destroy, autosave: true
    end

    ...
  end
  class_methods do
    def annotated
      class_eval do
        include Model
      end
    end
  end

  class << self
    def models
      <see above the body of this method>
    end
  end
end
ActiveRecord::Base.include(Annotating::Model)

Later use Annotating.models to generate a trigger for all annotated models and for testing all annotated models with some standard operations.


Solution

  • To eager load a directory in a way that works well after reloads, do this:

    # config/initializers/eager_load_foo.rb
    
    # Run when the application boots, and on each reload.
    Rails.application.config.to_prepare do
      Rails.autoloaders.main.eager_load_dir("#{Rails.root}/my/dir")
    end
    

    That needs Zeitwerk 2.6.2 or later (docs). Zeitwerk is also able to eager load namespaces if that would match better your use case (docs).

    That won't conflict in production, because eager loading is idempotent (but compatible with reloading, as the example above shows).