Active Record 是 MVC 中的 M(模型),负责处理数据和业务逻辑。Active Record 负责创建和使用需要持久存入数据库中的数据。Active Record 实现了 Active Record 模式,是一种对象关系映射系统。
Active Record 模式
在 Active Record 模式中,对象中既有持久存储的数据,也有针对数据的操作。Active Record 模式把数据存取逻辑作为对象的一部分,处理对象的用户知道如何把数据写入数据库,还知道如何从数据库中读出数据。'
对象关系映射
对象关系映射(ORM)是一种技术手段,把应用中的对象和关系型数据库中的数据表连接起来。使用 ORM,应用中对象的属性和对象之间的关系可以通过一种简单的方法从数据库中获取,无需直接编写 SQL 语句,也不过度依赖特定的数据库种类。
用作 ORM 框架的 Active Record
- 表示模型和其中的数据;
- 表示模型之间的关系;
- 通过相关联的模型表示继承层次结构;
- 持久存入数据库之前,验证模型;
- 以面向对象的方式处理数据库操作。
Active Record 中的“多约定少配置”原则
使用其他编程语言或框架开发应用时,可能必须要编写很多配置代码。大多数 ORM 框架都是这样。但是,如果遵循 Rails 的约定,创建 Active Record 模型时不用做多少配置(有时甚至完全不用配置)。Rails 的理念是,如果大多数情况下都要使用相同的方式配置应用,那么就应该把这定为默认的方式。所以,只有约定无法满足要求时,才要额外配置。
命名约定
- 数据库表名:复数,下划线分隔单词(例如
book_clubs
) - 模型类名:单数,每个单词的首字母大写(例如
BookClub
)
模式约定
根据字段的作用不同,Active Record 对数据库表中的字段命名也做了相应的约定:
- 外键:使用
singularized_table_name_id
形式命名,例如item_id
,order_id
。创建模型关联后,Active Record 会查找这个字段; - 主键:默认情况下,Active Record 使用整数字段
id
作为表的主键。使用 Active Record 迁移创建数据库表时,会自动创建这个字段;
还有一些可选的字段,能为 Active Record 实例添加更多的功能:
created_at
:创建记录时,自动设为当前的日期和时间;updated_at
:更新记录时,自动设为当前的日期和时间;lock_version
:在模型中添加乐观锁;type
:让模型使用单表继承;(association_name)_type
:存储多态关联的类型;(table_name)_count
:缓存所关联对象的数量。比如说,一个Article
有多个Comment
,那么comments_count
列存储各篇文章现有的评论数量;
然这些字段是可选的,但在 Active Record 中是被保留的。如果想使用相应的功能,就不要把这些保留字段用作其他用途。例如,type
这个保留字段是用来指定数据库表使用单表继承(Single Table Inheritance,STI)的。如果不用单表继承,请使用其他的名称,例如“context”,这也能表明数据的作用。
创建 Active Record 模型
创建 Active Record 模型的过程很简单,只要继承 ApplicationRecord
类就行了:
|
上面的代码会创建 Product
模型,对应于数据库中的 products
表。同时,products
表中的字段也映射到 Product
模型实例的属性上。
假如 products
表由下面的 SQL 语句创建:
|
按照这样的数据表结构,可以编写下面的代码:
|
创建
调用 create
方法会创建一个新记录,并将其存入数据库:
|
new
方法实例化一个新对象,但不保存:
|
调用 user.save
可以把记录存入数据库。
最后,如果在 create
和 new
方法中使用块,会把新创建的对象拉入块中,初始化对象:
|
Active Record 数据验证:
数据验证概览
下面是一个非常简单的数据验证:
|
可以看出,如果 Person
没有 name
属性,验证就会将其视为无效对象。第二个 Person
对象不会存入数据库。
为什么要做数据验证?
数据验证确保只有有效的数据才能存入数据库。例如,应用可能需要用户提供一个有效的电子邮件地址和邮寄地址。在模型中做验证是最有保障的,只有通过验证的数据才能存入数据库。数据验证和使用的数据库种类无关,终端用户也无法跳过,而且容易测试和维护。在 Rails 中做数据验证很简单,Rails 内置了很多辅助方法,能满足常规的需求,而且还可以编写自定义的验证方法。
在数据存入数据库之前,也有几种验证数据的方法,包括数据库原生的约束、客户端验证和控制器层验证。下面列出这几种验证方法的优缺点:
- 数据库约束和存储过程无法兼容多种数据库,而且难以测试和维护。然而,如果其他应用也要使用这个数据库,最好在数据库层做些约束。此外,数据库层的某些验证(例如在使用量很高的表中做唯一性验证)通过其他方式实现起来有点困难。
- 客户端验证很有用,但单独使用时可靠性不高。如果使用 JavaScript 实现,用户在浏览器中禁用 JavaScript 后很容易跳过验证。然而,客户端验证和其他验证方式相结合,可以为用户提供实时反馈。
- 控制器层验证很诱人,但一般都不灵便,难以测试和维护。只要可能,就要保证控制器的代码简洁,这样才有利于长远发展。
你可以根据实际需求选择使用合适的验证方式。Rails 团队认为,模型层数据验证最具普适性
数据在何时验证?
Active Record 对象分为两种:一种在数据库中有对应的记录,一种没有。新建的对象(例如,使用 new
方法)还不属于数据库。在对象上调用 save
方法后,才会把对象存入相应的数据库表。Active Record 使用实例方法 new_record?
判断对象是否已经存入数据库。假如有下面这个简单的 Active Record 类:
|
我们可以在 rails console
中看一下到底怎么回事:
|
新建并保存记录会在数据库中执行 SQL INSERT
操作。更新现有的记录会在数据库中执行 SQL UPDATE
操作。一般情况下,数据验证发生在这些 SQL 操作执行之前。如果验证失败,对象会被标记为无效,Active Record 不会向数据库发送 INSERT
或 UPDATE
指令。这样就可以避免把无效的数据存入数据库。你可以选择在对象创建、保存或更新时执行特定的数据验证。
注意:修改数据库中对象的状态有多种方式。有些方法会触发数据验证,有些则不会。所以,如果不小心处理,还是有可能把无效的数据存入数据库。
下列方法会触发数据验证,如果验证失败就不把对象存入数据库:
create
create!
save
save!
update
update!
爆炸方法(例如 save!
)会在验证失败后抛出异常。验证失败后,非爆炸方法不会抛出异常,save
和 update
返回 false
,create
返回对象本身。
跳过验证
下列方法会跳过验证,不管验证是否通过都会把对象存入数据库,使用时要特别留意。
decrement!
decrement_counter
increment!
increment_counter
toggle!
touch
update_all
update_attribute
update_column
update_columns
update_counters
注意,使用 save
时如果传入 validate: false
参数,也会跳过验证。使用时要特别留意。
save(validate: false)
valid?
和 invalid?
Rails 在保存 Active Record 对象之前验证数据。如果验证过程产生错误,Rails 不会保存对象。
你还可以自己执行数据验证。valid?
方法会触发数据验证,如果对象上没有错误,返回 true
,否则返回 false
。前面我们已经用过了:
|
Active Record 执行验证后,所有发现的错误都可以通过实例方法 errors.messages
获取。该方法返回一个错误集合。如果数据验证后,这个集合为空,说明对象是有效的。
注意,使用 new
方法初始化对象时,即使无效也不会报错,因为只有保存对象时才会验证数据,例如调用 create
或 save
方法。