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
![]() |
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
![]() |
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.
![]() |
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.
![]() |
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
![]() |
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
![]() |
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
![]() |
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
![]() |
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.
![]() |
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
![]() |
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
![]() |
You can use subfolders to organize your initializers if you like, because Rails will look into the whole hierarchy. |
![]() |
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.
![]() |
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