跳过正文

第4篇:ActiveRecord 基础架构

·1742 字·9 分钟
xieweicong
作者
xieweicong

学习目标:深入理解 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 的约定:

约定说明示例
表名类名的复数、下划线形式Userusers
主键id自动创建自增主键
外键模型名_iduser_id, post_id
时间戳created_at, updated_at自动管理
继承列typeSTI(单表继承)
# 遵循约定,无需配置
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. 本章总结
#

通过本章学习,你应该理解:

  1. ActiveRecord::Base 的模块化设计:通过 Concern 组合多个功能模块
  2. 数据库连接管理:连接池、多数据库、适配器模式
  3. 属性系统:类型转换、脏检查、动态方法
  4. Schema 和迁移:数据库结构的版本控制
  5. 继承模式:STI 单表继承、多态关联
  6. 回调生命周期:完整的回调链和执行顺序

10. 练习题
#

  1. 创建一个自定义属性类型,实现 URL 的验证和规范化
  2. 实现一个简单的连接池,理解连接复用的原理
  3. 使用 STI 创建一个商品分类系统(实体商品、数字商品)
  4. 追踪一次完整的 save 调用,观察所有回调的执行

11. 下一步
#

在下一篇 ActiveRecord 查询接口 中,我们将学习:

  • Relation 查询构建器
  • Arel:抽象关系代数
  • 查询缓存
  • N+1 问题的解决

上一篇ActiveSupport 核心扩展

下一篇ActiveRecord 查询接口

返回学习指南首页