Bratish Goswami

Personal website and blog

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:

  1. Sets up RAILS_ROOT
  2. Loads the Rails version mentioned as RAILS_GEM_VERSION (or default if not specified)
  3. 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 → uses load (auto-reloads)
  • Production: cache_classes = true → uses require (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!