#299 Rails Initialization Walkthrough pro
In this episode we’ll walk through the Rails initialization process and determine what happens behind the scenes when you start up a Rails server. Doing this should give us a better understanding of how engines and railties work so let’s get started.
We’ll be looking at Rails’ source code so first we’ll need to get a copy of it. We can do this by running
$ git clone https://github.com/rails/rails.git
When it’s downloaded we’ll checkout the version we want to look at: 3.1.1.
$ cd rails $ git checkout v3.1.1
The code we’ll be looking at is under the railties/lib
directory as that’s where the initialization code lives. Even though we’re under the railties
directory the lib
directory has a rails
subdirectory so if you see something prefixed with the Rails path or inside a Rails
module the source code for it is probably in railties
.
The rails Command
Now that we have a copy of the source code we’ll start by looking at the rails
command. This command is interesting as it behaves very differently depending on whether it is called from inside a Rails application’s directory. Outside a Rails app it can’t do much other than create a new application or engine. We’ll use this functionality now to create a new Rails application.
$ rails new blog
When we move into our new application’s directory and run the rails
command again it behaves quite differently.
$ cd blog/ $ rails Usage: rails COMMAND [ARGS] The most common rails commands are: generate Generate new code (short-cut alias: "g") console Start the Rails console (short-cut alias: "c") server Start the Rails server (short-cut alias: "s") dbconsole Start a console for the database specified in config/database.yml (short-cut alias: "db") new Create a new Rails application. "rails new my_app" creates a new application called MyApp in "./my_app" In addition to those, there are: application Generate the Rails application code destroy Undo code generated with "generate" benchmarker See how fast a piece of code runs profiler Get profile information from a piece of code plugin Install a plugin runner Run a piece of code in the application environment (short-cut alias: "r") All commands can be run with -h for more information.
The rails
command now has a completely different set of subcommands that perform actions on our application. This command loads and runs the cli.rb
file so let's take a look at it.
require 'rbconfig' require 'rails/script_rails_loader' # If we are inside a Rails application this method performs an exec and thus # the rest of this script is not run. Rails::ScriptRailsLoader.exec_script_rails! require 'rails/ruby_version_check' Signal.trap("INT") { puts; exit(1) } if ARGV.first == 'plugin' ARGV.shift require 'rails/commands/plugin_new' else require 'rails/commands/application' end
The first thing the code in the file does is look for a file called rails
in a script
directory under immediately under the directory in which the command is run. If cli.rb
finds this file it will load and run it and not run the rest of the script. If it doesn’t the rest of the script will run and create a new Rails application or plugin depending on the option passed to it. It does this by running either plugin_new.rb
or application.rb
in the commands
subdirectory. If we’re creating an application this will load up the generators and run the AppGenerator
class.
So, running rails in a Rails application is the same as running the script/rails
command. Let’s take a look at what this command does.
#!/usr/bin/env ruby # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. APP_PATH = File.expand_path('../../config/application', __FILE__) require File.expand_path('../../config/boot', __FILE__) require 'rails/commands'
This code only does a few things. It records the path to the application’s configuration file then it loads the /config/boot.rb
file and rails/commands
. We’ll take a look at the boot file first.
require 'rubygems' # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
All this file does is load rubygems
and run Bundler. The application’s gems aren’t run here, but the load paths are set up so they can be required later.
We’ll find the rails/commands
file under Rails’ railties/lib
directory. Near the top of the file we’ll find aliases for some of the commands that we can pass to rails
.
aliases = { "g" => "generate", "c" => "console", "s" => "server", "db" => "dbconsole", "r" => "runner" } command = ARGV.shift command = aliases[command] || command
This code then shifts the command that was passed in to a variable and expands any alias that was used. The rest of the file is made up of a case
statement which defines the behaviour for each command. We’ll focus on the server
command.
Starting up a Rails Server
When we run rails server
(or rails s
) the following code runs.
when 'server' # Change to the application's path if there is no config.ru file in current dir. # This allows us to run script/rails server from other directories, but still get # the main config.ru and properly set the tmp directory. Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) require 'rails/commands/server' Rails::Server.new.tap { |server| # We need to require application after the server sets environment, # otherwise the --environment option given to the server won't propagate. require APP_PATH Dir.chdir(Rails.application.root) server.start }
This code requires the /commands/server.rb
file, creates a new server instance, loads the application’s config file then moves to the application’s root directory and starts the server up.
The server.rb
file is where Rails::Server
is defined and this class inherits from Rack::Server
. The Rails::Server
class has a start
method which outputs some familiar looking text and then calls super
to start up the Rack server.
def start url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" puts "=> Call with -d to detach" unless options[:daemonize] trap(:INT) { exit } puts "=> Ctrl-C to shutdown server" unless options[:daemonize] #Create required tmp directories if not found %w(cache pids sessions sockets).each do |dir_to_make| FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) end super ensure # The '-h' option calls exit before @options is set. # If we call 'options' with it unset, we get double help banners. puts 'Exiting' unless @options && options[:daemonize] end
The Rack server expects a rackup
file and by default it will look for a file called config.ru
in the Rails application’s root directory. This is nice as it means that our Rails 3 app is just a simple Rack application. You’ll find a config.ru
file in any Rails 3 application’s root directory. All this file does is load the /config/environment.rb
file and then tell Rack to run the application.
# This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) run Blog::Application
Whenever Rack receives a web request it will call call
on the object it was passed and pass in any request arguments. This means that our blog application is a full Rack-compatible application and this is how it serves requests.
How The Rails Application Loads Up
So we know how the server starts up and serves requests, but we’ve skipped the loading of the application itself. We’ll dive into environment.rb
next and step through the loading process.
# Load the rails application require File.expand_path('../application', __FILE__) # Initialize the rails application Blog::Application.initialize!
This file is fairly simple. It loads the app’s /config/application.rb
file then calls initialize!
on our application. Let’s open that file and see what it does.
require File.expand_path('../boot', __FILE__) require 'rails/all' if defined?(Bundler) # If you precompile assets before deploying to production, use this line Bundler.require(*Rails.groups(:assets => %w(development test))) # If you want your assets lazily compiled in production, use this line # Bundler.require(:default, :assets, Rails.env) end
This file first loads the boot.rb
file if it hasn’t already been loaded. In our case it already has (we looked at it earlier and it sets up Bundler). Next it requires rails/all
which loads the entire Rails framework. Let’s see how that works.
require "rails" %w( active_record action_controller action_mailer active_resource rails/test_unit sprockets ).each do |framework| begin require "#{framework}/railtie" rescue LoadError end end
This file loads up each part of the Rails framework by requiring the railtie
file for that part of the framework. We won’t go into each framework here but you can checkout each railtie
to see what it does.
Once application.rb
has loaded the Rails framework it includes all of the gems in the application’s Gemfile
using Bundler by calling Bundler.require
. This code has changed in Rails 3.1 and now it only loads the assets
group of gems if the current environment is development
or test
. This means that if we’re using precompiled assets the assets
gems won’t have to be loaded inside the production environment. The file includes some comments that tell you how to change this behaviour if you want to. Next we’ll move on to the interesting part of the file, the part that creates the Blog::Application
class.
module Blog class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] config.assets.enabled = true config.assets.version = '1.0' end end
This class is passed to the Rack server in the Rackup file and is in itself a Rack application. The class inherits from Rails::Application
so let’s take a look at that. Rails::Application
inherits from Engine
which means that every Rails application is also a Rails engine. Applications have some additional functionality defined, though, and one such thing is you cannot have more than one Rails::Application
subclass.
The code below is executed when a class inherits from Rails::Application
. When this happens the Rails.application
variable is set to base.instance
which creates a new instance of our Rails application. This means that we can gain access to our Rails application from anywhere in it by calling Rails.application
.
class << self def inherited(base) raise "You cannot have more than one Rails::Application" if Rails.application super Rails.application = base.instance Rails.application.add_lib_to_load_path! ActiveSupport.run_load_hooks(:before_configuration, base.instance) end end
The way that this instance behaviour works is interesting. It makes the Application
class a singleton. We can trace that functionality back through the call to super
in the code above. This takes us into the Engine
class and the first thing to note about Engine
is that it inherits from Railtie
. Railtie
is used all over in Ruby gems. Anything that wants to integrate into a Rails application creates its own railtie. Engine
also defines the inherited
method but it doesn’t do much of interest in there, just cleans up some of the backtrace.
def inherited(base) unless base.abstract_railtie? base.called_from = begin # Remove the line number from backtraces making sure we don't leave anything behind call_stack = caller.map { |p| p.sub(/:\d+.*/, '') } File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] end end super end
The Railtie class doesn’t inherit from another class, but it does include a module. Whenever this class is inherited the Railtie::Configurable
module is included.
def inherited(base) unless base.abstract_railtie? base.send(:include, Railtie::Configurable) subclasses << base end end
Note that this class keeps track of the classes that subclass it so that it can use them later on.
The Configurable
module is where our rabbit trail ends. It’s here where the instance
method that we call back in our Application
class is defined. This method creates a new instance of the class and caches it so that the same instance is returned every time the method is called.
require 'active_support/concern' module Rails class Railtie module Configurable extend ActiveSupport::Concern module ClassMethods delegate :config, :to => :instance def inherited(base) raise "You cannot inherit from a #{self.superclass.name} child" end def instance @instance ||= new end def respond_to?(*args) super || instance.respond_to?(*args) end def configure(&block) class_eval(&block) end protected def method_missing(*args, &block) instance.send(*args, &block) end end end end end
This module also does something very interesting. It overrides method_missing
so that every class method that is called on this class that isn’t explicitly defined is passed to the instance. This works just like a singleton does, any method called on the class will act as an instance method.
Back in our Application
class there are a number of instance methods defined and these can all act as class methods by being called directly on the class. We will often see these methods called on the Blog
class itself and these will be delegated to the instance. We can see an example of this by going back to our application and looking at the application.rb
file. In this file we see config
called as a class method in our application to access the configuration.
module Blog class Application < Rails::Application config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] # Enable the asset pipeline config.assets.enabled = true # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' end end
If we look at the internals of the Application
class, however, we’ll see the config
method defined there, but as an instance method. The class call will delegate to this instance call.
def config @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) end
Notice that this method returns a new Application::Configuration
object. We won’t get into the configuration in this episode but it’s worth taking a look at this class to see how it works.
Initializers
One thing we will take a look at are initializers. Initializers are small pieces of code that are run in a sequence and this is where most of the initialization process for the application takes place, Rails’ Application
class has an initialize!
method that runs all of the initializers.
def initialize!(group=:default) raise "Application has been already initialized." if @initialized run_initializers(group, self) @initialized = true self end
This method is called from inside our application’s /config/environment.rb
file.
So we know when our initializers are executed, but where are they defined? Most are defined in the Engine
class and this class makes several calls to an initializer
method. If you ever set up a Rails plugin or engine through a gem you’ll be familiar with this as this is how you get a piece of code to run at the beginning of a Rails initialization process. There are various initializers in this class and some interesting things that happen in them such as the loading of the config files for the current environment and the loading of the initializers in the application’s config/initializers
directory. This is where the loading of a Rails application happens.
What about initializers defined outside of our application such as those in gems? If we look in the Engine
class we’ll find a method called initializers
. This takes all the railties, including any gems railties, and fetches their initializers. It then returns them along with the application’s railties so that they are all run.
def initializers initializers = [] railties.all { |r| initializers += r.initializers } initializers += super initializers end
That’s it for our whirlwind tour of what happens as a Rails application starts up. If you want some related information on this topic there are a couple of Rails Guides that are worth reading. “The Rails Initialization Process” walks through some of what we’ve covered here while another, “Configuring Rails Applications” is worth reading as configuration is ties in closely with initialization.