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_dependenc