Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autoloaded classes raise NameError when accessed in parent's on_load hook during load #51327

Open
bensheldon opened this issue Mar 14, 2024 · 0 comments

Comments

@bensheldon
Copy link
Contributor

bensheldon commented Mar 14, 2024

This is not a new issue with Rails autoloading, but a challenge I feel when working with Engines (either developing engines as a gem maintainer or consuming engines as an application developer)

For example, an Engine may have a setup like this:

module GoodJob
  class ApplicationJob < ActiveJob::Base
    # some configuration
    ActiveSupport.run_load_hooks(:good_job_application_job, self)
  end

  class UtilityOneJob < ApplicationJob; end
  class UtilityTwoJob < ApplicationJob; end
  class UtilityThreeJob < ApplicationJob; end
  # ... and many more
end

I give an example of GoodJob, but this is a very analogous situation to other Engines with autoloaded classes/subclasses (models, controllers, etc.); for example Devise, which has a DeviseController (with load_hook) that is then subclassed with a desire to extend.

I want to be able to allow application developers to extend the subclasses, and I want to defer autoloaded constants as much as possible (ideally not touching Action Controller or Active Model at all until during initialization), and this seems like it should be reasonable:

# config/initializers/good_job.rb

ActiveSupport.on_load(:good_job_application_job) do
  # Customize a subclass
  GoodJob::UtilityJobOne.queue_name = "something_custom"
end

This usually works ok, except in the case when the UtilityJobOne being loaded causes ApplicationJob to be loaded for the very first time, in which case it raises a uninitialized constant UtilityJobOne (NameError). This is because of the autoloading chain:

GoodJob::UtilityJobOne first must load its parent class GoodJob::ApplicationJob for the first time, which triggers the load_hook which itself references UtilityOneJob which has not yet completed loading and thus a NameError 💥

There is what I consider a workaround for this which is to use inherited (which is triggered _after the subclass constant is loaded), but I find it somewhat complex and wonder if there is a better way, or whether this is something that should be turned into a hook pattern:

# config/initializers/good_job.rb

module ConfigureUtilityJob
  def inherited(subclass)
    super
    if subclass.to_s == "UtilityJobOne"
      subclass.queue_name = "something_custom"
    end
  end
end

ActiveSupport.on_load(:good_job_application_job) do
  GoodJob::ApplicationJob.extend ConfigureUtilityJob
end

I realize that another solution as a engine maintainer is to add load hooks to every subclass, but that seems onerous, and as an application developer I'd have to convince other engine maintainers to do so (which is totally ok, so long as there isn't a better way I'm missing or a reasonable central interface for modifying autoloaded subclasses).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant