Ticker

6/recent/ticker-posts

How Rails server bootstrap

There are following steps involved in this,

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.

Post a Comment

0 Comments