initialization.md 20.0 KB
Newer Older
1 2
The Rails Initialization Process
================================
3

4 5
This guide explains the internals of the initialization process in Rails
as of Rails 4. It is an extremely in-depth guide and recommended for advanced Rails developers.
6

7
* Using `rails server`
8

9
--------------------------------------------------------------------------------
10

11
This guide goes through every method call that is
12 13 14 15
required to boot up the Ruby on Rails stack for a default Rails 4
application, explaining each part in detail along the way. For this
guide, we will be focusing on what happens when you execute +rails
server+ to boot your app.
16 17

NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified.
18

19 20
TIP: If you want to follow along while browsing the Rails [source
code](https://github.com/rails/rails), we recommend that you use the `t`
21
key binding to open the file finder inside GitHub and find files
22 23
quickly.

24 25
Launch!
-------
26

27
A Rails application is usually started with the command `rails server`.
28

29
### `bin/rails`
30

31
The actual `rails` command is kept in _bin/rails_:
32

33
```ruby
34 35
#!/usr/bin/env ruby

36 37
if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
  railties_path = File.expand_path('../../lib', __FILE__)
38 39
  $:.unshift(railties_path)
end
40
require "rails/cli"
41
```
42

43 44
This file will first attempt to push the `railties/lib` directory if
present, and then requires `rails/cli`.
45

46
### `railties/lib/rails/cli.rb`
47

48 49
This file looks like this:

50
```ruby
51 52
require 'rbconfig'
require 'rails/script_rails_loader'
53

54 55 56
# 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!
57

58
require 'rails/ruby_version_check'
59
Signal.trap("INT") { puts; exit(1) }
60

61 62 63 64 65 66
if ARGV.first == 'plugin'
  ARGV.shift
  require 'rails/commands/plugin_new'
else
  require 'rails/commands/application'
end
67
```
68

69
The `rbconfig` file from the Ruby standard library provides us with the `RbConfig` class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in `railties/lib/rails/script_rails_loader`.
70

71
```ruby
72
require 'pathname'
73

74 75 76 77 78
module Rails
  module ScriptRailsLoader
    RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
    SCRIPT_RAILS = File.join('script', 'rails')
    ...
79

80 81
  end
end
82
```
83

84
The `rails/script_rails_loader` file uses `RbConfig::Config` to obtain the `bin_dir` and `ruby_install_name` values for the configuration which together form the path to the Ruby interpreter. The `RbConfig::CONFIG["EXEEXT"]` will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in `exec_script_rails!`. As for the `SCRIPT_RAILS` constant, we'll see that when we get to the `in_rails_application?` method.
85

86
Back in `rails/cli`, the next line is this:
87

88
```ruby
89
Rails::ScriptRailsLoader.exec_script_rails!
90
```
91

92
This method is defined in `rails/script_rails_loader`:
93

94
```ruby
95 96 97 98 99 100 101 102
def self.exec_script_rails!
  cwd = Dir.pwd
  return unless in_rails_application? || in_rails_application_subdirectory?
  exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
  Dir.chdir("..") do
    # Recurse in a chdir block: if the search fails we want to be sure
    # the application is generated in the original working directory.
    exec_script_rails! unless cwd == Dir.pwd
103
  end
104 105 106
rescue SystemCallError
  # could not chdir, no problem just return
end
107
```
108

109
This method will first check if the current working directory (`cwd`) is a Rails application or a subdirectory of one. This is determined by the `in_rails_application?` method:
110

111
```ruby
112 113 114
def self.in_rails_application?
  File.exists?(SCRIPT_RAILS)
end
115
```
116

117
The `SCRIPT_RAILS` constant defined earlier is used here, with `File.exists?` checking for its presence in the current directory. If this method returns `false` then `in_rails_application_subdirectory?` will be used:
118

119
```ruby
120 121 122
def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
  File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent)
end
123
```
124

125
This climbs the directory tree until it reaches a path which contains a `script/rails` file. If a directory containing this file is reached then this line will run:
126

127
```ruby
128
exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
129
```
130

131
This is effectively the same as running `ruby script/rails [arguments]`, where `[arguments]` at this point in time is simply "server".
132

133 134
Rails Initialization
--------------------
O
Oscar Del Ben 已提交
135 136

Only now we finally start the real initialization process, beginning
137
with `script/rails`.
O
Oscar Del Ben 已提交
138

139
TIP: If you execute `script/rails` directly from your Rails app you will
O
Oscar Del Ben 已提交
140
skip executing all the code that we've just described.
141

142
### `script/rails`
143

144
This file is as follows:
145

146
```ruby
147 148 149
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'
150
```
151

152
The `APP_PATH` constant will be used later in `rails/commands`. The `config/boot` file referenced here is the `config/boot.rb` file in our application which is responsible for loading Bundler and setting it up.
153

154
### `config/boot.rb`
155

156
`config/boot.rb` contains:
157

158
```ruby
159
# Set up gems listed in the Gemfile.
O
Oscar Del Ben 已提交
160 161 162
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
163
```
164

165 166 167 168
In a standard Rails application, there's a `Gemfile` which declares all
dependencies of the application. `config/boot.rb` sets
`ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile
exists, `bundler/setup` is then required.
O
Oscar Del Ben 已提交
169 170 171 172

The gems that a Rails 4 application depends on are as follows:

TODO: change these when the Rails 4 release is near.
173 174

* abstract (1.0.0)
175 176 177 178 179
* actionmailer (4.0.0.beta)
* actionpack (4.0.0.beta)
* activemodel (4.0.0.beta)
* activerecord (4.0.0.beta)
* activesupport (4.0.0.beta)
180
* arel (2.0.7)
181 182 183 184 185 186 187 188 189 190 191
* builder (3.0.0)
* bundler (1.0.6)
* erubis (2.6.6)
* i18n (0.5.0)
* mail (2.2.12)
* mime-types (1.16)
* polyglot (0.3.1)
* rack (1.2.1)
* rack-cache (0.5.3)
* rack-mount (0.6.13)
* rack-test (0.5.6)
192 193
* rails (4.0.0.beta)
* railties (4.0.0.beta)
194 195 196 197 198 199
* rake (0.8.7)
* sqlite3-ruby (1.3.2)
* thor (0.14.6)
* treetop (1.4.9)
* tzinfo (0.3.23)

200
### `rails/commands.rb`
201

202
Once `config/boot.rb` has finished, the next file that is required is `rails/commands` which will execute a command based on the arguments passed in. In this case, the `ARGV` array simply contains `server` which is extracted into the `command` variable using these lines:
203

204
```ruby
205 206
ARGV << '--help' if ARGV.empty?

207 208
aliases = {
  "g"  => "generate",
209
  "d"  => "destroy",
210 211
  "c"  => "console",
  "s"  => "server",
V
Vijay Dev 已提交
212 213
  "db" => "dbconsole",
  "r"  => "runner"
214
}
215

216 217
command = ARGV.shift
command = aliases[command] || command
218
```
219

220 221 222
TIP: As you can see, an empty ARGV list will make Rails show the help
snippet.

223
If we used `s` rather than `server`, Rails will use the `aliases` defined in the file and match them to their respective commands. With the `server` command, Rails will run this code:
224

225
```ruby
226 227 228 229 230 231 232 233 234 235 236 237 238 239
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
  }
240
```
241

242
This file will change into the root of the directory (a path two directories back from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class.
243

244
```ruby
245 246 247 248 249 250
require 'fileutils'
require 'optparse'
require 'action_dispatch'

module Rails
  class Server < ::Rack::Server
251
```
252

253
`fileutils` and `optparse` are standard Ruby libraries which provide helper functions for working with files and parsing options.
254

255
### `actionpack/lib/action_dispatch.rb`
256

257 258
Action Dispatch is the routing component of the Rails framework.
It adds functionalities like routing, session, and common middlewares.
259

260
### `rails/commands/server.rb`
261

262
The `Rails::Server` class is defined in this file as inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`:
263

264
```ruby
265 266 267 268
def initialize(*)
  super
  set_environment
end
269
```
270

271
Firstly, `super` is called which calls the `initialize` method on `Rack::Server`.
272

273
### Rack: `lib/rack/server.rb`
274

275
`Rack::Server` is responsible for providing a common server interface for all Rack-based applications, which Rails is now a part of.
276

277
The `initialize` method in `Rack::Server` simply sets a couple of variables:
278

279
```ruby
280 281 282 283
def initialize(options = nil)
  @options = options
  @app = options[:app] if options && options[:app]
end
284
```
285

286
In this case, `options` will be `nil` so nothing happens in this method.
287

288
After `super` has finished in `Rack::Server`, we jump back to `rails/commands/server.rb`. At this point, `set_environment` is called within the context of the `Rails::Server` object and this method doesn't appear to do much at first glance:
289

290
```ruby
291 292 293
def set_environment
  ENV["RAILS_ENV"] ||= options[:environment]
end
294
```
295

296
In fact, the `options` method here does quite a lot. This method is defined in `Rack::Server` like this:
297

298
```ruby
299 300 301
def options
  @options ||= parse_options(ARGV)
end
302
```
303

304
Then `parse_options` is defined like this:
305

306
```ruby
307 308
def parse_options(args)
  options = default_options
309

310 311 312
  # Don't evaluate CGI ISINDEX parameters.
  # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
  args.clear if ENV.include?("REQUEST_METHOD")
313

314 315 316 317 318
  options.merge! opt_parser.parse! args
  options[:config] = ::File.expand_path(options[:config])
  ENV["RACK_ENV"] = options[:environment]
  options
end
319
```
320

321
With the `default_options` set to this:
322

323
```ruby
324 325 326 327 328 329 330 331 332 333
def default_options
  {
    :environment => ENV['RACK_ENV'] || "development",
    :pid         => nil,
    :Port        => 9292,
    :Host        => "0.0.0.0",
    :AccessLog   => [],
    :config      => "config.ru"
  }
end
334
```
335

336
There is no `REQUEST_METHOD` key in `ENV` so we can skip over that line. The next line merges in the options from `opt_parser` which is defined plainly in `Rack::Server`
337

338
```ruby
339 340 341
def opt_parser
  Options.new
end
342
```
343

344
The class *is* defined in `Rack::Server`, but is overwritten in `Rails::Server` to take different arguments. Its `parse!` method begins like this:
345

346
```ruby
347 348
def parse!(args)
  args, options = args.dup, {}
349

V
Vijay Dev 已提交
350 351 352 353
  opt_parser = OptionParser.new do |opts|
    opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
    opts.on("-p", "--port=port", Integer,
            "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
354
  ...
355
```
356

357 358 359
This method will set up keys for the `options` which Rails will then be
able to use to determine how its server should run. After `initialize`
has finished, we jump back into `rails/server` where `APP_PATH` (which was
360
set earlier) is required.
361

362
### `config/application`
363

364
When `require APP_PATH` is executed, `config/application.rb` is loaded.
365
This file exists in your app and it's free for you to change based
366
on your needs.
367

368
### `Rails::Server#start`
369

370
After `congif/application` is loaded, `server.start` is called. This method is defined like this:
371

372
```ruby
373
def start
374
  url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
375
  puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
376
  puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
377 378 379 380 381 382 383
  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))
384
  end
385

386 387 388 389 390 391 392 393 394
  unless options[:daemonize]
    wrapped_app # touch the app so the logger is set up

    console = ActiveSupport::Logger.new($stdout)
    console.formatter = Rails.logger.formatter

    Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
  end

395 396 397 398 399 400
  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
401
```
402

403
This is where the first output of the Rails initialization happens. This
404
method creates a trap for `INT` signals, so if you `CTRL-C` the server,
405
it will exit the process. As we can see from the code here, it will
406 407
create the `tmp/cache`, `tmp/pids`, `tmp/sessions` and `tmp/sockets`
directories. It then calls `wrapped_app` which is responsible for
408
creating the Rack app, before creating and assigning an
409
instance of `ActiveSupport::Logger`.
410

411
The `super` method will call `Rack::Server.start` which begins its definition like this:
412

413
```ruby
414
def start &blk
415 416 417
  if options[:warn]
    $-w = true
  end
418

419 420 421
  if includes = options[:include]
    $LOAD_PATH.unshift(*includes)
  end
422

423 424 425
  if library = options[:require]
    require library
  end
426

427 428 429 430 431 432 433
  if options[:debug]
    $DEBUG = true
    require 'pp'
    p options[:server]
    pp wrapped_app
    pp app
  end
434

435
  check_pid! if options[:pid]
436

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
  # Touch the wrapped app, so that the config.ru is loaded before
  # daemonization (i.e. before chdir, etc).
  wrapped_app

  daemonize_app if options[:daemonize]

  write_pid if options[:pid]

  trap(:INT) do
    if server.respond_to?(:shutdown)
      server.shutdown
    else
      exit
    end
  end

  server.run wrapped_app, options, &blk
end
455
```
456

457
The interesting part for a Rails app is the last line, `server.run`. Here we encounter the `wrapped_app` method again, which this time
458
we're going to explore more (even though it was executed before, and
459
thus memorized by now).
460

461
```ruby
462
@wrapped_app ||= build_app app
463
```
464

465
The `app` method here is defined like so:
466

467
```ruby
468 469 470 471
def app
  @app ||= begin
    if !::File.exist? options[:config]
      abort "configuration #{options[:config]} not found"
472
    end
473 474 475 476

    app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
    self.options.merge! options
    app
477
  end
478
end
479
```
480

481
The `options[:config]` value defaults to `config.ru` which contains this:
482

483
```ruby
484
# This file is used by Rack-based servers to start the application.
485

486
require ::File.expand_path('../config/environment',  __FILE__)
O
Oscar Del Ben 已提交
487
run <%= app_const %>
488
```
489 490


491
The `Rack::Builder.parse_file` method here takes the content from this `config.ru` file and parses it using this code:
492

493
```ruby
494
app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
495
    TOPLEVEL_BINDING, config
496
```
497

498
The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run:
499

500
```ruby
501
require ::File.expand_path('../config/environment',  __FILE__)
502
```
503

504
### `config/environment.rb`
505

506
This file is the common file required by `config.ru` (`rails server`) and Passenger. This is where these two ways to run the server meet; everything before this point has been Rack and Rails setup.
507

508
This file begins with requiring `config/application.rb`.
509

510
### `config/application.rb`
511

512
This file requires `config/boot.rb`, but only if it hasn't been required before, which would be the case in `rails server` but *wouldn't* be the case with Passenger.
513

M
Matt Duncan 已提交
514
Then the fun begins!
515

516 517
Loading Rails
-------------
518

519
The next line in `config/application.rb` is:
520

521
```ruby
522
require 'rails/all'
523
```
524

525
### `railties/lib/rails/all.rb`
526

527
This file is responsible for requiring all the individual frameworks of Rails:
528

529
```ruby
530
require "rails"
531

532
%w(
533 534 535 536
    active_record
    action_controller
    action_mailer
    rails/test_unit
537
    sprockets/rails
538 539 540 541
).each do |framework|
  begin
    require "#{framework}/railtie"
  rescue LoadError
542
  end
543
end
544
```
545

546
This is where all the Rails frameworks are loaded and thus made
547
available to the application. We won't go into detail of what happens
548 549
inside each of those frameworks, but you're encouraged to try and
explore them on your own.
550

551
For now, just keep in mind that common functionality like Rails engines,
552
I18n and Rails configuration is all being defined here.
553

554
### Back to `config/environment.rb`
555

556 557
When `config/application.rb` has finished loading Rails, and defined
your application namespace, you go back to `config/environment.rb`,
558
where your application is initialized. For example, if you application was called
559 560
`Blog`, here you would find `Blog::Application.initialize!`, which is
defined in `rails/application.rb`
O
Oscar Del Ben 已提交
561

562
### `railties/lib/rails/application.rb`
O
Oscar Del Ben 已提交
563

564
The `initialize!` method looks like this:
O
Oscar Del Ben 已提交
565

566
```ruby
O
Oscar Del Ben 已提交
567 568 569 570 571 572
def initialize!(group=:default) #:nodoc:
  raise "Application has been already initialized." if @initialized
  run_initializers(group, self)
  @initialized = true
  self
end
573
```
O
Oscar Del Ben 已提交
574 575 576 577 578 579

As you can see, you can only initialize an app once. This is also where the initializers are run.

TODO: review this

The initializers code itself is tricky. What Rails is doing here is it
580 581 582
traverses all the class ancestors looking for an `initializers` method,
sorting them and running them. For example, the `Engine` class will make
all the engines available by providing the `initializers` method.
O
Oscar Del Ben 已提交
583

584
After this is done we go back to `Rack::Server`
O
Oscar Del Ben 已提交
585

586
### Rack: lib/rack/server.rb
587

588
Last time we left when the `app` method was being defined:
589

590
```ruby
591 592 593 594 595 596 597 598 599 600 601
def app
  @app ||= begin
    if !::File.exist? options[:config]
      abort "configuration #{options[:config]} not found"
    end

    app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
    self.options.merge! options
    app
  end
end
602
```
603

604
At this point `app` is the Rails app itself (a middleware), and what
605 606
happens next is Rack will call all the provided middlewares:

607
```ruby
608 609 610 611 612 613 614 615 616
def build_app(app)
  middleware[options[:environment]].reverse_each do |middleware|
    middleware = middleware.call(self) if middleware.respond_to?(:call)
    next unless middleware
    klass = middleware.shift
    app = klass.new(app, *middleware)
  end
  app
end
617
```
618

619
Remember, `build_app` was called (by wrapped_app) in the last line of `Server#start`.
620 621
Here's how it looked like when we left:

622
```ruby
623
server.run wrapped_app, options, &blk
624
```
625

626
At this point, the implementation of `server.run` will depend on the
O
Oscar Del Ben 已提交
627
server you're using. For example, if you were using Mongrel, here's what
628
the `run` method would look like:
O
Oscar Del Ben 已提交
629

630
```ruby
O
Oscar Del Ben 已提交
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
def self.run(app, options={})
  server = ::Mongrel::HttpServer.new(
    options[:Host]           || '0.0.0.0',
    options[:Port]           || 8080,
    options[:num_processors] || 950,
    options[:throttle]       || 0,
    options[:timeout]        || 60)
  # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
  # Use is similar to #run, replacing the app argument with a hash of
  # { path=>app, ... } or an instance of Rack::URLMap.
  if options[:map]
    if app.is_a? Hash
      app.each do |path, appl|
        path = '/'+path unless path[0] == ?/
        server.register(path, Rack::Handler::Mongrel.new(appl))
      end
    elsif app.is_a? URLMap
      app.instance_variable_get(:@mapping).each do |(host, path, appl)|
       next if !host.nil? && !options[:Host].nil? && options[:Host] != host
       path = '/'+path unless path[0] == ?/
       server.register(path, Rack::Handler::Mongrel.new(appl))
      end
    else
      raise ArgumentError, "first argument should be a Hash or URLMap"
    end
  else
    server.register('/', Rack::Handler::Mongrel.new(app))
  end
  yield server  if block_given?
  server.run.join
end
662
```
O
Oscar Del Ben 已提交
663

664
We won't dig into the server configuration itself, but this is
O
Oscar Del Ben 已提交
665 666
the last piece of our journey in the Rails initialization process.

667
This high level overview will help you understand when your code is
O
Oscar Del Ben 已提交
668 669 670
executed and how, and overall become a better Rails developer. If you
still want to know more, the Rails source code itself is probably the
best place to go next.