Autloading vs Eager loading

The difference between autoloading and eager loading might be a little tricky to understand at first. Both concepts look similar but solves different problems.

Autoloading

In a typical Ruby code, dependencies need to be loaded by hand using i.e. require method. To make the code easier to write and not to worry about the dependencies, frameworks like Ruby on Rails implements code loaders to autoload all the required constants for you. With autoloading enabled the code is loaded on-the-fly when required, based on the class/module name.

In Rails there is a list of predefined folders the autoloader checks to find the required code. The list is defined in Rails::Engine::Configuration#paths method:

 def paths
        @paths ||= begin
          paths = Rails::Paths::Root.new(@root)

          paths.add "app",                 eager_load: true,
                                           glob: "{*,*/concerns}",
                                           exclude: ["assets", javascript_path]
          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
          paths.add "app/views"

          paths.add "lib",                 load_path: true
          paths.add "lib/assets",          glob: "*"
          paths.add "lib/tasks",           glob: "**/*.rake"

          paths.add "config"
          paths.add "config/environments", glob: -"#{Rails.env}.rb"
          paths.add "config/initializers", glob: "**/*.rb"
          paths.add "config/locales",      glob: "**/*.{rb,yml}"
          paths.add "config/routes.rb"
          paths.add "config/routes",       glob: "**/*.rb"

          paths.add "db"
          paths.add "db/migrate"
          paths.add "db/seeds.rb"

          paths.add "vendor",              load_path: true
          paths.add "vendor/assets",       glob: "*"

          paths
        end
      end

By default, it includes an app with all subdirectories that exist when the application boots, except for assets, javascript and views, plus the autoload paths of engines it might depend on.

If you add your code to any of the app folders or subfolders and follow the naming convention, the code will be loaded automatically at the moment you use it for the first time.

Please note the lib folder from the main (not app) directory is not included. This is by design, and you should not add your code there if you want it to be autoloaded. It is possible to extend the array of autoload paths by adding config.autoload_paths in config/application.rb, but nowadays this is discouraged. There are a lot of discussions if this is a good practice to put some of your code to app/lib instead of lib folder, but this is a different topic.

Autoloading example

I created a brand new Rails app (Test1) and added two models, User and Order. I also enabled autoload logging (added Rails.autoloaders.log! to config/application.rb) to see which files were autoloaded. Now we can check it using Rails console.

irb(main):001:0> User
Zeitwerk@rails.main: constant ApplicationRecord loaded from file /home/paul/dev/test1/app/models/application_record.rb
Zeitwerk@rails.main: constant User loaded from file /home/paul/dev/test1/app/models/user.rb
=> User
irb(main):002:0> User
=> User
irb(main):003:0> Order
Zeitwerk@rails.main: constant Order loaded from file /home/paul/dev/test1/app/models/order.rb
=> Order
irb(main):004:0> Order
=> Order

When the class name (User) was used for the first time, two files were loaded, user.rb and application_record.rb with an ApplicationRecord class User inherits from. When the User class was called for the second time, nothing more was loaded. When the Order class was called for the first time, only the order.rb file was loaded, as the ApplicationRecord was already in memory.

Autoloading summary

In general, the process of finding a proper file based on the constant name is not trivial. Rails 6 is using Zeitwerk framework for autoloading. If you want to learn more about the details, please check Rails documentation and the Zeitwerk project on GitHub.

Eager loading

Unfortunately, autloading is not thread-safe and for multithreading environments we have to make sure all constants are loaded when the application starts. That concept is called eager loading and loads all the code to memory upfront. Starting from Rails 5 eager loading is enabled by default for the production environment.

The default list of folders to eager load is the same as for autoloading and you can extend that list by adding a similar line to your config/application.yml file:

config.paths.add "extras", eager_load: true

Eager loading example

We are going to use the same app, with User and Order models. For the test purposes I changed the config.eager_load flag in the config/environments/development.rb file to true to enable eager loading.

# config/environments/development.rb

Rails.application.configure do
  config.eager_load = true
end

At the moment Rails starts, both models are loaded automatically.

$ rails s
=> Booting Puma
=> Rails 6.1.3.2 application starting in development
=> Run `bin/rails server --help` for more startup options
[...]
Zeitwerk@rails.main: autoload set for ApplicationRecord, to be loaded from /home/paul/dev/test1/app/models/application_record.rb
Zeitwerk@rails.main: autoload set for Order, to be loaded from /home/paul/dev/test1/app/models/order.rb
Zeitwerk@rails.main: autoload set for User, to be loaded from /home/paul/dev/test1/app/models/user.rb
[...]
Puma starting in single mode...
* Puma version: 5.3.2 (ruby 2.7.1-p83) ("Sweetnighter")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 2745
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000

User and Order classes were autoloaded upfront to memory and ready to use.

Summary

To summarise, autoloading is responsible to find and load into memory a constant definition we want to use. On the other hand, eager loading puts all the application code, found using autoloading, in memory when the application boots.

ps. There is also a eager loading mechanism used by ActiveRecord, but it is a different story, not related to loading modules and classes.