学习目标:深入理解 ActiveRecord 的核心设计、数据库连接管理和属性系统
引言#
ActiveRecord 是 Rails 中最复杂也最核心的组件之一。它实现了 Martin Fowler 提出的 Active Record 模式,让数据库表的每一行都对应一个对象。本章我们将深入探讨 ActiveRecord 的基础架构。
1. ActiveRecord 的设计哲学#
1.1 Active Record 模式#
Active Record 模式的核心思想:
对象 = 数据 + 行为
一个对象实例 = 数据库表的一行
对象的属性 = 表的列
对象的方法 = 对数据的操作
示例:
user = User.new(name: "John", email: "john@example.com")
user.save # 保存到数据库
# 等价于 SQL:
# INSERT INTO users (name, email) VALUES ('John', 'john@example.com')
1.2 约定优于配置#
ActiveRecord 的约定:
约定 | 说明 | 示例 |
---|---|---|
表名 | 类名的复数、下划线形式 | User → users |
主键 | id | 自动创建自增主键 |
外键 | 模型名_id | user_id , post_id |
时间戳 | created_at , updated_at | 自动管理 |
继承列 | type | STI(单表继承) |
# 遵循约定,无需配置
class User < ApplicationRecord
# 自动映射到 users 表
# 自动有 id, created_at, updated_at
end
# 打破约定时,需要显式配置
class User < ApplicationRecord
self.table_name = "customers"
self.primary_key = "customer_id"
end
2. ActiveRecord::Base 的组成#
2.1 模块化设计#
ActiveRecord::Base
不是一个庞大的类,而是通过 Concern 组合了多个模块:
# activerecord/lib/active_record/base.rb (简化版)
module ActiveRecord
class Base
extend ActiveModel::Naming
extend ActiveSupport::Benchmarkable
extend ActiveSupport::DescendantsTracker
include Core # 核心功能
include Persistence # 持久化(保存、更新、删除)
include ReadonlyAttributes # 只读属性
include ModelSchema # 模型定义
include Inheritance # 继承
include Scoping # 作用域
include Sanitization # SQL 清理
include AttributeAssignment # 属性赋值
include ActiveModel::Conversion # 转换
include Integration # URL 辅助方法
include Validations # 验证
include CounterCache # 计数缓存
include Associations # 关联
include Timestamp # 时间戳
include Callbacks # 回调
include AttributeMethods # 属性方法
include Locking::Optimistic # 乐观锁
include Locking::Pessimistic # 悲观锁
include Transactions # 事务
include NestedAttributes # 嵌套属性
include Store # 存储
include SecurePassword # 密码加密
# ... 还有更多模块
end
end
设计优势:
- 职责分离,每个模块负责一个功能
- 易于测试和维护
- 可以选择性地使用某些功能
2.2 核心模块详解#
Core 模块#
# activerecord/lib/active_record/core.rb
module ActiveRecord
module Core
extend ActiveSupport::Concern
included do
# 类配置
mattr_accessor :logger
mattr_accessor :default_timezone
# 类变量
class_attribute :default_connection_handler
class_attribute :default_role
class_attribute :default_shard
end
module ClassMethods
# 查找记录
def find(*ids)
# 实现查找逻辑
end
# 初始化
def initialize_find_by_cache
@find_by_statement_cache = {}
end
end
# 初始化实例
def initialize(attributes = nil)
@attributes = self.class._default_attributes.deep_dup
# ...
end
end
end
核心职责:
- 定义类和实例的基础方法
- 初始化对象
- 实现
find
等基础查询方法
Persistence 模块#
# activerecord/lib/active_record/persistence.rb
module ActiveRecord
module Persistence
# 保存记录
def save(**options)
create_or_update(**options)
rescue ActiveRecord::RecordInvalid
false
end
# 保存或抛出异常
def save!(**options)
create_or_update(**options) || raise(RecordNotSaved.new("Failed to save", self))
end
# 更新属性
def update(attributes)
assign_attributes(attributes)
save
end
# 删除记录
def destroy
_raise_readonly_record_error if readonly?
destroy_associations
@_trigger_destroy_callback = if persisted?
destroy_row > 0
else
true
end
@destroyed = true
freeze
end
private
def create_or_update(**options)
_raise_readonly_record_error if readonly?
return false if destroyed?
result = new_record? ? _create_record : _update_record
result != false
end
end
end
持久化方法:
save
/save!
- 保存新记录或更新现有记录create
- 创建并保存update
- 更新属性destroy
- 删除记录
3. 数据库连接管理#
3.1 连接池(Connection Pool)#
ActiveRecord 使用连接池管理数据库连接:
# activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
module ActiveRecord
module ConnectionAdapters
class ConnectionPool
attr_reader :size, :automatic_reconnect
def initialize(pool_config)
@size = pool_config.pool_size # 默认 5
@connections = []
@available = ConnectionLeasingQueue.new
end
# 获取连接
def connection
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
end
# 借出连接
def checkout(checkout_timeout = @checkout_timeout)
checkout_and_verify(acquire_connection(checkout_timeout))
end
# 归还连接
def checkin(conn)
conn.lock.synchronize do
@available.add conn
end
end
end
end
end
连接池配置:
# config/database.yml
production:
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
工作原理:
1. 请求到达
↓
2. 从连接池借出连接
↓
3. 执行查询
↓
4. 归还连接到连接池
↓
5. 连接可被其他请求复用
3.2 多数据库支持#
Rails 6+ 支持多数据库:
# config/database.yml
production:
primary:
adapter: postgresql
database: app_production
host: primary.db
analytics:
adapter: postgresql
database: analytics_production
host: analytics.db
replica: true
使用多数据库:
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary_replica }
end
# app/models/analytics_record.rb
class AnalyticsRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :analytics, reading: :analytics_replica }
end
# 使用
class User < ApplicationRecord
end
class Event < AnalyticsRecord
end
# 手动切换连接
ActiveRecord::Base.connected_to(role: :reading) do
User.first # 从读副本读取
end
3.3 连接适配器(Adapters)#
ActiveRecord 支持多种数据库通过适配器:
# activerecord/lib/active_record/connection_adapters/
├── abstract_adapter.rb # 抽象适配器基类
├── postgresql_adapter.rb # PostgreSQL
├── mysql2_adapter.rb # MySQL
├── sqlite3_adapter.rb # SQLite
└── trilogy_adapter.rb # Trilogy (新的 MySQL 客户端)
适配器的职责:
- 建立数据库连接
- 执行 SQL 查询
- 处理数据类型转换
- 实现特定数据库的功能
适配器模式示例:
# activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
def execute(sql, name = nil)
raise NotImplementedError
end
def select_all(sql, name = nil, binds = [])
raise NotImplementedError
end
def insert(sql, name = nil, pk = nil, id_value = nil)
raise NotImplementedError
end
end
end
end
# activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
class PostgreSQLAdapter < AbstractAdapter
def execute(sql, name = nil)
log(sql, name) do
@connection.exec(sql)
end
end
end
4. 属性系统(Attributes)#
4.1 属性的定义#
ActiveRecord 的属性系统非常强大:
class User < ApplicationRecord
# 从数据库表自动推断属性
# 如果表有 name, email 列,自动有这些属性
# 自定义属性
attribute :admin, :boolean, default: false
attribute :settings, :json, default: {}
attribute :created_date, :date
# 虚拟属性(不对应数据库列)
attribute :full_name, :string
def full_name
"#{first_name} #{last_name}"
end
end
4.2 属性类型系统#
ActiveRecord 提供了丰富的类型系统:
# activerecord/lib/active_record/type.rb
module ActiveRecord
module Type
class Boolean < Value
def cast(value)
# 将值转换为布尔值
end
end
class Integer < Value
def cast(value)
# 将值转换为整数
end
end
class String < ImmutableString
def cast(value)
# 将值转换为字符串
end
end
# 还有:DateTime, Date, Decimal, Float, Json, Text 等
end
end
类型转换示例:
user = User.new(age: "25")
user.age # => 25 (自动转换为整数)
user.age.class # => Integer
user.active = "true"
user.active # => true (转换为布尔值)
user.settings = '{"theme": "dark"}'
user.settings # => {"theme" => "dark"} (解析 JSON)
4.3 自定义属性类型#
# app/models/types/money_type.rb
class MoneyType < ActiveRecord::Type::Value
def cast(value)
case value
when String
value.gsub(/[,$]/, '').to_f
when Numeric
value.to_f
else
value
end
end
def serialize(value)
cast(value)
end
end
# 注册类型
ActiveRecord::Type.register(:money, MoneyType)
# 使用
class Product < ApplicationRecord
attribute :price, :money
end
product = Product.new(price: "$19.99")
product.price # => 19.99
4.4 属性方法(Attribute Methods)#
ActiveRecord 动态生成大量属性方法:
# 对于每个属性 name,生成:
user.name # getter
user.name = "John" # setter
user.name? # 查询方法
user.name_was # 修改前的值
user.name_changed? # 是否改变
user.name_change # => ["old", "new"]
user.name_will_change! # 标记将要改变
user.reset_name! # 重置为原值
# 源码位置:
# activerecord/lib/active_record/attribute_methods.rb
module ActiveRecord
module AttributeMethods
extend ActiveSupport::Concern
included do
# 定义属性方法
attribute_method_suffix "", "=", "?", "_before_type_cast", "_came_from_user?"
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
attribute_method_suffix "_previously_changed?", "_previous_change"
attribute_method_prefix "reset_", "restore_"
end
end
end
4.5 脏检查(Dirty Tracking)#
跟踪对象的修改:
user = User.find(1)
user.name = "Jane"
user.changed? # => true
user.changed # => ["name"]
user.changes # => {"name" => ["John", "Jane"]}
user.name_changed? # => true
user.name_was # => "John"
user.name_change # => ["John", "Jane"]
# 保存后
user.save
user.changed? # => false
user.previous_changes # => {"name" => ["John", "Jane"]}
实现原理:
# activerecord/lib/active_record/attribute_methods/dirty.rb
module ActiveRecord
module AttributeMethods
module Dirty
def write_attribute(attr_name, value)
# 保存原始值
set_attribute_was(attr_name, _read_attribute(attr_name))
super
end
def changes
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr|
[attr, attribute_change(attr)]
}]
end
end
end
end
5. Schema 定义与迁移#
5.1 Schema 定义#
ActiveRecord 通过迁移定义数据库结构:
# db/migrate/20240101000000_create_users.rb
class CreateUsers < ActiveRecord::Migration[8.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: { unique: true }
t.integer :age
t.boolean :admin, default: false
t.json :settings, default: {}
t.timestamps
end
end
end
迁移方法:
# 创建表
create_table :products do |t|
t.string :name
t.decimal :price, precision: 8, scale: 2
t.references :category, foreign_key: true
t.timestamps
end
# 修改表
change_table :products do |t|
t.remove :description
t.string :sku
t.rename :name, :title
end
# 添加列
add_column :products, :stock, :integer, default: 0
# 添加索引
add_index :products, :sku, unique: true
add_index :products, [:category_id, :created_at]
# 添加外键
add_foreign_key :products, :categories
5.2 迁移的执行#
# 运行迁移
rails db:migrate
# 回滚
rails db:rollback
# 回滚多步
rails db:rollback STEP=3
# 重置数据库
rails db:reset
# 删除并重建
rails db:drop db:create db:migrate
迁移状态跟踪:
# Rails 使用 schema_migrations 表跟踪已执行的迁移
# db/schema.rb 记录当前数据库结构
ActiveRecord::Schema[8.0].define(version: 2024_01_01_000000) do
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
5.3 模型定义与表结构#
查询表结构:
# 列信息
User.columns
# => [#<ActiveRecord::ConnectionAdapters::Column:0x...
# @name="id", @sql_type="integer", ...>, ...]
User.column_names
# => ["id", "name", "email", "created_at", "updated_at"]
User.columns_hash["name"]
# => #<ActiveRecord::ConnectionAdapters::Column @name="name" @type=:string>
# 主键
User.primary_key # => "id"
# 表名
User.table_name # => "users"
6. 继承#
6.1 单表继承(STI)#
# 只需一个 type 列
class CreateVehicles < ActiveRecord::Migration[8.0]
def change
create_table :vehicles do |t|
t.string :type # STI 列
t.string :color
t.integer :doors # 只对 Car 有用
t.integer :wheels # 只对 Bike 有用
t.timestamps
end
end
end
# 模型定义
class Vehicle < ApplicationRecord
end
class Car < Vehicle
end
class Bike < Vehicle
end
# 使用
car = Car.create(color: 'red', doors: 4)
bike = Bike.create(color: 'blue', wheels: 2)
Vehicle.all
# => [#<Car id: 1, type: "Car", color: "red", doors: 4>,
# #<Bike id: 2, type: "Bike", color: "blue", wheels: 2>]
Car.all
# => [#<Car id: 1, type: "Car", color: "red", doors: 4>]
STI 的优缺点:
✅ 优点:
- 简单,只需一个表
- 查询方便
- 关联简单
❌ 缺点:
- 表可能有很多空列
- 所有子类共享同一张表
6.2 多态关联(Polymorphic)#
# 一个评论可以属于多种类型
class CreateComments < ActiveRecord::Migration[8.0]
def change
create_table :comments do |t|
t.text :content
t.references :commentable, polymorphic: true
t.timestamps
end
end
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Photo < ApplicationRecord
has_many :comments, as: :commentable
end
# 使用
post = Post.first
post.comments.create(content: "Great post!")
comment = Comment.first
comment.commentable # => #<Post id: 1>
comment.commentable_type # => "Post"
comment.commentable_id # => 1
7. 回调生命周期#
7.1 完整的回调链#
class User < ApplicationRecord
# 验证回调
before_validation :normalize_email
after_validation :log_validation
# 保存回调(create 和 update 都会触发)
before_save :encrypt_password
around_save :benchmark_save
after_save :clear_cache
# 创建回调
before_create :set_default_role
around_create :log_creation
after_create :send_welcome_email
# 更新回调
before_update :check_permissions
after_update :notify_changes
# 删除回调
before_destroy :cleanup_associations
around_destroy :log_destruction
after_destroy :remove_files
# 提交回调(事务提交后)
after_commit :index_search, on: :create
after_rollback :log_rollback
end
回调执行顺序:
创建记录:
before_validation
after_validation
before_save
before_create
[SQL INSERT]
after_create
after_save
after_commit
更新记录:
before_validation
after_validation
before_save
before_update
[SQL UPDATE]
after_update
after_save
after_commit
删除记录:
before_destroy
[SQL DELETE]
after_destroy
after_commit
7.2 跳过回调#
# 跳过回调的方法(谨慎使用!)
user.save(validate: false) # 跳过验证
user.update_column(:name, "John") # 跳过回调和验证
user.update_columns(name: "John") # 批量更新,跳过回调
user.delete # 跳过回调的删除(vs destroy)
User.update_all(active: true) # 批量更新,跳过回调
8. 实战示例#
8.1 完整的模型示例#
# app/models/user.rb
class User < ApplicationRecord
# 关联
has_many :posts, dependent: :destroy
has_one :profile, dependent: :destroy
belongs_to :company, optional: true
# 验证
validates :email, presence: true, uniqueness: true
validates :name, presence: true, length: { minimum: 2 }
validates :age, numericality: { greater_than: 0 }, allow_nil: true
# 回调
before_save :normalize_email
after_create :send_welcome_email
# 作用域
scope :active, -> { where(active: true) }
scope :adults, -> { where('age >= ?', 18) }
# 枚举
enum :role, { user: 0, moderator: 1, admin: 2 }
# 自定义属性
attribute :settings, :json, default: {}
# 密码加密
has_secure_password
# 类方法
def self.find_by_credentials(email, password)
user = find_by(email: email)
user&.authenticate(password)
end
# 实例方法
def full_name
"#{first_name} #{last_name}"
end
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
8.2 调试技巧#
# 查看生成的 SQL
User.where(active: true).to_sql
# => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 1"
# 查看执行计划
User.where(active: true).explain
# 查看属性变化
user.changes
user.previous_changes
# 查看回调
User._create_callbacks.map(&:filter)
# 查看验证
User.validators
User.validators_on(:email)
9. 本章总结#
通过本章学习,你应该理解:
- ActiveRecord::Base 的模块化设计:通过 Concern 组合多个功能模块
- 数据库连接管理:连接池、多数据库、适配器模式
- 属性系统:类型转换、脏检查、动态方法
- Schema 和迁移:数据库结构的版本控制
- 继承模式:STI 单表继承、多态关联
- 回调生命周期:完整的回调链和执行顺序
10. 练习题#
- 创建一个自定义属性类型,实现 URL 的验证和规范化
- 实现一个简单的连接池,理解连接复用的原理
- 使用 STI 创建一个商品分类系统(实体商品、数字商品)
- 追踪一次完整的
save
调用,观察所有回调的执行
11. 下一步#
在下一篇 ActiveRecord 查询接口 中,我们将学习:
- Relation 查询构建器
- Arel:抽象关系代数
- 查询缓存
- N+1 问题的解决
返回:学习指南首页