30

I would like to be able to load an entire app so that I may find the descendants of a given class.

For example given I have the following class defined:

# app/models/foo_class.rb
class FooClass < MySpecialBaseClass
  # pasta code
end

It won't be found with:

irb> ObjectSpace.each_object.select { |obj| obj.is_a?(Class) && obj <= MySpecialBaseClass }
=> []

Until I call the const:

irb> FooClass

Then it is returned:

irb> ObjectSpace.each_object.select { |obj| obj.is_a?(Class) && obj <= MySpecialBaseClass } 

=> [FooClass]

How would I go about doing this?

Jason Waldrip
  • 5,038
  • 8
  • 36
  • 59
  • This question appears to have been [addressed already](http://stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby). Does this not work for you? – PinnyM Nov 11 '13 at 18:32
  • 1
    This is essentially what I am doing, but it does not address the autoload problem. – Jason Waldrip Nov 11 '13 at 21:47

4 Answers4

41

Well, after some digging, it actually is really simple. Just need to run the following.

Rails.application.eager_load!
Jason Waldrip
  • 5,038
  • 8
  • 36
  • 59
13

After a great deal of trial and error, I recently learned that Jason Waldrip's answer is somewhat incomplete. As of Rails 5, Rails.application.eager_load! will indeed load all of the directories specified in config/application.rb. But it doesn't match Rails' actual behavior in production. To do that, one must instead mirror what Rails does:

Rails.configuration.eager_load_namespaces.each(&:eager_load!)

The salient difference between the approaches is that OPs answer won't eager load the files within app directories of Engines that live in the gems or vendor folders. Whereas Rails itself will identify where subclasses of Engine exist and will see to it that the appropriate app subdirectories are eager-loaded.

Behind the scenes

Rails 5 adds eager load directories in railties-5.0.2/lib/rails/engine/configuration.rb:39, where it runs

      paths.add "app",                 eager_load: true, glob: "{*,*/concerns}"
      paths.add "app/assets",          glob: "*"
      paths.add "app/controllers",     eager_load: true
      paths.add "app/channels",        eager_load: true, glob: "**/*_channel.rb"
      paths.add "app/helpers",         eager_load: true
      paths.add "app/models",          eager_load: true
      paths.add "app/mailers",         eager_load: true

These directories are not currently included in a default Rails.application.eager_load!

wbharding
  • 4,213
  • 2
  • 30
  • 25
  • 1
    This is throwing /railties-5.2.2/lib/rails/engine.rb:478:in `block (2 levels) in eager_load!': undefined method `require_dependency' for # (NoMethodError) Any ideas on what might be the problem? – Tim Reistetter Jan 23 '19 at 21:18
10

From Configuring Rails Applications

  1. config.eager_load when true Eager loads all registered config.eager_load_namespaces. This includes your application, engines, Rails frameworks and any other registered namespace.
  2. config.eager_load_namespaces registers namespaces that are eager loaded when config.eager_load is true. All namespaces in the list must respond to the eager_load! method.
  3. config.eager_load_paths accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the app directory of the application.

EDIT:

To manually load you should be able to do something like:

matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
  require_dependency file.sub(matcher, '\1')
end
Gupta
  • 8,882
  • 4
  • 49
  • 59
user2657413
  • 167
  • 4
10

With Rails 6, Zeitwerk became the default code loader.

Try the following for eager loading:

Zeitwerk::Loader.eager_load_all
davidb-st
  • 3
  • 3
demir
  • 4,591
  • 2
  • 22
  • 30