Today, we investigate Rails’ boot sequence by observing what happens when we run rails console. Part 2 will look at rails server. Github links to relevant files are provided as necessary.

Our journey begins inside the rails binary1, which is executed by ruby_executable_hooks2:

#!/usr/bin/env ruby_executable_hooks
# This file was generated by RubyGems.
# The application 'railties' is installed as part of a gem, and
# this file is here to facilitate running it.

require 'rubygems'
version = ">= 0"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
    version = $1

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)

It calls load Gem.bin_path('railties', 'rails', version), which corresponds to gems/railties-4.X.X/bin/rails.rb:

#!/usr/bin/env ruby

git_path = File.expand_path('../../../.git', __FILE__)

if File.exist?(git_path)
  railties_path = File.expand_path('../../lib', __FILE__)
require "rails/cli"

In gems/railties-4.X.X/lib/rails/cli.rb:

require 'rails/app_loader'

# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.

exec_app is in charge of executing the bin/rails inside your Rails application. It will look for it recursively, meaning that you can call rails anywhere in your application directory. In fact, rails server or rails console is equivalent to calling ruby bin/rails server or ruby bin/rails console See the abridged contents of rails/app_loader.rb below:

module Rails
  module AppLoader # :nodoc:
    extend self

    RUBY = Gem.ruby
    EXECUTABLES = ['bin/rails', 'script/rails']

    def exec_app
      original_cwd = Dir.pwd

      loop do
        (code to check for the executable and execute it if found)

        # If we exhaust the search there is no executable, this could be a
        # call to generate a new application, so restore the original cwd.
        Dir.chdir(original_cwd) and return if

        # Otherwise keep moving upwards in search of an executable.

    def find_executable
      EXECUTABLES.find { |exe| File.file?(exe) }

Next, we turn our focus temporarily to your Rails application. In bin/rails, two files are required:

#!/usr/bin/env ruby

### The below part will be present if you use spring
# begin
#  load File.expand_path("../spring", __FILE__)
# rescue LoadError
# end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'

../config/boot (in your app directory) determines the location of the Gemfile and allows Bundler to configure the load path for your Gemfile’s dependencies.

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' # Set up gems listed in the Gemfile.

rails/commands parses options passed in as command line arguments, including alias mapping (c for console, g for generate, etc.)

ARGV << '--help' if ARGV.empty?

aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner"

command = ARGV.shift
command = aliases[command] || command

require 'rails/commands/commands_tasks'!(command)

rails/commands/commands_tasks.rb is in charge of throwing errors in the case of invalid commands, or delegating valid commands to the respective methods, themselves split into files in the rails/commands directory:

$ ls
application.rb    console.rb        destroy.rb        plugin.rb         server.rb
commands_tasks.rb dbconsole.rb      generate.rb       runner.rb

For example, if rails console is run, the console method in commands_tasks.rb requires console.rb and runs the start class method from the Rails::Console class, passing it your application as the first argument (command_tasks.rb is made known of your application by requiring APP_PATH, which you’ve kindly provided previously in bin/rails):

def console
  options = Rails::Console.parse_arguments(argv)

  # RAILS_ENV needs to be set before config/application is required
  ENV['RAILS_ENV'] = options[:environment] if options[:environment]

  # shift ARGV so IRB doesn't freak

  Rails::Console.start(Rails.application, options)

# some ways down
  def require_command!(command)
    require "rails/commands/#{command}"
  def require_application_and_environment!
    require APP_PATH

In rails/commands/console.rb, you can see the start class method instantiating itself and calling the new instance’s start instance method:

class << self # old idiom for defining class methods, equivalent to def self.start
  def start(*args)

As it is instantiated, @app is set as your Rails application, and @console is set to app.config.console if present, or defaults to IRB:

def initialize(app, options={})
  @app     = app
  @options = options

  app.sandbox = sandbox?

  @console = app.config.console || IRB

Let’s see if the above code actually works by setting your application config to use Pry as the console instead:

# don't forget to add gem 'pry' to your Gemfile and bundle
# in coolrailsapp/config/application.rb
module CoolRailsApp
  class Application < Rails::Application
    config.console = Pry
$ rails c
Loading development environment (Rails 4.2.3)
[1] pry(main)>

Great success! Now let’s look at the actual start instance method, whose code is relatively self-explanatory:

def start
  if RUBY_VERSION < '2.0.0'
    require_debugger if debugger?

  set_environment! if environment?

  if sandbox?
    puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})"
    puts "Any modifications you make will be rolled back on exit"
    puts "Loading #{Rails.env} environment (Rails #{Rails.version})"

  if defined?(console::ExtendCommandBundle)
    console::ExtendCommandBundle.send :include, Rails::ConsoleMethods

Finally, console.start boots the console3.

Next, we’ll look at the code path taken by rails server.

  1. As indicated in the comments, this file is auto-generated by RubyGems. How does it know to load Rails, as in the last line (load Gem.bin_path('railties', 'rails', version))? Taking a look in railties.gemspec gives us the answer:

    s.bindir      = 'exe'
    s.executables = ['rails']

    What does the above mean? RubyGem’s documentation:


    Executables included in the gem.

    For example, the rake gem has rake as an executable. You don’t specify the full path (as in bin/rake); all application-style files are expected to be found in bindir.

    Take a look inside the exe directory - its contents will be very familiar soon :)

  2. The binary is defined by the sha-bang to be executed by ruby_executable hooks, which is a thin wrapper that allows RubyGems to run initialization hooks ($0)) before Ruby runs the actual code (eval$0), binding, $0). This is what the actual ruby_executable_hooks binary looks like:

    #!/usr/bin/env ruby
    title = "ruby #{ARGV*" "}"
    $0    = ARGV.shift
    Process.setproctitle(title) if Process.methods.include?(:setproctitle)
    require 'rubygems'
      require 'executable-hooks/hooks'$0)
    rescue LoadError
      warn "unable to load executable-hooks/hooks" if ENV.key?('ExecutableHooks_DEBUG')
    eval$0), binding, $0

  3. You can test this out for yourself with just 3 lines of code. Create a file with the following:

    require 'irb'
    x = IRB

    Run it and see what happens:

    $ ruby ~/test.rb