A Brief Introduction to Rails Initializers: Why, What, and How

A Brief Introduction to Rails Initializers: Why, What, and How

October 19, 2024

At first glance, Rails initializers seem complex, but they're solving a simple, but important problem: run some code after framework and gems are loaded, to initialize the application. This post covers the basics of initializers, including what they are, how they work, and how Rails implements them.

Initializers are an important concept in Rails, but I couldn't find much information about them online, other than the official Rails guides. Recently, 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.

Rails Initialization Sequence

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 this article: Hide Sensitive Information from Logs using Parameter Filtering

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.

# config/initializers/delayed_job_config.rb

Delayed::Worker.destroy_failed_jobs = false
Delayed::Worker.sleep_delay = 60
Delayed::Worker.max_attempts = 3
Delayed::Worker.max_run_time = 5.minutes
Delayed::Worker.default_queue_name = 'default'
Delayed::Worker.delay_jobs = !Rails.env.test?

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:

  1. To set up one-time operations, like configuration of external gems
  2. Anything that needs to happen before your app starts running.
  3. 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 Does Rails Load the Initializers?

For the section that follows, I'd highly recommend that you open the Rails codebase, put a breakpoint, and follow the code path with me. To learn more about stepping through Rails, check out: How to Debug and Step-Through the Rails Codebase

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:

  1. The name for the initializer.
  2. 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.
  3. 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.

Rails Initialization Process

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 from Rails::Engine, which inherits from Rails::Railtie.
  • The Rails::Railtie class includes the Rails::Initializable module defined in the railites/lib/rails/initializable.rb file.
  • The Rails::Initializable module provides the run_initializers method, which is included in your application class.

Here's the inheritance hierarchy of these classes.

Rails Application Inheritance Hierarchy

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.

Sign up for my newsletter

Let's learn to become better developers.

Comments (4)

G
Gary leydon

Nicely done. Your articles are always well written a comprehensive. Thanks

A
Akshay Khot

Thank you, Gary! Really appreciated.

E
Ernest

Awesome article

A
Akshay Khot

Thanks!

Sign in to leave a comment.