The code in the previous example looks simple, but ActiveRecord::Base is capable of much more than that. Indeed, the more you use Active Record, the more the methods in Base seem to multiply. You might assume that Base is a huge class with thousands of lines of code that define methods such as save or validate.
Surprisingly, the source code of ActiveRecord::Base contains no trace of those methods. This is a common problem for newcomers to Rails: it’s often difficult to understand where a specific method comes from and how it gets into a class such as Base. The rest of this short chapter will look at how ActiveRecord::Base’s functionality is assembled.
Let’s start by taking a step back to the first line in our example: require ’active_record’.
Here’s the code in active_record.rb, the only Active Record file that you’re likely to require:
| require 'active_support' |
| require 'active_model' |
| # ... |
| |
| module ActiveRecord |
| extend ActiveSupport::Autoload |
| |
| autoload :Base |
| autoload :NoTouching |
| autoload :Persistence |
| autoload :QueryCache |
| autoload :Querying |
| autoload :Validations |
| # ... |
Active Record relies heavily on two other libraries that it loads straight away: Active Support and Active Model. We’ll get to Active Model soon, but one piece of Active Support is already used in this code: the ActiveSupport::Autoload module, which defines an autoload method. This method uses a naming convention to automatically find and require the source code of a module (or class) the first time you use the module’s name. Active Record extends ActiveSupport::Autoload, so autoload becomes a class method on the ActiveRecord module itself. (If you’re confused by this mechanism, look back at the Class Extension () spell.)
Active Record then uses autoload as a Class Macro () to register dozens of modules, a few of which you can see in the code above. As a result, Active Record acts like a smart Namespace () that automatically loads all the bits and pieces that make up the library. For example, when you use ActiveRecord::Base for the first time, autoload automatically requires the file active_record/base.rb, which in turn defines the class. Let’s take a look at this definition.
Here is the entire definition of ActiveRecord::Base:
| module ActiveRecord |
| class Base |
| extend ActiveModel::Naming |
| extend ActiveSupport::Benchmarkable |
| extend ActiveSupport::DescendantsTracker |
| extend ConnectionHandling |
| extend QueryCache::ClassMethods |
| extend Querying |
| extend Translation |
| extend DynamicMatchers |
| extend Explain |
| extend Enum |
| extend Delegation::DelegateCache |
| |
| include Core |
| include Persistence |
| include NoTouching |
| include ReadonlyAttributes |
| include ModelSchema |
| include Inheritance |
| include Scoping |
| include Sanitization |
| include AttributeAssignment |
| include ActiveModel::Conversion |
| include Integration |
| include Validations |
| include CounterCache |
| include Locking::Optimistic |
| include Locking::Pessimistic |
| include AttributeMethods |
| include Callbacks |
| include Timestamp |
| include Associations |
| include ActiveModel::SecurePassword |
| include AutosaveAssociation |
| include NestedAttributes |
| include Aggregations |
| include Transactions |
| include Reflection |
| include Serialization |
| include Store |
| include Core |
| end |
| |
| ActiveSupport.run_load_hooks(:active_record, Base) |
| end |
It’s not uncommon to see a class that assembles its functionality out of modules, but ActiveRecord::Base does this on a large scale. The code above does nothing but extend or include tens of modules. (Plus one additional line, the call to run_load_hooks, that allows some of those modules to run their own configuration code after they’ve been autoloaded.) As it turns out, many of the modules included by Base also include even more modules.
This is where the autoloading mechanism pays off. ActiveRecord::Base doesn’t need to require a module’s source code and then include the module. Instead, it just includes the module. Thanks to autoloading, classes such as Base can do lots of module inclusions with minimal code.
In some cases, it’s not too hard to find which module a specific method in Base comes from. For example, persistence methods such as save come from ActiveRecord::Persistence:
| module ActiveRecord |
| module Persistence |
| def save(*) # ... |
| def save!(*) # ... |
| def delete # ... |
Other method definitions are harder to find. In , you looked at validation methods such as valid? and validate. Let’s go hunting for them.
Among the other modules, ActiveRecord::Base includes a module named ActiveRecord::Validations. This module looks like a good candidate to define methods such as valid? and validate. Indeed, if you look in ActiveRecord::Validations, you’ll find the definition of valid?—but no validate:
| module ActiveRecord |
| module Validations |
| include ActiveModel::Validations |
| # ... |
| def valid?(context = nil) # ... |
Where is validate? We can look for the answer in ActiveModel::Validations, a module that ActiveRecord::Validation includes. This module comes from Active Model, a library that Active Record depends on. Sure enough, if you look into its source, you’ll find that validate is defined in ActiveModel::Validation.
A couple of puzzling details exist in this sequence of module inclusions. The first one is this: normally, a class gains instance methods by including a module. But validate is a class method on ActiveRecord::Base. How can Base gain class methods by including modules? This is the topic of the next chapter, Chapter 10, , where we’ll also look at the metaprogramming treasure trove that hides behind this assembly of modules. For now, notice that the modules in Active Record are special. You gain both instance and class methods by including them.
You might also have this question: why does ActiveRecord::Base need both ActiveRecord::Validations and ActiveModel::Validations? There is a story behind these two similarly named modules: in earlier versions of Rails there was no Active Model library, and validate was indeed defined in ActiveRecord::Validations. As Active Record kept growing, its authors realized that it was doing two separate jobs. The first job was dealing with the database operations, such as saving and loading. The second job was dealing with the object model: maintaining an object’s attributes, or tracking which of those attributes were valid.
At this point, the authors of Active Record decided to split the library in two separate libraries, and thus was Active Model born. While the database-related operations stayed in Active Record, the model-related ones moved to Active Model. In particular, the valid? method has its own reasons to dabble with the database (it cares whether an object has ever been saved to the database already)—so it stayed in ActiveRecord::Validations. On the contrary, validate has no relationship to the database, and it only cares about the object’s attributes. So it moved to ActiveModel::Validations.
We could hunt for more method definitions, but by now you can see what Active Record’s high-level design boils down to: the most important class, ActiveRecord::Base, is an assembly of modules. Each module adds instance methods (and even class methods) to the Base mix. Some modules, such as Validations, in turn include more modules, sometimes from different libraries, bringing even more methods into Base.
Before looking deeper into Active Record’s structure, let’s see what this unusual design can teach us.