The Rails Initialization Process

This guide covers the startup of Rails. By referring to this guide, you will be able to:

  • Understand what Rails loads, and when

  • See how Rails sets up to dispatch incoming requests

  • Identify spots where you can customize or extend the initialization process

Note For best results, you should have a copy of the Rails source handy when reading this guide.

1. Where it All Begins

A good place to start thinking about the Rails initialization process is with the files in railties/dispatches. Depending on your server software stack and dispatch method, incoming requests will hit one or another of the files in this folder. (If you want to know the details of the process for your particular server, take a look in railties/lib/commands/servers). I'll assume that the request is coming in through the pure ruby interface, instead of through one of the CGI interfaces. In that case, it will hit dispatch.rb:

#!/usr/bin/env ruby

require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)

# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
require "dispatcher"

ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
Dispatcher.dispatch
Note This file, like some others in the initialization process, end up getting copied to your application when you execute the rails command to build a new application. In this guide, I'll point to them in their location in the Rails source tree.

So, at the very highest level, there are three things going on here:

  • Pull in the code in your environment.rb file, unless this has already been done

  • Pull in the code in the dispatcher module

  • Possibly set up some additional load paths

  • Call Dispatcher.dispatch to handle the request

2. Processing environment.rb

You've probably looked at your application's config/environment.rb many times in the course of configuring your Rails applications. But now, it's time to look at it again in the context of initialization.

Note I'll be looking at a stock, unmodified environment.rb in this guide.

Stripped down to its essentials, and with no user-specified configuration, here's what you'll find in environment.rb:

# ...
RAILS_GEM_VERSION = '2.1.1' unless defined? RAILS_GEM_VERSION
# ...
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  # ...
  config.time_zone = 'UTC'
  # ...
  config.action_controller.session = {
    :session_key => '_myapp_session',
    :secret      => 'secret'
  }
  # ...
end

The first step here is to specify the version of the Rails gem that you want to be using with this application. As you'll see in a bit, this is ignored if you're running vendored Rails. Then this file pulls in your boot.rb file. After that, Rails calls Initializer#run. Time to dig one level deeper.

2.1. Processing boot.rb

In boot.rb - which Rails copies unchanged to your application's config folder from railties/environments - things start to happen. Here's an outline of the action:

# ...
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)

module Rails
  # ...
end

Rails.boot!

This file first sets up the RAILS_ROOT constant to point to its own parent folder (which will be the root of your application). It then defines Rails and calls its boot! method. The rest of the details are located within the Rails module, still in the same file. The boot! method begins with a check to prevent re-entrancy, gives you a chance to do some very early initialization, and runs Rails:

def boot!
  unless booted?
    preinitialize
    pick_boot.run
  end
end

The Rails.preinitialize method is the first chance that you have to run custom code. It loads the file config/preinitializer.rb in your application:

def preinitialize
  load(preinitializer_path) if File.exist?(preinitializer_path)
end

def preinitializer_path
  "#{RAILS_ROOT}/config/preinitializer.rb"
end

By default, this file does not exist; if you want to use it (perhaps to spin up some prerequisite of your application functionality) you'll have to define it yourself.

Note Remember, at this point, none of Rails itself is loaded. If you want to do something that requires the framework, you'll need to wait until a later point in the process.

The pick_boot method decides whether to use vendored Rails or gem Rails, and then kicks it off:

def pick_boot
  (vendor_rails? ? VendorBoot : GemBoot).new
end

def vendor_rails?
  File.exist?("#{RAILS_ROOT}/vendor/rails")
end
Note The decision on whether to run vendored Rails is made purely on the basis of whether the folder containing Rails is present in your application.

Both VendorBoot and GemBoot inherit from Rails::Boot, which is where the run method called by boot! is actually defined:

class Boot
  def run
    load_initializer
    Rails::Initializer.run(:set_load_path)
  end
end

In either case (vendored or gem Rails), the load_initializer bootstraps in some more code. If you're dealing with gem Rails, it loads the Rails gem (there's a bunch of code related to version checking both ruby gems and Rails involved, which I'm not going to wade through):

class GemBoot < Boot
  def load_initializer
    self.class.load_rubygems
    load_rails_gem
    require 'initializer'
  end
  # ...
end

If you're going down the vendored Rails path, load_initializer is a bit different:

def load_initializer
  require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
  Rails::Initializer.run(:install_gem_spec_stubs)
end

Rails::Initializer is defined in railties/lib/initializer.rb, and you'll see a great deal of it as this walkthrough progresses. Requiring this file drags in a great deal of other code:

require 'logger'
require 'set'
require 'pathname'

$LOAD_PATH.unshift File.dirname(__FILE__)
require 'railties_path'
require 'rails/version'
require 'rails/plugin/locator'
require 'rails/plugin/loader'
require 'rails/gem_dependency'
require 'rails/rack'

RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)

So, before I dig into the first call to a method of Initializer, it's necessary to sidetrack for a look at the contents of these libraries. The logger, set, and pathname utilites are the parts of the Ruby Standard Library that Rails absolutely needs. The rest of these dependencies come out of Rails' own libraries. I'll proceed as if you're running vendored Rails, though of course the same libraries are also in gem Rails.

2.1.1. Processing railties_path

The railties_path library is simple: all it does is set up a single constant for use later in the process.

RAILTIES_PATH = File.join(File.dirname(__FILE__), '..')

In your application, this will point to vendor/rails/railties.

2.2. Processing rails/version

This is another easy one. rails/version is where the version number of Rails is set:

module Rails
  module VERSION #:nodoc:
    MAJOR = 2
    MINOR = 2
    TINY  = 0

    STRING = [MAJOR, MINOR, TINY].join('.')
  end
end
Tip If you're running edge Rails, the Rails version string is going to return the version of the next release. That's one of the prices you pay for being on edge.

2.2.1. Processing rails/plugin/locator

Requiring rails/plugin/locator brings in the Rails::Plugin::Locator class and its subclasses. You'll see how these are used shortly - but just requiring this file doesn't run any code.

2.3. Processing rails/plugin/loader

The rails/plugin/loader library starts with a requirement of its own:

require "rails/plugin"

This makes Rails::Plugin available. rails/plugin/loader itself sets up Rails::Plugin::Loader. At this point, Rails has all the machinery necessary to load plugins - but it hasn't loaded any of them yet.

2.3.1. Processing rails/gem_dependency

Requiring rails/gem_dependency loads up Rails::GemDependency. Again, there's no initialization code in this file.

2.3.2. Processing rails/rack

The rails/rack library uses autoload to lazy-load a couple more classes for use down the line:

module Rails
  module Rack
    autoload :Logger, "rails/rack/logger"
    autoload :Static, "rails/rack/static"
  end
end

2.4. Continuing the Boot Process

It's time to take stock. The reason for looking at all of that code was that Rails was in the process of loading boot.rb. Specifically, the next step for vendored Rails is to call Rails::Initializer.run(:install_gem_spec_stubs):

def install_gem_spec_stubs
  unless Rails.respond_to?(:vendor_rails?)
    abort %{Your config/boot.rb is outdated: Run "rake rails:update".}
  end

  if Rails.vendor_rails?
    begin; require "rubygems"; rescue LoadError; return; end

    stubs = %w(rails activesupport activerecord actionpack actionmailer activeresource)
    stubs.reject! { |s| Gem.loaded_specs.key?(s) }

    stubs.each do |stub|
      Gem.loaded_specs[stub] = Gem::Specification.new do |s|
        s.name = stub
        s.version = Rails::VERSION::STRING
      end
    end
  end
end

This is part of the setup that vendored Rails does to allow plugin initialization to proceed smoothly. The idea is to allow plugins to depend on the various gems that make up Rails, even when the gem version of Rails isn't being loaded. So, after checking to make sure that none of the actual gems are already loaded, Rails manufactures fake gem specs that do nothing more than provide the name and version to fool any affected plugins into thinking that what they're looking for is already present.

2.5. Finishing environment processing with Initializer.run method

OK, great - the Rails boot process is finished. What next? If you unwind your mental stack, you'll see that the next step is to process the Initializer.run method in +environment.rb:

Rails::Initializer.run do |config|
  # ...
  config.time_zone = 'UTC'
  # ...
  config.action_controller.session = {
    :session_key => '_myapp_session',
    :secret      => 'secret'
  }
  # ...
end

Remember, at this point the various files required by Initializer have already been brought in as part of the boot process. So this code proceeds straight into the run method:

def self.run(command = :process, configuration = Configuration.new)
  yield configuration if block_given?
  initializer = new configuration
  initializer.send(command)
  initializer
end

Rails first spins up a default configuration using Configuration.new and then uses the yield to allow any configuration arguments specified in your environment.rb to overwrite the default values. It then sets up a new Initializer instance to reference the altered configuration:

def initialize(configuration)
  @configuration = configuration
  @loaded_plugins = []
end

2.6. Initializing the Configuration object

The Configuration class has a fairly fat initializer:

def initialize
  set_root_path!

  self.frameworks                   = default_frameworks
  self.load_paths                   = default_load_paths
  self.load_once_paths              = default_load_once_paths
  self.eager_load_paths             = default_eager_load_paths
  self.log_path                     = default_log_path
  self.log_level                    = default_log_level
  self.view_path                    = default_view_path
  self.controller_paths             = default_controller_paths
  self.cache_classes                = default_cache_classes
  self.dependency_loading           = default_dependency_loading
  self.whiny_nils                   = default_whiny_nils
  self.plugins                      = default_plugins
  self.plugin_paths                 = default_plugin_paths
  self.plugin_locators              = default_plugin_locators
  self.plugin_loader                = default_plugin_loader
  self.database_configuration_file  = default_database_configuration_file
  self.routes_configuration_file    = default_routes_configuration_file
  self.gems                         = default_gems

  for framework in default_frameworks
    self.send("#{framework}=", Rails::OrderedOptions.new)
  end
  self.active_support = Rails::OrderedOptions.new
end

The first step here is to set the @root_path instance variable to the root of your Rails application:

def set_root_path!
  raise 'RAILS_ROOT is not set' unless defined?(::RAILS_ROOT)
  raise 'RAILS_ROOT is not a directory' unless File.directory?(::RAILS_ROOT)

  @root_path =
    # Pathname is incompatible with Windows, but Windows doesn't have
    # real symlinks so File.expand_path is safe.
    if RUBY_PLATFORM =~ /(:?mswin|mingw)/
      File.expand_path(::RAILS_ROOT)

    # Otherwise use Pathname#realpath which respects symlinks.
    else
      Pathname.new(::RAILS_ROOT).realpath.to_s
    end

  Object.const_set(:RELATIVE_RAILS_ROOT, ::RAILS_ROOT.dup) unless defined?(::RELATIVE_RAILS_ROOT)
  ::RAILS_ROOT.replace @root_path
end

After that, the initialize method calls various methods within the class to set up default class properties. I'll go into these later as they become pertinent to the rest of the initialization process. One that is immediately important, though, is default_frameworks. Unless you override this in your application's configuration file, this returns the full set of frameworks that make up Rails:

def default_frameworks
  [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ]
end

For each of the frameworks (as well as for active support, which you have no option to leave out), the Configuration object initializes an empty Rails::OrderedOptions object - an array that can hold configuration options.

TODO: Revisit that step in more detail

2.7. The Initializer#process Method

Now that there is a configured Initializer, whatever command was passed in to the run method gets executed by that initializer. By default, this is the process command, which steps through all of the available initialization routines in a pre-set order:

def process
  Rails.configuration = configuration

  check_ruby_version
  install_gem_spec_stubs
  set_load_path
  add_gem_load_paths

  require_frameworks
  set_autoload_paths
  add_plugin_load_paths
  load_environment

  initialize_encoding
  initialize_database

  initialize_cache
  initialize_framework_caches

  initialize_logger
  initialize_framework_logging

  initialize_dependency_mechanism
  initialize_whiny_nils
  initialize_temporary_session_directory
  initialize_time_zone
  initialize_framework_settings
  initialize_framework_views

  add_support_load_paths

  load_gems
  load_plugins

  # pick up any gems that plugins depend on
  add_gem_load_paths
  load_gems
  check_gem_dependencies

  load_application_initializers

  # the framework is now fully initialized
  after_initialize

  # Prepare dispatcher callbacks and run 'prepare' callbacks
  prepare_dispatcher

  # Routing must be initialized after plugins to allow the former to extend the routes
  initialize_routing

  # Observers are loaded after plugins in case Observers or observed models are modified by plugins.
  load_observers

  # Load view path cache
  load_view_paths

  # Load application classes
  load_application_classes

  # Disable dependency loading during request cycle
  disable_dependency_loading

  # Flag initialized
  Rails.initialized = true
end

Whew! What a mouthful. Time to look at each of those initialization steps in order.

2.7.1. The check_ruby_version method

The check_ruby_version method makes sure that the application is trying to run on an acceptable version of ruby:

def check_ruby_version
  require 'ruby_version_check'
end

The check is implemented in an external file, railties/lib/ruby_version_check.rb, so that it's available to the rails application generator as well as the initialization code. At the moment, it accepts ruby 1.8.2 or anything from 1.8.4 to current.

2.7.2. The install_gem_spec_stubs method

You've already seen install_gem_spec_stubs above; it's called as part of the boot.rb processing.

2.7.3. The set_load_path method

The set_load_path method sets the global $LOAD_PATH variable based on two different configuration options. The method strips away any duplication between the two arrays:

def set_load_path
  load_paths = configuration.load_paths + configuration.framework_paths
  load_paths.reverse_each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
  $LOAD_PATH.uniq!
end

The Configuration object delivers load_paths from its default_load_paths method:

def default_load_paths
  paths = []

  # Add the old mock paths only if the directories exists
  paths.concat(Dir["#{root_path}/test/mocks/#{environment}"]) if File.exists?("#{root_path}/test/mocks/#{environment}")

  # Add the app's controller directory
  paths.concat(Dir["#{root_path}/app/controllers/"])

  # Then components subdirectories.
  paths.concat(Dir["#{root_path}/components/[_a-z]*"])

  # Followed by the standard includes.
  paths.concat %w(
    app
    app/models
    app/controllers
    app/helpers
    app/services
    components
    config
    lib
    vendor
  ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }

  paths.concat builtin_directories
end

Here, root_path is the application's base directory, which is set as part of the Configuration#initialize process. Rails puts together an array here that includes the locations of your controllers, any components subdirectories, and standard include paths. If you're running in the development environment, it also pulls in the internal Rails builtin folder:

def builtin_directories
  # Include builtins only in the development environment.
  (environment == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : []
end
Tip The builtin folder is where the Rails info controller lives.

The Configuration object delivers framework_paths from its framework_paths method:

def framework_paths
  paths = %w(railties railties/lib activesupport/lib)
  paths << 'actionpack/lib' if frameworks.include? :action_controller or frameworks.include? :action_view

  [:active_record, :action_mailer, :active_resource, :action_web_service].each do |framework|
    paths << "#{framework.to_s.gsub('_', '')}/lib" if frameworks.include? framework
  end

  paths.map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
end

This gets you the lib folders from all of the loaded frameworks as part of your loadpath.

2.7.4. The add_gem_load_paths method

Rails still isn't done finding places that it might have to load code from in the future. There still remains the possibility that you have some plugins loading as gems. The add_gem_load_paths method accounts for this possibility:

def add_gem_load_paths
  Rails::GemDependency.add_frozen_gem_path
  unless @configuration.gems.empty?
    require "rubygems"
    @configuration.gems.each { |gem| gem.add_load_paths }
  end
end

The call to add_frozen_gem_path makes sure that Rails will look in vendor/gems for gems before it goes hunting across the system. By default, the configuration does not load any gems, so this code will do nothing unless you specify some gems in your application's configuration file.

2.7.5. The require_frameworks method

At this point, Rails knows where to find all of its own frameworks and gems (though note that it hasn't dug into plugin loading yet), so it's safe to require the ones that you've said you will actually use:

def require_frameworks
  configuration.frameworks.each { |framework| require(framework.to_s) }
rescue LoadError => e
  # re-raise because Mongrel would swallow it
  raise e.to_s
end

2.7.6. The set_autoload_paths method

The next step is to set the paths from which Rails will automatically load files. These are further split up into load paths and "load once" paths:

def set_autoload_paths
  ActiveSupport::Dependencies.load_paths = configuration.load_paths.uniq
  ActiveSupport::Dependencies.load_once_paths = configuration.load_once_paths.uniq

  extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths
  unless extra.empty?
    abort <<-end_error
      load_once_paths must be a subset of the load_paths.
      Extra items in load_once_paths: #{extra * ','}
    end_error
  end

  # Freeze the arrays so future modifications will fail rather than do nothing mysteriously
  configuration.load_once_paths.freeze
end

You've already seen that Configuration#load_paths gives you all of the places in your application where code can reside (but not the framework paths). The +Configuration#load_once_paths method returns an empty array by default:

def default_load_once_paths
  []
end

2.7.7. The add_plugin_load_paths method

Yes, there are still more paths for Rails to deal with. Now it's time to bring in the paths for plugins:

def add_plugin_load_paths
  plugin_loader.add_plugin_load_paths
end
Tip The plugin load paths are defined after the application's load paths. This makes it possible for your application to override any code within a plugin by adding your own files to your application's lib folder.

The plugin loader is lazy-initialized from the active Configuration object:

def plugin_loader
  @plugin_loader ||= configuration.plugin_loader.new(self)
end

Within the Configuration class, this in turn is just a reference to the Plugin::Loader class:

def default_plugin_loader
  Plugin::Loader
end

You'll see much more of the plugin loader as the initialization process continues; it has the primary responsibility for loading only the plugins you specify in your application's configuration, and bringing them in in the right order.

Note Plugins are not actually loaded at this point; that happens considerably later in the initialization process.

The plugin loader picks up the paths from each loaded plugin:

def add_plugin_load_paths
  plugins.each do |plugin|
    plugin.load_paths.each do |path|
      $LOAD_PATH.insert(application_lib_index + 1, path)
      ActiveSupport::Dependencies.load_paths      << path
      unless Rails.configuration.reload_plugins?
        ActiveSupport::Dependencies.load_once_paths << path
      end
    end
  end
  $LOAD_PATH.uniq!
end

Inside the plugin loader, the plugins collection is initialized to an array of all plugins to be loaded, sorted by load order:

def plugins
  @plugins ||= all_plugins.select { |plugin| should_load?(plugin) }.sort { |p1, p2| order_plugins(p1, p2) }
end

TODO: More detail on plugin loader

2.7.8. The load_environment method

So far, all of the initialization has been the same whether you're running in product, development, or test. The load_environment method is where Rails finally picks up the configuration file for the current environment from your application's config/environment folder and processes it:

def load_environment
  silence_warnings do
    return if @environment_loaded
    @environment_loaded = true

    config = configuration
    constants = self.class.constants

    eval(IO.read(configuration.environment_path), binding, configuration.environment_path)

    (self.class.constants - constants).each do |const|
      Object.const_set(const, self.class.const_get(const))
    end
  end
end

TODO: Perhaps some more detail on what's going on here.

Rails determines which environment to process by looking at environment_path:

def environment_path
  "#{root_path}/config/environments/#{environment}.rb"
end

And that in turn gets its information from the environment method:

def environment
  ::RAILS_ENV
end

And RAILS_ENV itself is set up to read from the environment of the Rails process, with a default to development:

RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)

2.7.9. The initialize_encoding method

The initialize_encoding method is one of the few places in Rails where you'll find an explicit dependency on the version of ruby that's running:

def initialize_encoding
  $KCODE='u' if RUBY_VERSION < '1.9'
end

For ruby 1.8, this sets a constant that will enable multibyte-safe operations elsewhere in Rails. For ruby 1.9, Rails doesn't have to worry about doing anything special.

2.7.10. The initialize_database method

The initialize_database method will actually get in touch with your application's configured database for the first time:

def initialize_database
  if configuration.frameworks.include?(:active_record)
    ActiveRecord::Base.configurations = configuration.database_configuration
    ActiveRecord::Base.establish_connection
  end
end

There's no point in doing this if your application isn't using Active Record, of course. If it is, this method configures ActiveRecord::Base from the current database configuration file. This defaults to config/database.yml, though that's a property of the Configuration object so you could override it if you wanted to:

def default_database_configuration_file
  File.join(root_path, 'config', 'database.yml')
end

The Configuration#database_configuration method processes this file through ERB first, and then through YAML:

def database_configuration
  require 'erb'
  YAML::load(ERB.new(IO.read(database_configuration_file)).result)
end

ActiveRecord::Base.configurations is defined in /activerecord/lib/activerecord/base as a simple class accessor which stores the processed hash of options out of database.yml. This is used by establish_connection to set up the initial connection pool with the database.

2.7.11. The initialize_cache method

The initialize_cache method brings up Active Support's cache and sets up the RAILS_CACHE constant, unless this has already been done:

def initialize_cache
  unless defined?(RAILS_CACHE)
    silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(configuration.cache_store) }
  end
end

2.7.12. The initialize_framework_caches method

Despite its name, initialize_framework_caches currently only initializes one cache, by passing RAILS_CACHE over to Action Controller:

def initialize_framework_caches
  if configuration.frameworks.include?(:action_controller)
    ActionController::Base.cache_store ||= RAILS_CACHE
  end
end

2.7.13. The initialize_logger method

The initialize_logger method brings Rails' logging online:

def initialize_logger
  # if the environment has explicitly defined a logger, use it
  return if Rails.logger

  unless logger = configuration.logger
    begin
      logger = ActiveSupport::BufferedLogger.new(configuration.log_path)
      logger.level = ActiveSupport::BufferedLogger.const_get(configuration.log_level.to_s.upcase)
      if configuration.environment == "production"
        logger.auto_flushing = false
      end
    rescue StandardError => e
      logger = ActiveSupport::BufferedLogger.new(STDERR)
      logger.level = ActiveSupport::BufferedLogger::WARN
      logger.warn(
        "Rails Error: Unable to access log file. Please ensure that #{configuration.log_path} exists and is chmod 0666. " +
        "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
      )
    end
  end

  silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
end

By default, this method uses ActiveSupport::BufferedLogger, which stores up data in memory for an adjustable length of time before flushing it out to the hard drive. You can override this by explicitly defining a logger class in your configuration file. Note that if Rails is unable to create the actual log for any reason, it cranks up the logging level to WARN and starts dumping log output to the console to get your attention.

2.7.14. The initialize_framework_logging method

Now that Rails has its own default logger, it passes this logger down to the various frameworks. This method is designed to be non-destructive: that is, if a framework already has its own logger initialized, then the master logger will not overwrite that.

def initialize_framework_logging
  for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
    framework.to_s.camelize.constantize.const_get("Base").logger ||= Rails.logger
  end

  ActiveSupport::Dependencies.logger ||= Rails.logger
  Rails.cache.logger ||= Rails.logger
end

2.7.15. The initialize_dependency_mechanism method

As you know, Rails caches things differently depending on whether it is in production or development mode: in development mode, your classes get reloaded on each request. The initialize_dependency_mechanism method looks at the value you've specified for cache_classes and tells ActiveSupport::Dependencies about it:

def initialize_dependency_mechanism
  ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load
end
Tip Class reloading is a complex subject. For an excellent introduction, see Frederick Cheung's Required or Not?.

TODO: Might be worth digging a bit deeper here

2.7.16. The initialize_whiny_nils method

"Whiny nils" is the Rails term for putting warnings into the log whenever a method is invoked on a nil value, with (hopefully) helpful information about which sort of object you might have been trying to use. This is handled by bringing in a special chunk of code if it's called for:

def initialize_whiny_nils
  require('active_support/whiny_nil') if configuration.whiny_nils
end

2.7.17. The initialize_temporary_session_directory method

Next up is getting ready to store session information:

def initialize_temporary_session_directory
  if configuration.frameworks.include?(:action_controller)
    session_path = "#{configuration.root_path}/tmp/sessions/"
    ActionController::Base.session_options[:tmpdir] = File.exist?(session_path) ? session_path : Dir::tmpdir
  end
end

If Rails can't find your configured session path for some reason, it uses Dir::tmpdir to store sessions rather than raising an error.

2.7.18. The initialize_time_zone method

The initialize_time_zone method sets the default timezone for the Time class. It also sets up ActiveRecord to be timezone-aware.

def initialize_time_zone
  if configuration.time_zone
    zone_default = Time.__send__(:get_zone, configuration.time_zone)
    unless zone_default
      raise %{Value assigned to config.time_zone not recognized. Run "rake -D time" for a list of tasks for finding appropriate time zone names.}
    end
    Time.zone_default = zone_default
    if configuration.frameworks.include?(:active_record)
      ActiveRecord::Base.time_zone_aware_attributes = true
      ActiveRecord::Base.default_timezone = :utc
    end
  end
end

If there is no valid timezone information in the configuration, this method will raise an error. However, the Configuration class does not include a default timezone. This is why Rails automatically generates a config.timezone setting in environment.rb:

Rails::Initializer.run do |config|
  # ...
  config.time_zone = 'UTC'
  # ...
end

2.7.19. The initialize_framework_settings method

At this point, the prerequisites are in place to start bringing the actual Rails frameworks to life. The initialize_framework_settings method is responsible for configuring each of the loaded frameworks:

def initialize_framework_settings
  configuration.frameworks.each do |framework|
    base_class = framework.to_s.camelize.constantize.const_get("Base")

    configuration.send(framework).each do |setting, value|
      base_class.send("#{setting}=", value)
    end
  end
  configuration.active_support.each do |setting, value|
    ActiveSupport.send("#{setting}=", value)
  end
end

The Configuration class includes accessors corresponding to each of the frameworks:

# A stub for setting options on ActionController::Base.
attr_accessor :action_controller

# A stub for setting options on ActionMailer::Base.
attr_accessor :action_mailer

# A stub for setting options on ActionView::Base.
attr_accessor :action_view

# A stub for setting options on ActiveRecord::Base.
attr_accessor :active_record

# A stub for setting options on ActiveResource::Base.
attr_accessor :active_resource

# A stub for setting options on ActiveSupport.
attr_accessor :active_support

So, when the initialize_framework_settings method runs, it takes, for example, all of the options from Configuration#active_record and passes them to ActiveRecord#Base. This is what makes it possible to handle settings like config.active_record.schema_format = :sql in your environment.rb or other configuration files.

TODO: It would be really nice to have a good list of framework configuration options

2.7.20. The initialize_framework_views method

The initialize_framework_views method sets the path to the application's views for the two frameworks that might need it:

def initialize_framework_views
  if configuration.frameworks.include?(:action_view)
    view_path = ActionView::PathSet::Path.new(configuration.view_path, false)
    ActionMailer::Base.template_root ||= view_path if configuration.frameworks.include?(:action_mailer)
    ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller) && ActionController::Base.view_paths.empty?
  end
end

By default, the view_path is set up to point to your app/views folder, but you could override this in your configuration file:

def default_view_path
  File.join(root_path, 'app', 'views')
end

Initializing a Path object from this creates a frozen copy. Here's Path#initialize:

def initialize(path, load = true)
  raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
  @path = path.freeze
  reload! if load
end

Setting up the view paths actually causes some processing. Here's what happens in ActionController::Base:

def view_paths=(value)
  @template.view_paths = ActionView::Base.process_view_paths(value)
end

If you look into process_view_paths, you'll see that it creates a new instance of ActionView::PathSet:

def self.process_view_paths(value)
  ActionView::PathSet.new(Array(value))
end

So, by default, Rails creates a new PathSet object and passes it an array containing a single Path object that points to your application's app/views folder.

TODO: Figure out the PathSet code

2.7.21. The add_support_load_paths method

Here's the default definition of the add_support_load_paths method:

def add_support_load_paths
end

This appears to be a do-nothing method.

TODO: What's up with this? I don't see that it's ever overridden either.

2.7.22. The load_gems method

The load_gems method calls load on each ruby gem known to the configuration:

def load_gems
  @configuration.gems.each { |gem| gem.load }
end

2.7.23. The load_plugins method

The load_plugins method loads your application's plugins by calling the load_plugins method of the plugin loader, which was itself initialized earlier in the add_plugin_load_paths method:

def load_plugins
  plugin_loader.load_plugins
end

Within the Plugin::Loader class, the load_plugins method iterates across all of the plugins for your application:

def load_plugins
  plugins.each do |plugin|
    plugin.load(initializer)
    register_plugin_as_loaded(plugin)
  end
  ensure_all_registered_plugins_are_loaded!
end

The Plugin::load method is the one that actually evaluates the init.rb file within each plugin. After this, the plugin instance is added to the Initializer#loaded_plugins collection. When that's finished, there's an explicit sanity check to make sure that at least all of the plugins listed in your configuration files are loaded:

def ensure_all_registered_plugins_are_loaded!
  if explicit_plugin_loading_order?
    if configuration.plugins.detect {|plugin| plugin != :all && !loaded?(plugin) }
      missing_plugins = configuration.plugins - (plugins + [:all])
      raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence}"
    end
  end
end

TODO: More details on the Plugin class. Though actually that might be another guide.

2.7.24. The add_gem_load_paths method

Rails isn't quite done dealing with gems yet. The next step is to add the paths to any frozen gems, via add_gem_load_paths:

def add_gem_load_paths
  unless @configuration.gems.empty?
    require "rubygems"
    @configuration.gems.each { |gem| gem.add_load_paths }
  end
end

2.7.25. The load_gems method

Now Rails calls load_gems for the second time. Why twice? The first time loads up any gems that can be easily found that might be needed by plugins. Then plugins are initialized, and then Rails builds up additional path information for gems. After this, it can make a final pass to load up the rest of the configured gems.

2.7.26. The check_gem_dependencies method

After two tries at loading gems, there's a sanity check to make sure that everything you claimed to depend on is actually present:

    def check_gem_dependencies
      unloaded_gems = @configuration.gems.reject { |g| g.loaded? }
      if unloaded_gems.size > 0
        @gems_dependencies_loaded = false
        # don't print if the gems rake tasks are being run
        unless $rails_gem_installer
          abort <<-end_error
Missing these required gems:
  #{unloaded_gems.map { |gem| "#{gem.name}  #{gem.requirement}" } * "\n  "}

You're running:
  ruby #{Gem.ruby_version} at #{Gem.ruby}
  rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}

Run `rake gems:install` to install the missing gems.
          end_error
        end
      else
        @gems_dependencies_loaded = true
      end
    end

TODO: Need a fair amount more detail on gem loading, preferably after that code settles down.

2.7.27. The load_application_initializers method

With gems and plugins available, Rails can finally start running some of the code in your own application. First up are any files you've dropped in the config/initializers folder:

def load_application_initializers
  if gems_dependencies_loaded
    Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer|
      load(initializer)
    end
  end
end
Note You can use subfolders to organize your initializers if you like, because Rails will look into the whole hierarchy.
Tip If you have any ordering dependency in your initializers, you can control the load order by naming. 01_critical.rb will be run before 02_normal.rb.

2.7.28. The after_initialize method

You can supply your own after_initialize block by setting config.after_initialize in your configuration files. Indeed, you can even supply an array of such blocks if you like. This is the point in the process where Rails will run your after_initialize code.

def after_initialize
  if gems_dependencies_loaded
    configuration.after_initialize_blocks.each do |block|
      block.call
    end
  end
end

Some pieces, notably observers and routing, have not yet been set up by Rails at the point where this block is called.

2.7.29. The prepare_dispatcher method

The Rails dispatcher is the piece that handles sending incoming HTTP requests to their proper controller for processing. Setting it up takes a few lines of code:

def prepare_dispatcher
  return unless configuration.frameworks.include?(:action_controller)
  require 'dispatcher' unless defined?(::Dispatcher)
  Dispatcher.define_dispatcher_callbacks(configuration.cache_classes)
  Dispatcher.new(Rails.logger).send :run_callbacks, :prepare_dispatch
end

There's no point in setting up the dispatcher if you're not loading Action Controller. If you are, then a require statement brings in the code in railties/lib/dispatcher.rb. This is just a wrapper that brings up the application's instance of ActionController::Dispatcher:

require 'action_controller/dispatcher'
Dispatcher = ActionController::Dispatcher

The dispatcher deserves a guide of its own (and perhaps I'll write on eventually), but for now, I just want to mention its participation in the initialization process. The define_dispatcher_callbacks method is the one that sets up the "reload classes on every request" behavior that Rails exhibits in development mode:

def define_dispatcher_callbacks(cache_classes)
  unless cache_classes
    # Development mode callbacks
    before_dispatch :reload_application
    after_dispatch :cleanup_application
  end

TODO: to_prepare comes into this somehow (setting up for the :prepare_dispatch) but I don't immediately see how.

2.7.30. The initialize_routing method

The Dispatcher needs the information from your routes.rb file to do its work, but this information needs to be processed from the DSL you write in that file to an internal data structure first. That's the job of initialize_routing:

def initialize_routing
  return unless configuration.frameworks.include?(:action_controller)
  ActionController::Routing.controller_paths = configuration.controller_paths
  ActionController::Routing::Routes.configuration_file = configuration.routes_configuration_file
  ActionController::Routing::Routes.reload
end

Again, there's no point in running this code if you don't have Action Controller loaded in your application.

Note For full details on the routing process, see Rails Routing from the Inside Out.

2.7.31. The load_observers method

Rails next instantiates any observer classes you've declared:

def load_observers
  if gems_dependencies_loaded && configuration.frameworks.include?(:active_record)
    ActiveRecord::Base.instantiate_observers
  end
end

TODO: Maybe worth looking at observer details

2.7.32. The load_view_paths method

Next, Rails figures out how to find its views:

def load_view_paths
  if configuration.frameworks.include?(:action_view)
    ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes
    ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller)
    ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer)
  end
end

The eager_load_templates flag tells the ActionView::Path class to freeze each template as it is loaded; this is turned on in the same circumstances as Rails' model cache (i.e., in production by default). After setting this flag, Rails loads templates for controllers and for mailers (assuming that those frameworks are in use). The applicable paths were set earlier in the initialization process by the initialize_framework_views method.

Loading templates amounts to freezing cached copies in memory after eager loading any methods:

def load
  reload! unless loaded?
  self
end

# Rebuild load path directory cache
def reload!
  @paths = {}

  templates_in_path do |template|
    # Eager load memoized methods and freeze cached template
    template.freeze if self.class.eager_load_templates?

    @paths[template.path] = template
    @paths[template.path_without_extension] ||= template
  end

  @paths.freeze
  @loaded = true
end

TODO: Probably need more detail

2.7.33. The load_application_classes method

The load_application_classes method is the point where Rails brings in all the classes that it keeps loaded for the duration of your application, if you're in production mode:

def load_application_classes
  if configuration.cache_classes
    configuration.eager_load_paths.each do |load_path|
      matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/
      Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
        require_dependency file.sub(matcher, '\1')
      end
    end
  end
end

The default configuration puts models, helpers, and controllers into the eager load paths:

def default_eager_load_paths
  %w(
    app/models
    app/controllers
    app/helpers
  ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
end

So, Rails ends up calling require_dependency on each ruby file found in these folders (and their sub-folders). This method is defined in activesupport/lib/active_support/dependencies.rb:

def require_dependency(file_name)
  Dependencies.depend_on(file_name)
end

The depend_on method calls require_or_load:

def depend_on(file_name, swallow_load_errors = false)
  path = search_for_file(file_name)
  require_or_load(path || file_name)
rescue LoadError
  raise unless swallow_load_errors
end

The require_or_load method goes through a fair bit of gyration, but the end result (in production) is to use require to pull in the specified file.

2.7.34. The disable_dependency_loading method

The final step of Initializer#process is to turn off automatic dependency loading:

def disable_dependency_loading
  if configuration.cache_classes && !configuration.dependency_loading
    ActiveSupport::Dependencies.unhook!
  end
end

3. Processing dispatcher

At this point, Rails has worked its way through environment.rb and boot.rb and completed it laundry list of initialization actions. There are only a few remaining steps on the way to being ready to dispatch requests. If you recall, I started off this discussion by looking at dispatch.rb, where the next line of code is:

require "dispatcher"

This brings in railties/lib/dispatcher.rb, which does nothing more than call up the dispatcher from Action Controller and create a constant for this class that will be used throughout your application:

require 'action_controller/dispatcher'
Dispatcher = ActionController::Dispatcher

4. Setting up Additional Load Paths

Next up in dispatch.rb is:

ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)

This is fossil code. It only executes if Apache::RubyRun is defined, and that is defined by mod_ruby. You're not running mod_ruby, so don't worry about it.

5. Dispatching the Request

At last, time to actually dispatch a request. Here's the starting point for the code, as defined in dispatch.rb:

Dispatcher.dispatch

The dispatch method takes three arguments, all of which have defaults:

def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
  new(output).dispatch_cgi(cgi, session_options)
end

As you can see, this falls through to dispatch_cgi, which builds up new request and response objects:

def dispatch_cgi(cgi, session_options)
  if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
    @request = CgiRequest.new(cgi, session_options)
    @response = CgiResponse.new(cgi)
    dispatch
  end
rescue Exception => exception
  failsafe_rescue exception
end

TODO: Figure out how this rabbit gets into this hat

With the request and response objects, it's off to the unadorned dispatch method:

def dispatch
  if ActionController::Base.allow_concurrency
    dispatch_unlocked
  else
    @@guard.synchronize do
      dispatch_unlocked
    end
  end
end

This dispatch method needs to worry some about thread safety. If you're running in threaded mode - as you will be if you specify config.threadsafe! in one of your environment files - then it falls straight through to dispatch_unlocked. If you're not in threaded mode, it may have to wait for another request to be processed before it gets to dispatch_unlocked:

def dispatch_unlocked
  begin
    run_callbacks :before_dispatch
    handle_request
  rescue Exception => exception
    failsafe_rescue exception
  ensure
    run_callbacks :after_dispatch, :enumerator => :reverse_each
  end
end

Here's the heart of handling a request: run the :before_dispatch callbacks, handle the request, then run the :after_dispatch callbacks.

5.1. :before_dispatch Callbacks

You saw dispatcher callbacks above in the discussion of the Initializer#prepare_dispatch method. By default, Rails sets up two callbacks in development mode, and none in production or test modes:

def define_dispatcher_callbacks(cache_classes)
  unless cache_classes
    # Development mode callbacks
    before_dispatch :reload_application
    after_dispatch :cleanup_application
  end

The Dispatcher#reload_application callback takes care of making sure that the latest changes to the application are loaded:

def reload_application
  # Run prepare callbacks before every request in development mode
  run_callbacks :prepare_dispatch

  Routing::Routes.reload
  ActionController::Base.view_paths.reload!
end

You've already seen all of those initializations discussed; all the reload_application method does is re-run selected parts of the initialization process.

5.2. Handling the Request

Handling the request is a two-step process:

def handle_request
  @controller = Routing::Routes.recognize(@request)
  @controller.process(@request, @response).out(@output)
end

The first step is to call the route recognizer to figure out which controller should deal with the request at hand.

The second step is to hand the actual request and response objects to that controller, and tell it to do its thing. Digging into that is a subject for another guide.

5.3. :after_dispatch Callbacks

In production mode (or any time you've set config.cache_classes = false), Rails finishes the dispatch process for a single request by calling the Dispatcher#cleanup_application method:

def cleanup_application
  ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
  ActiveSupport::Dependencies.clear
  ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
end

The point here is to clear the loaded classes back out of memory, so they can be reloaded on the next request without any conflicts.

TODO: One more level of detail on this?

6. The End

If you've followed along this far, congratulations! You now understand the basics of what Rails has to do before it can handle a simple request for +http://localhost:3000/blogs/1. More importantly, you should have a good idea of the various knobs that you can turn to customize the process, and their effects on the overall behavior of Rails.

TODO: Further doc of config.threadsafe!. Anything else that can be set in configs that isn't used in the main line?

7. Changelog

  • November 2, 2008: Picked up change to add_gem_load_paths from edge by Mike Gunderloy

  • October 12, 2008: First complete draft (still plenty of TODOs) by Mike Gunderloy

  • October 11, 2008: Finish coverage of Initializer#process by Mike Gunderloy

  • October 10, 2008: Added details on final initialization, observers, routing by Mike Gunderloy

  • October 9, 2008: Added details on framework initialization, plugin loading, gem loading by Mike Gunderloy

  • October 5, 2008: Added details on path initialization by Mike Gunderloy

  • October 4, 2008: First partial draft by Mike Gunderloy