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.