Rails Booting Process v3.2.5
There are serval important gems like activesuppot, activerecord, actionpack...etc in rails. So how do rails initizlize these gems and boot? Rails::Application have an overview about the process:
== Booting process ==
1) require "config/boot.rb" to setup load paths
2) require railties and engines
3) Define Rails.application as "class MyApp::Application < Rails::Application"
4) Run config.beforeconfiguration callbacks
5) Load config/environments/ENV.rb
6) Run config.beforeinitialize callbacks
7) Run Railtie#initializer defined by railties, engines and application. One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
9) Custom Railtie#initializers added by railties, engines and applications are executed
10) Build the middleware stack and run toprepare callbacks
11) Run config.beforeeagerload and eagerload if cache classes is true
12) Run config.after_initialize callbacks
where is step 8 ?
Type rails new myapp
new a rails project, then we type rails server
in terminal to rackup the rails server.
bin/rails script
rails
script will load railties/bin/rails
script in background, railties/bin/rails
require 'rails/cli' in railties gem.
rails/cli.rb
rails/cli
firstly will require rbconfig
and rails/script_rails_loader
, and then excute Rails::ScriptRailsLoader.execscriptrails!
RbConfig #rbconfig.rb is written by mkconfig.rb, (when the Ruby was built,) which may have itself changed over the various versions. Also, on Windows, some users install a one-click installer edition, that was not built on their own computer, and they may install it in a custom path, which may invalidate some of the values in the rbconfig.rb file.
script/rails
Rails::ScriptRailsLoader.execscriptrails! check curruent path whether have 'script/rails', if so, exec 'ruby script/rails server'.
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
(1) require "config/boot.rb" to setup load paths
config/boot.rb
require 'rubygems' and setup bundler by Gemfile.
rails/commands.rb
rails/commands.rb
will take ARGV we passed to excute specify action, support 'generate', 'destroy', 'plugin', 'benchmarker', 'profiler', 'console', 'server', 'dbconsole', 'application', 'runner', 'new', '--version'... Here we pass server, codes below will be excuted:
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
}
rails/commands/server
rails/commands/server
will require 'actiondispatch', defined _Rails::Server__ which inherited from Rack::Server, when Rails::Server.new will will call Rack::Server.initialize method first, set ENV["RAILS_ENV"] and then default options for servers:
:environment=>"development",
:pid=>"/home/ysorigin/projects/iflyee/myapp/tmp/pids/server.pid",
:Port=>3000,
:Host=>"0.0.0.0",
:AccessLog=>[],
:config=>"/home/ysorigin/projects/iflyee/myapp/config.ru",
:daemonize=>false,
:debugger=>false,
:server=>nil
action_dispatch
require 'active_support'
require 'active_support/dependencies/autoload'
require 'action_pack'
# only version loaded
require 'active_model'
# active_model will require I18n, and add its locales/en file to I18n.load path
require 'rack'
After initialize server require APP_PATH set in script/rails which is 'config/application.rb', and chdir to project root and start to run.
(2) require railties and engines
config/application.rb
appllication.rb require config/boot.rb
again, and rails/all
.
##rails/all
require "rails"
%w(
active_record
action_controller
action_mailer
active_resource
rails/test_unit
sprockets
).each do |framework|
begin
require "#{framework}/railtie"
rails.rb
rails.rb load rails/rubyversioncheck, this will check ruby version for Rails 3, requires Ruby 1.8.7 or 1.9.2 above, 1.9.1 have segfaulted the test suite. and load activesupport/railtie and activedispatch/railtie.
and config/application.rb also define our Myapp::Application < Rails::Application.
Railties will load by this sort: I18n::Railtie, ActiveSupport::Railtie, ActionDispatch::Railtie, ActionView::Railtie, ActionController::Railtie, ActiveRecord::Railtie, ActionMailer::Railtie, ActiveResource::Railtie, Sprockets::Railtie, Jquery::Rails::Engine, Remotipart::Rails::Engine, Sass::Rails::Railtie, Coffee::Rails::Engine, MyApp::Application
All required railties inherited from Rails::Railtie, and its inherited
will be excuted to include Railtie::Configurable module railtie.rb # Line 131.
(3) Define Rails.application as class MyApp::Application
MyApp::Application < Rails::Application < Rails::Engine < Rails::Railtie, afterl our Rails.application defined, Rails::Application.inherited will be excuted:
Rails.application = base.instance # set Rails.application
#load classes in lib and use them during application configuration.
Rails.application.add_lib_to_load_path!
# run before_configuration callbacks
ActiveSupport.run_load_hooks(:before_configuration, base.instance)
(4) Run config.before_configuration callbacks
And then is Rails::Engine.inherited and Rails::Railtie.inherited:
# Rails::Engine.inherited
Rails.application = base.instance # set Rails.application
#load classes in lib and use them during application configuration.
Rails.application.add_lib_to_load_path!
# run before_configuration callbacks, like sass, haml have this block defined
ActiveSupport.run_load_hooks(:before_configuration, base.instance)
# Rails::Railtie.inherited
base.send(:include, Railtie::Configurable)
subclasses << base
When we use config
to add options to application, Rails::Application initialize Rails::Application::Configuration which < Rails::Engine::Configuration < Rails::Railtie::Configuration.
After this go back to rails/commands.rb, rails server run start method.Here it setup url and puts some logger to terminal and created tmp dirs for cache, pids, sessions, sockets, and then call super, which it's Rack::Server.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]
%w(cache pids sessions sockets).each do |dir_to_make|
FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
end
super
Rack::Server.start
According default options Rack::server will set varibles and require other libs, and call wrapped_app
which initizlize app --> Rack::Builder parse config.ru file into Rack Application(excute config.ru) ,and built the app --> call the default middlewares [Rails::Rack::LogTailer, Rack::ContentLength].
#######config.ru#######
require File.expand_path('../application', __FILE__)
# Initialize the rails application
Myapp::Application.initialize!
Here Myapp::Application.initialize! will excute Rails::Initializable.run_initializers(:default) call initializers define in engine.rb, application/boostrap.rb application/finisher.rb and other railties(activerecord, actioncontroller, actiondispatch...) with sorts. initializer(name, opts, &blk)
, opts set as {:before => :xxx} or {:after => :xx } to pass so that we can specify :xxx before or after some initializer to excute.
After initializers get sort, will be excuted by this sort:
* #setloadpath
Add configured load paths to ruby load paths and remove duplicates.
* #setautoloadpaths
Add load paths to ruby to load, $LOADPATH, and uniq remove duplicate paths,Set the paths from which Rails will automatically load source files, and the loadonce paths. config autoloadpaths, eagerloaded_path.
* #addroutingpaths
Load config/routes.rb app.routesreloader.routesets << routes
* #add_locales
Add config/locales to i18n #load i18n locales ##added later, but load higher priority
* #addviewpaths
ActiveSupport.on_load views path
(5) Load config/environments/ENV.rb
* #loadenvironmentconfig
@_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"),
paths will add config/environments/xxx.rb according Rails.env, as @_env. ENV is Ruby hash-like accessor for environment variables. by default it will load config/development.rb
* #loadenvironmenthook
* #loadactivesupport
require "active_support/all"
* #preload_frameworks
Preload all frameworks specified by the rails.config#frameworks.
* #initialize_logger
Initialize logger, Rails.logger || config.logger || ActiveSupport::TaggedLogging
* #initialize_cache
Set Rails.cache to ActiveSupport::Cache.new.
* #initializedependencymechanism
Sets the dependency loading mechanism.## ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
(6) Run config.before_initialize callbacks
* #bootstrap_hook
ActiveSupport.runloadhooks(:before_initialize, app)
(7) Run Railtie#initializer defined by railties, engines and application. One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
* #i18n.callbacks
add I18n.reloader(ActiveSupport::FileUpdateChecker checking locales file, it provides API for Rails to watch files and control reloading) to ActionDispatch::Reloader middleware to_prepare! callback(like before filter).By default ActionDispatch::Reloader is included in the middleware stack only in the development environment.
* #activesupport.initializewhiny_nils
require 'activesupport/whinynil', whinynil extend _NilClass__ id method, when call nil.id, it will raise RunTimeError.
* #activesupport.deprecationbehavior
Setup where active_support deprecation messages to print, default "development" => :log, "production" => :notify, "test" => :stderr
* #activesupport.initializetime_zone
Setup TimeZone with config.time_zone value.
* #action_dispatch.configure
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
# [TLD(top level domain)](http://en.wikipedia.org/wiki/Top-level_domain), default is 1
# ActionDispatch::Http::URL.extract_domain("www.google.com", tld_length=1) # google.com
# ActionDispatch::Http::URL.extract_domain("www.google.com", tle_length=2) #www.google.com
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
# if request didn't set params[:format], will check ignore_accept_header will returns the accepted MIME type for the request, which is "action_dispatch.request.formats"
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
# response default charset
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
# add more rescue error and templates for exception raise and show it then
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
# whether to set always for cookies, for development default is true
* #actionview.embedauthenticitytokeninremoteforms
ActionView::Helpers::FormTagHelper.embedauthenticitytokeninremoteforms = app.config.actionview.delete(:embedauthenticitytokeninremoteforms) #By default it's true, embeded authenticitytoken for the form
* #actionview.cacheasset_ids
ActionView::Helpers::AssetTagHelper::AssetPaths.cacheassetids = false # With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running.
* #actionview.javascriptexpansions [should be change name to javascriptandstylesheet?]
ActionView::Helpers::AssetTagHelper.register_javascript_expansion(app.config.action_view.delete(:javascript_expansions))
# Register one or more javascript files to be included when symbol
# is passed to javascript_include_tag. This method is typically intended
# to be called from plugin initialization to register javascript files
# that the plugin installed in public/javascripts.
#
# ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
#
# javascript_include_tag :monkey # =>
# <script type="text/javascript" src="/javascripts/head.js"></script>
# <script type="text/javascript" src="/javascripts/body.js"></script>
# <script type="text/javascript" src="/javascripts/tail.js"></script>
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion(app.config.action_view.delete(:stylesheet_expansions))
more or less with registerjavascriptexpansion
* #actionview.setconfigs
app.config.action_view.each {|k,v| send "#{k}=", v}, initialize config variables
* #action_view.caching
if app.config.action_view.cache_template_loading.nil?
ActionView::Resolver.caching = app.config.cache_classes
end
#cache view templates, default is true
* #action_controller.logger
Set action_controller.logger to Rails.logger
* #actioncontroller.initializeframework_caches
Set actioncontroller cache to default rails cache, initizlize in railtie 'initializecache' initializer. Default it's :filestore. check activesupport more details.
* #actioncontroller.assetsconfig
Set actioncontroller.assetsdir to 'public' dir
* #actioncontroller.setconfigs
javascripts_dir ||= paths["public/javascripts"].first
stylesheets_dir ||= paths["public/stylesheets"].first
page_cache_directory ||= paths["public"].first
ure readers methods get compiled
asset_path ||= app.config.asset_path#### #action_controller.compile_config_methods
asset_host ||= app.config.asset_host
relative_url_root ||= app.config.relative_url_root#### #active_record.initialize_timezone
#set these config to action_controller as method
* #active_record.logger
Set active_record.logger to Rails.logger
* #activerecord.identitymap
Insert ActiveRecord::IdentityMap::Middleware middleware after ActionDispatch::Callbacks middleware to config.middlewares, default it's nil,set up mannual
* #activerecord.setconfigs
Delete whitelist attributes value, and
app.config.active_record.each do |k,v|
send "#{k}=", v
end
* #activerecord.initializedatabase
Establish connection to databse through config/database.yml file
* #activerecord.logruntime
Include ActiveRecord::Railties::ControllerRuntime, logger active_record run time
* #activerecord.setreloader_hooks
According config.reloadclassesonlyonchange(default is true), set up the callback ActiveRecord::Base.clearreloadableconnections! and ActiveRecord::Base.clear_cache! to excuted by ActionDispatch prepare or cleanup callbacks.
* #activerecord.addwatchable_files
config.watchable_files.concat ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"]
* #action_mailer.logger
Set active_mailer.logger to Rails.logger
* #actionmailer.setconfigs
javascripts_dir ||= paths["public/javascripts"].first
stylesheets_dir ||= paths["public/stylesheets"].first
page_cache_directory ||= paths["public"].first
ure readers methods get compiled
asset_path ||= app.config.asset_path#### #action_controller.compile_config_methods
asset_host ||= app.config.asset_host
relative_url_root ||= app.config.relative_url_root#### #active_record.initialize_timezone
# set these config to action_mailer as method
register_interceptors(options.delete(:interceptors))
# Register one or more Interceptors which will be called before mail is sent.
register_observers(options.delete(:observers))
# Register one or more Observers which will be notified when mail is delivered.
* #actionmailer.compileconfig_methods
config.compile_methods! if config.respond_to?(:compile_methods!), for now active_support configuration responsed.
* #activeresource.setconfigs
app.config.active_resource.each do |k,v|
ActiveResource::Base.send "#{k}=", v
end
* #sprockets.environment
setup sprockets environment, logger, cache, load manifest.yml and load helpers.
* #setup_sass
Override stylesheet engine to the preferred syntax
Set the sass cache location
Establish configuration defaults that are evironmental in nature
* #setup_compression
app.config.sass.style = :compressed
app.config.assets.css_compressor = CssCompressor.new
* #appendassetspath
app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
* #prependhelperspath
app.config.helpers_paths.unshift(*paths["app/helpers"].existent)
(9) Custom Railtie#initializers added by railties, engines and applications are executed
* #loadconfiginitializers
Load all config/initializers, including other gems, plugins.
* #addgeneratortemplates
* #ensureautoloadoncepathsas_subset
* #addbuiltinroute
For development ##match '/rails/info/properties' => "rails/info#properties"
(10) Build the middleware stack and run to_prepare callbacks
* #buildmiddlewarestack
Include default middlewares
* #definemainapp_helper
app.routes.definemountedhelper(:main_app)
* #addtoprepare_blocks
ActionDispatch::Reloader.to_prepare(blk)
* #runpreparecallbacks
ActionDispatch::Reloader.prepare!
(11) Run config.beforeeagerload and eager_load if cache classes is true
* #eager_load!
ActiveSupport.runloadhooks(:beforeeagerload, self) railties.all(&:eagerload!), excute eagerload! for all railties
(12) Run config.after_initialize callbacks
* #finisher_hook
ActiveSupport.runloadhooks(:after_initialize, self)
* #setroutesreloader_hook
Set app reload just after the finisher hook to ensure routes added in the hook are still loaded.
* #setcleardependencies_hook
Set app reload just after the finisher hook to ensure paths added in the hook are still loaded.
* #disabledependencyloading
ActiveSupport::Dependencies.unhook!
'
Finally Rack::Server pass the app to handler(webrick, thin or others) to receive request from browsers.Rails Booting done!