Книга: Metaprogramming Ruby 2
Назад: A Short Active Record Example
Дальше: A Lesson Learned

How Active Record Is Put Together

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’.

The Autoloading Mechanism

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.

ActiveRecord::Base

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.

The Validations Modules

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.

Назад: A Short Active Record Example
Дальше: A Lesson Learned