The Rails startup process from a paragliders perspective
The Rails 2.0 startup process
When you look at the Rails startup and initialization process from a paraglider's perspective you can watch the following things happening:
- the framework gets initalized
- the environment gets loaded
- plugins, observers and routes are initialized
- the dispatcher is initialized
- the request is dispatched to the controller selected by the routing system
So, let's dive into things straight away.
Depending on your server setup, when a request from the web hits your Rails application, it enters Rails' realm through one of the doorways that sit in your public/ directory in form of the dispatcher files.
E.g. the dispatch.rb file contains something like this:
require File.dirname(__FILE__) + "/../config/environment" require "dispatcher" Dispatcher.dispatch
Loading the environment
As you can see from that code Rails does immediately go into your environment.rb.
The first thing it does there is to require boot.rb which defines the RAILS_ROOT constant and branches out to different boot mechanisms for booting up Rails from the vendor/rails directory or from the Rails gem respectively.
Basically, what happens in boot.rb is that the Rails::Initializer class is required from different locations (depending on whether you're using the Rails gem or have frozen Rails to vendor/rails) and #run is called on Rails::Initializer with the task to set the $LOAD_PATH to the Rails libraries and your application.
After boot.rb has finished the Rails::Initializer is run again with the config block as defined in your environment.rb. More specifically a new configuration object is created and yielded to the block from your environment.rb so that you have the opportunity to set various config options here. The configuration object (now complete with your options set) is then handed to a new Rails::Initializer instance and now #process is called on it.
In Rails::Initializer#process a whole lot of work is done. Some the more interesting things here are:
- The frameworks are required (require_frameworks).
- The environment is loaded from config/environments (load_environment). Typically this will be the development, test or production environment where you have the opportunity to further tweak the configuration on a per environment basis.
- Next, amongst other things, the Ruby encoding (KCODE), database, logger and the dependency mechanism are initialized. (As for the dependency mechanism this means that it is set to either load or require files depending on whether Rails will cache classes or not based on the config.cache_classes setting in your environment files. E.g. by default Rails will cache classes in production but reload them in development mode.)
- Now (in this order) the plugins and observers the routing system are loaded and initialized. Therefor, plugins have the opportunity to modify Observers or observed models and extend routes.
- Then the after_initialize callback is executed by calling all blocks that have been registered through config.after_initialize by now.
- Lastly the application initializers are loaded from config/initializers. (This has been invented in Rails 2.0 that allows you to keep your environment.rb uncluttered.)
Load the plugins
Let's have a closer look at how the plugins are loaded and intialized:
Plugins are loaded by calling the #load_plugins method on the Plugin::Loader class that is defined by config.plugin_loader. (Now, how funny is this sentence read?)
That means that you can have a different implementation of the Plugin::Loader class load your plugins in a more funky way. (E.g. amongst others RailsEngines for Rails 2.0 are supposed to leverage this). By default the Rails::Plugin::Loader is used.
After the Rails::Plugin::Loader has been instantiated by the Initializer the method #add_plugin_load_paths is called on it. The Plugin::Loader will locate the plugins, instantiate them and the plugins lib/ subdirectories will be registered to the following collections:
- $LOAD_PATH
- Dependencies.load_paths
- Dependencies.load_once_paths
Plugins are located using the Plugin::Locator classes that are configured through config.plugin_locators. By default the Rails::Plugin::FileSystemLocator class is used. This runs through the paths given by config.plugin_paths (defaults to the vendor/plugins path) and according to the config.plugins setting (which defaults to 'all') to collect valid plugin instances and returns instances of the Rails::Plugin class.
Whether or not a directory contains a plugin is determined by Rails::Plugin#valid? which checks if the directory exists and it contains either an init.rb file or a lib/ subdirectory.
An interesting bit that doesn't seem to be widely known is that (as the rdoc puts it) "plugins can be nested arbitrarily deep within an unspecified number of intermediary directories, this method runs recursively until it finds a plugin directory".
Later on in its initialization process (after loading the environment, initializing the database, logger and frameworks) the Initializer will call #load_plugins on the Plugin::Loader. This in turn will iterate through all the plugin objects and call the #load method on each of them.
This method most importantly calls its private method #evaluate_init_rb which evaluates the plugin's init.rb file using this method's binding. This ensures that you can access (amongst other, less important things) the private method #lib_path and the config object from within the init.rb file.
Define the dispatcher and fire it off
After the environment loading has completed dispatcher.rb is required. Nothing really overwhelming happens here (see the code at the top of this article). The dispatcher constant is set to ActionController::Dispatcher so that the last line effectively executes ActionController::Dispatcher.dispatch. This creates an instance of the same class and calls the method #dispatch_cgi on it.
In #dispatch_cgi the @request (an instance of CgiRequest) and @response (an instance of CgiResponse) instance variables of the ActionController are created that everybody is familiar with. After that it calls the #dispatch method which ...
- runs :before callbacks,
- then handles the request
- and finally runs :after callbacks.
Also, If an exception is thrown it rescues it and calls #failsafe_rescue for it.
There are two :before callback methods registered by this class itself. These are:
- #reload_application which calls Routing::Routes.reload and sets the Dispatcher instance to self.unprepared = true. (This will only happen if the Dependencies load mechanism is configured to load files instead of requiring them. Not sure what difference this makes.)
- #prepare_application which will already require your ApplicationController, call ActiveRecord::Base.verify_active_connections! (which seems to do what the name says, not sure what that means in practice) and afterwards runs the :prepare callbacks if the class is set to unprepared (which is the case if the application is either in development mode or if it's the first request in production mode)
The :prepare callbacks are registered through the method Dispatcher#to_prepare which you can also call through config.to_prepare during the startup process (e.g. from within your plugin's init.rb).
There seems to be only one task registered for the :prepare stage by Rails itself and that is to call ActiveRecord::Base.instantiate_observers.
Now the :before callbacks stage is finished and #handle_request is called which does three things:
- it kicks of the routing recognition through Routing::Routes.recognize(@request) which returns a controller instance (depending on which route matches the requested URL)
- then calls process(@request, @response) on the controller
- and finally calls out(@output) on the response object.
More resources
Here are some more resources about the Rails initialization process. Keep in mind though that these are based on Rails 1.2.
- Environments and the Rails initialisation process (toolmantim)
- Understanding the Rails Plugin Initialization process (technoweenie)
- Understanding the Rails Plugin Initialization process: Part 2
- Understanding the Rails Plugin Initialization process (this is Part 3)
- Exploring the Request/Response Life-Cycle through the Mongrel and Rails Source Code. (Ezra Zygmuntowicz)
Also, for a detailled view on the internals of the routing system, be sure to check out these awesome articles by Jamis Buck: