diff options
Diffstat (limited to 'guides/source/initialization.textile')
-rw-r--r-- | guides/source/initialization.textile | 666 |
1 files changed, 0 insertions, 666 deletions
diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile deleted file mode 100644 index b23f31cb1a..0000000000 --- a/guides/source/initialization.textile +++ /dev/null @@ -1,666 +0,0 @@ -h2. The Rails Initialization Process - -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. - -* Using +rails server+ - -endprologue. - -This guide goes through every method call that is -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. - -NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified. - -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+ -key binding to open the file finder inside GitHub and find files -quickly. - -h3. Launch! - -A Rails application is usually started with the command +rails server+. - -h4. +bin/rails+ - -The actual +rails+ command is kept in _bin/rails_: - -<ruby> -#!/usr/bin/env ruby - -if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git')) - railties_path = File.expand_path('../../lib', __FILE__) - $:.unshift(railties_path) -end -require "rails/cli" -</ruby> - -This file will first attempt to push the +railties/lib+ directory if -present, and then requires +rails/cli+. - -h4. +railties/lib/rails/cli.rb+ - -This file looks like this: - -<ruby> -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 -</ruby> - -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+. - -<ruby> -require 'pathname' - -module Rails - module ScriptRailsLoader - RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] - SCRIPT_RAILS = File.join('script', 'rails') - ... - - end -end -</ruby> - -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. - -Back in +rails/cli+, the next line is this: - -<ruby> -Rails::ScriptRailsLoader.exec_script_rails! -</ruby> - -This method is defined in +rails/script_rails_loader+: - -<ruby> -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 - end -rescue SystemCallError - # could not chdir, no problem just return -end -</ruby> - -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: - -<ruby> -def self.in_rails_application? - File.exists?(SCRIPT_RAILS) -end -</ruby> - -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: - -<ruby> -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 -</ruby> - -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: - -<ruby> -exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? -</ruby> - -This is effectively the same as running +ruby script/rails [arguments]+, where +[arguments]+ at this point in time is simply "server". - -h3. Rails Initialization - -Only now we finally start the real initialization process, beginning -with +script/rails+. - -TIP: If you execute +script/rails+ directly from your Rails app you will -skip executing all the code that we've just described. - -h4. +script/rails+ - -This file is as follows: - -<ruby> -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' -</ruby> - -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. - -h4. +config/boot.rb+ - -+config/boot.rb+ contains: - -<ruby> -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) - -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) -</ruby> - -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. - -The gems that a Rails 4 application depends on are as follows: - -TODO: change these when the Rails 4 release is near. - -* abstract (1.0.0) -* 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) -* arel (2.0.7) -* 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) -* rails (4.0.0.beta) -* railties (4.0.0.beta) -* rake (0.8.7) -* sqlite3-ruby (1.3.2) -* thor (0.14.6) -* treetop (1.4.9) -* tzinfo (0.3.23) - -h4. +rails/commands.rb+ - -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: - -<ruby> -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 -</ruby> - -TIP: As you can see, an empty ARGV list will make Rails show the help -snippet. - -If we used <tt>s</tt> 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: - -<ruby> -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 - } -</ruby> - -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. - -<ruby> -require 'fileutils' -require 'optparse' -require 'action_dispatch' - -module Rails - class Server < ::Rack::Server -</ruby> - -+fileutils+ and +optparse+ are standard Ruby libraries which provide helper functions for working with files and parsing options. - -h4. +actionpack/lib/action_dispatch.rb+ - -Action Dispatch is the routing component of the Rails framework. -It adds functionalities like routing, session, and common middlewares. - -h4. +rails/commands/server.rb+ - -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+: - -<ruby> -def initialize(*) - super - set_environment -end -</ruby> - -Firstly, +super+ is called which calls the +initialize+ method on +Rack::Server+. - -h4. Rack: +lib/rack/server.rb+ - -+Rack::Server+ is responsible for providing a common server interface for all Rack-based applications, which Rails is now a part of. - -The +initialize+ method in +Rack::Server+ simply sets a couple of variables: - -<ruby> -def initialize(options = nil) - @options = options - @app = options[:app] if options && options[:app] -end -</ruby> - -In this case, +options+ will be +nil+ so nothing happens in this method. - -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: - -<ruby> -def set_environment - ENV["RAILS_ENV"] ||= options[:environment] -end -</ruby> - -In fact, the +options+ method here does quite a lot. This method is defined in +Rack::Server+ like this: - -<ruby> -def options - @options ||= parse_options(ARGV) -end -</ruby> - -Then +parse_options+ is defined like this: - -<ruby> -def parse_options(args) - options = default_options - - # Don't evaluate CGI ISINDEX parameters. - # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html - args.clear if ENV.include?("REQUEST_METHOD") - - options.merge! opt_parser.parse! args - options[:config] = ::File.expand_path(options[:config]) - ENV["RACK_ENV"] = options[:environment] - options -end -</ruby> - -With the +default_options+ set to this: - -<ruby> -def default_options - { - :environment => ENV['RACK_ENV'] || "development", - :pid => nil, - :Port => 9292, - :Host => "0.0.0.0", - :AccessLog => [], - :config => "config.ru" - } -end -</ruby> - -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+ - -<ruby> -def opt_parser - Options.new -end -</ruby> - -The class *is* defined in +Rack::Server+, but is overwritten in +Rails::Server+ to take different arguments. Its +parse!+ method begins like this: - -<ruby> -def parse!(args) - args, options = args.dup, {} - - 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 } - ... -</ruby> - -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 -set earlier) is required. - -h4. +config/application+ - -When +require APP_PATH+ is executed, +config/application.rb+ is loaded. -This file exists in your app and it's free for you to change based -on your needs. - -h4. +Rails::Server#start+ - -After +congif/application+ is loaded, +server.start+ is called. This method is defined like this: - -<ruby> -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 - - 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 - - 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 -</ruby> - -This is where the first output of the Rails initialization happens. This -method creates a trap for +INT+ signals, so if you +CTRL-C+ the server, -it will exit the process. As we can see from the code here, it will -create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ -directories. It then calls +wrapped_app+ which is responsible for -creating the Rack app, before creating and assigning an -instance of +ActiveSupport::Logger+. - -The +super+ method will call +Rack::Server.start+ which begins its definition like this: - -<ruby> -def start &blk - if options[:warn] - $-w = true - end - - if includes = options[:include] - $LOAD_PATH.unshift(*includes) - end - - if library = options[:require] - require library - end - - if options[:debug] - $DEBUG = true - require 'pp' - p options[:server] - pp wrapped_app - pp app - end - - check_pid! if options[:pid] - - # 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 -</ruby> - -The interesting part for a Rails app is the last line, +server.run+. Here we encounter the +wrapped_app+ method again, which this time -we're going to explore more (even though it was executed before, and -thus memorized by now). - -<ruby> -@wrapped_app ||= build_app app -</ruby> - -The +app+ method here is defined like so: - -<ruby> -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 -</ruby> - -The +options[:config]+ value defaults to +config.ru+ which contains this: - -<ruby> -# This file is used by Rack-based servers to start the application. - -require ::File.expand_path('../config/environment', __FILE__) -run <%= app_const %> -</ruby> - - -The +Rack::Builder.parse_file+ method here takes the content from this +config.ru+ file and parses it using this code: - -<ruby> -app = eval "Rack::Builder.new {( " <plus> cfgfile <plus> "\n )}.to_app", - TOPLEVEL_BINDING, config -</ruby> - -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: - -<ruby> -require ::File.expand_path('../config/environment', __FILE__) -</ruby> - -h4. +config/environment.rb+ - -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. - -This file begins with requiring +config/application.rb+. - -h4. +config/application.rb+ - -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. - -Then the fun begins! - -h3. Loading Rails - -The next line in +config/application.rb+ is: - -<ruby> -require 'rails/all' -</ruby> - -h4. +railties/lib/rails/all.rb+ - -This file is responsible for requiring all the individual frameworks of Rails: - -<ruby> -require "rails" - -%w( - active_record - action_controller - action_mailer - rails/test_unit - sprockets/rails -).each do |framework| - begin - require "#{framework}/railtie" - rescue LoadError - end -end -</ruby> - -This is where all the Rails frameworks are loaded and thus made -available to the application. We won't go into detail of what happens -inside each of those frameworks, but you're encouraged to try and -explore them on your own. - -For now, just keep in mind that common functionality like Rails engines, -I18n and Rails configuration is all being defined here. - -h4. Back to +config/environment.rb+ - -When +config/application.rb+ has finished loading Rails, and defined -your application namespace, you go back to +config/environment.rb+, -where your application is initialized. For example, if you application was called -+Blog+, here you would find +Blog::Application.initialize!+, which is -defined in +rails/application.rb+ - -h4. +railties/lib/rails/application.rb+ - -The +initialize!+ method looks like this: - -<ruby> -def initialize!(group=:default) #:nodoc: - raise "Application has been already initialized." if @initialized - run_initializers(group, self) - @initialized = true - self -end -</ruby> - -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 -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. - -After this is done we go back to +Rack::Server+ - -h4. Rack: lib/rack/server.rb - -Last time we left when the +app+ method was being defined: - -<ruby> -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 -</ruby> - -At this point +app+ is the Rails app itself (a middleware), and what -happens next is Rack will call all the provided middlewares: - -<ruby> -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 -</ruby> - -Remember, +build_app+ was called (by wrapped_app) in the last line of +Server#start+. -Here's how it looked like when we left: - -<ruby> -server.run wrapped_app, options, &blk -</ruby> - -At this point, the implementation of +server.run+ will depend on the -server you're using. For example, if you were using Mongrel, here's what -the +run+ method would look like: - -<ruby> -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 -</ruby> - -We won't dig into the server configuration itself, but this is -the last piece of our journey in the Rails initialization process. - -This high level overview will help you understand when your code is -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. |