For my Google Summer of Code project for Ruby on Rails, I’ve been trying to reorganize how Rails get initialized. My ultimate goal is to provide support for starting up multiple applications in a single Ruby process. Getting to this point, however, requires quite a few changes to the Rails codebase beforehand.
Currently, there are a bunch of configurations that are set up through ActiveRecord::Base
and are shared across Active Record. These configurations are global in a Ruby process and Rails application (since currently there is only a notion of a single application per process). Since the design of Rails originally only called for a single application, having these global configurations isn’t a really big deal. It’s only when you start trying to configure multiple applications at the same time when things become a headache.
Setting Configurations
The current state of configurations in Active Record isn’t very pretty. Let’s walk through the process of configuring the pluralize_table_names
property. First, what you’ll do is change application.rb
file:
module MyApplication
class Application < Rails::Application
config.active_record.pluralize_table_names = false
end
end
We’ve now set the Active Record configuration so that pluralize_table_names
is false
. Let’s see how this gets sent to the correct place in the Rails codebase.
The Active Record Railtie
Once we’ve set config.active_record.pluralize_table_names
, the Active Record Railtie will try to set the configurations on ActiveRecord::Base
correctly. A railtie is basically a class used to pull in separate components inside of Rails. The Active Record railtie makes sure that all of the necessary classes and modules are required when ActiveRecord
is loaded, and also that the correct initializers are set. The initializers are basically blocks of code that are run as soon as ActiveRecord
is pulled into the Rails application.
If we look at a high-level overview of the Active Record Railtie, we’ll find something like this:
module ActiveRecord
class Railtie < Rails::Railtie
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
app.config.active_record.each do |k,v|
send "#{k}=", v
end
end
end
end
end
Basically, what this is saying is that Rails should take each of the configurations that is held on app
, the application being initialized, and use attribute writers to set their values correctly. In our example, one of the key values on app.config.active_record
would have been pluralize_table_names
. This means, at some point in the loop, we would have set the following:
send "pluralize_table_names=", false
This would change the attribute on ActiveRecord::Base
, so that ActiveRecord::Base.pluralize_table_names == false
.
Running the Initializers (Addendum)
Note that the application in the initializer which is referred to as app
is the application which is being initialized. If you check out the initialize!
method on the Rails::Application
class, then you have the following definition:
def initialize!(group=:default) #:nodoc:
raise "Application has been already initialized." if @initialized
run_initializers(group, self)
@initialized = true
self
end
The initializers are run with self
, which in this context is the application being initialized. For example, I might call initialize!
when booting up my application by using MyApplicationName.initialize!
.
Discussion
So now that we better understand configurations in Active Record, what is wrong, and how can we change it?
First, let’s notice that the Active Record configurations are defined directly on ActiveRecord::Base
. That means you can access these configurations from anywhere that has Active Record loaded. For example, there are parts of the code that do this:
def self.default_fixture_model_name(fixture_set_name) # :nodoc:
ActiveRecord::Base.pluralize_table_names ?
fixture_set_name.singularize.camelize :
fixture_set_name.camelize
end
In this code, the pluralize_table_names
configuration is directly accessed by some class method which is defined on the FixtureSet
class. Theoretically, the FixtureSet
class shouldn’t know anything at all about the pluralize_table_names
configuration except through parameters that it is passed.
This can be good and bad. It’s good because it’s super easy to access a configuration. This is bad because it makes methods which use the access pattern very brittle. It is hard to change these methods if the application singleton is removed from Rails. It is also harder to reason about the code because these global configurations can be changed from anywhere.