Initializers are an important concept in Rails, but I couldn't find much information about them online, other than the official Rails guides. So on the weekend, I did a deep dive into the Rails initialization process, and what follows is everything I learned about initializers.
What we'll learn:
- What are Initializers?
- Why use an initializer?
- How Rails loads the initializers?
- How Rails executes an initializer?
By the end of the article, I hope you'll have a much better understanding of initializers and an appreciation of all the things that Rails does behind the scenes to not only make it work, but also make it look like magic!
Let's begin...
What are Initializers?
An initializer is simply a piece of Ruby code under the config/initializers
directory that you can use to configure your Rails application or external gems.
To create a new initializer, add a Ruby file in the config/initializers
directory. Rails will run the initializers after loading the framework and any other gems used in your application.
As an example, consider the filter_parameter_logging
initializer that Rails provides out-of-box. You can use it to hide sensitive data from the log file. This prevents sensitive data such as credit card numbers or auth keys from accidentally leaking into the log files.
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [ :password, :secret, :token ]
Note that it's a plain Ruby file, and you have access to the Rails application and configuration objects.
Configuring these values inside an initializer provides a single place where you can add/remove them and avoids having to mix this functionality into the application code.
P.S. To learn how parameter filtering works in Rails, check out the following article:
Why use an Initializer?
You can use initializers to configure your Rails application or external gems. Initializers allow you to provide configuration settings that should be made after all of the frameworks and gems are loaded, such as options to configure their settings.
Instead of writing the one-time initialization logic in your application code, use an initializer.
You can also use an initializer to set default values for your application and external gems. For example, here's the Delayed_Job
initializer setting a bunch of useful defaults.
I also asked about the different use-cases for Rails initializers on Reddit and received a bunch of great answers. Here're the main reasons people use initializers:
- To set up one-time operations, like configuration of external gems
- Anything that needs to happen before your app starts running.
- Add monkey-patching code to overwrite a library you‘re using.
Rails ensures that the initializer code is loaded only once on startup and before receiving the first request.
If you know other use cases for them, please let me know in the comments below.
How Rails Loads the Initializers?
At this point, you might be wondering: How does Rails picks up these Ruby files under config/initializers
directory, and runs them when the application starts?
It does seem like magic, doesn't it?
The answer is: Code that loads the initializer scripts is an initializer itself, defined by the framework.
The initializer files in config/initializers
(and any subdirectories of config/initializers
) are sorted and loaded one by one as part of the load_config_initializers
initializer.
# railties/lib/rails/engine.rb
module Rails
class Engine < Railtie
initializer :load_config_initializers do
config.paths["config/initializers"].existent.sort.each do |initializer|
load_config_initializer(initializer)
end
end
end
end
In fact, the above snippet shows the other way to create an initializer using the initializer
method. Let's take a look.
The initializer method
The initializer
method comes from the Initializable
module, which is included by Rails::Railtie
, the superclass of Rails::Engine
class.
This method simply stores the passed blocks into a collection of initializers. Here's a simplified implementation:
module Initializable
def self.initializer(name, opts = {}, &blk)
initializers << Initializer.new(name, &blk)
end
end
It takes three arguments:
- The name for the initializer.
- An options hash. The
:before
key in the options hash can be specified to specify which initializer this new initializer must run before, and the:after
key will specify which initializer to run this initializer after. - A block. It's a piece of code to execute when the initializer runs.
Initializers defined using the initializer
method will be run in the order they are defined in, with the exception of ones that use the :before
or :after
methods.
Out-of-box initializers
Rails provides several initializers out-of-box that run on startup that are all defined using the initializer
method. Here's an example of the assets_config
initializer from Action Controller.
module ActionController
class Railtie < Rails::Railtie
initializer "action_controller.assets_config", group: :all do |app|
app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
end
end
You can find a comprehensive list of all initializers in Rails on the official guides on configuring Rails applications.
But defining an initializer is only half of the process. How do all these initializers get executed? Who executes them?
Keep on reading...
Executing Initializers: Rails Initialization Process
So far, we've only seen how to create initializers. However, simply defining an initializer is not enough. The initialization code needs to run for it to do something useful.
This section answers the question: How Rails executes the initializers?
What follows is a detailed explanation of the Rails initialization process, where Rails takes all the initializers that are included out-of-box, or those that you've provided, and executes them sequentially.
Here's a high-level sequence diagram you can refer to while reading the rest of the article.
All Rails applications use the config.ru
file. This file is used by Rack-based servers, such as Puma, to launch the server and start the application.
To learn more about Rack and config.ru file, check out my detailed article on Rack.
Step 1: The config.ru
file requires the config/environment
file.
# config.ru
require_relative "config/environment"
Step 2: The environment file loads the Rails application and initializes it using the Rails.application.initialize!
method.
# config/environment.rb
require_relative "application"
Rails.application.initialize!
Step 3: The Rails.application
points to your application's instance, defined in the config/application.rb
file. The Application
class inherits from the Rails::Application
class, which provides the initialize!
method.
# config/application.rb
module Blog
class Application < Rails::Application
end
end
# railties/lib/rails/application.rb
module Rails
class Application < Engine
def initialize!(group = :default)
run_initializers(group, self)
end
end
end
The initialize!
method calls this run_initializers
method, as seen above. This is the method that grabs all the initializers and sequentially runs them.
However, you don't see the run_initializers
method in the application.rb
file. Where does it come from?
Answer:
- The
Rails::Application
class inherits fromRails::Engine
, which inherits fromRails::Railtie
. - The
Rails::Railtie
class includes theRails::Initializable
module defined in therailites/lib/rails/initializable.rb
file. - The
Rails::Initializable
module provides therun_initializers
method, which is included in your application class.
Here's the inheritance hierarchy of these classes.
Here's a simplified implementation of the run_initializers
method.
# railties/lib/rails/initializable.rb
def run_initializers(*args)
initializers.each do
initializer.run(*args)
end
end
In reality, it topologically sorts the initializers graph, based on the:before
and:after
options, and then runs them in order. But that's a topic for another blog post.
And that's how Rails executes the initializers to configure your application and other gems. Once all the initializers have run, your application is ready to handle incoming requests.
Summary
- An initializer is a piece of Ruby code under the
config/initializers
directory. You can use initializers to configure your Rails application or external gems. - Rails makes sure that the initializer code is loaded during the initialization process, after loading the framework and any other gems, and before receiving the first request.
- Instead of writing the one-time initialization logic in your application code, you should use an initializer.
That's a wrap. I hope you liked this article and you learned something new.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.