Rails Initialization Sequence

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

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.

7 min read

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:

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
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 the following article:
Hide Sensitive Information from Logs using Parameter Filtering
This article explains why you shouldn’t log confidential or user-identifiable information and how to filter it using parameter filtering in Rails. We’ll also learn how Rails implements this feature internally.

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?
DelayedJob Initializer

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 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:

  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
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.
The Definitive Guide to Rack for Rails Developers
This article explains pretty much everything you need to know about Rack as a Ruby and Rails developer. We will start with the basics and move to more advanced concepts like middleware and the Rack DSL.

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
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.

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.