Rails Server Startup Process
I was trying to print out migration version information from the schema_info table when the server starts up. Thought it would be simple - just put some code at the beginning of environment.rb after the boot statement.
require File.join(File.dirname(__FILE__), 'boot')
puts ActiveRecord::Base.connection.execute('select * from schema_info').fetch_hash['version']
Rails::Initializer.run do |config|
# ...
end
But when I start the server, I get this error:
in "load_missing_constant": uninitialized constant ActiveRecord (NameError)
The application doesn't know what ActiveRecord is! So I decided to dig deeper and understand what happens when Rails server starts up.
How Rails Boots Up
Apache and Dispatcher
When Apache gets a request, it looks for .htaccess file in the public directory. This file's job is to call the dispatcher, which handles all the Ruby/Rails code processing.
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
environment.rb
First two lines in environment.rb:
RAILS_GEM_VERSION = '1.2.5' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')
boot.rb - The Foundation
boot.rb does these important things:
- Sets up RAILS_ROOT
- Loads the Rails version mentioned as RAILS_GEM_VERSION (or default if not specified)
- Adds to LOAD_PATH by calling
Rails::Initializer.run(:set_load_path)
LOAD_PATH is the list of directories where Ruby searches for classes and modules. At this point, nothing is actually loaded yet - we just have the paths set up.
Rails::Initializer.run
Next comes the main part:
Rails::Initializer.run do |config|
# configuration stuff
end
The Configuration class holds all parameters for the Initializer. When no argument is passed, it defaults to calling the :process method.
def self.run(command = :process, configuration = Configuration.new)
yield configuration if block_given?
initializer = new configuration
initializer.send(command)
initializer
end
So when we have the config block, we're just modifying the configuration object. The real action happens later in the process method.
The Process Method - Where Magic Happens
Here's what happens step by step:
1. set_load_path
Already called in boot.rb, but gets called again (not sure why).
2. set_connection_adapters
By default Rails loads all connection adapters. You can configure it to load only what you need:
config.connection_adapters = ['mysql']
3. require_frameworks
Rails loads all frameworks by default. You can specify only what you need:
config.frameworks = [:active_record, :action_controller, :action_view, :action_mailer]
4. load_environment
Loads the environment-specific file (development.rb, test.rb, or production.rb). The main code:
eval(IO.read(configuration.environment_path), binding, configuration.environment_path)
5. initialize_database - The Important One!
This is where database configuration gets set and connection is established:
ActiveRecord::Base.configurations = configuration.database_configuration
ActiveRecord::Base.establish_connection
Now I understand my problem! Before this step, any database operation will fail because ActiveRecord::Base.configurations isn't even populated with values from database.yml.
6. initialize_logger
Creates a new logger instance.
7. initialize_framework_logging
Sets the logger in all frameworks so we can just use logger.info("...") in controllers without creating logger instances.
8. initialize_dependency_mechanism
This controls how classes are loaded. Here's the key:
Dependencies.mechanism = configuration.cache_classes ? :require : :load
- Development:
cache_classes = false→ usesload(auto-reloads) - Production:
cache_classes = true→ usesrequire(caches classes)
9. load_plugins
This is where plugins get loaded. All init.rb files under vendor/plugins/* are evaluated.
find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path }
Plugins are loaded only once, which is why you need to restart the server when changing plugin code.
10. initialize_routing
Loads routing definitions:
ActionController::Routing.controller_paths = configuration.controller_paths
ActionController::Routing::Routes.reload
11. after_initialize
Provides a hook for final configuration after everything is loaded:
config.after_initialize do
# custom configuration here
end
config.to_prepare
This is special - it's called before every request in development mode and before the first request in production mode. Good place for deployment checks.
What I Learned
My original problem was trying to access ActiveRecord before the database was initialized. The solution is to put such code in the after_initialize block or in the environment-specific files.
Rails 2.0 is coming soon with new features, but the booting process should remain similar. Understanding this process helps a lot in debugging startup issues!