Active Record

321 阅读9分钟
  • 领域模型模式

Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。 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 类就行了:

class Product < ApplicationRecord end 上面的代码会创建 Product 模型,对应于数据库中的 products 表。同时,products 表中的字段也映射到 Product 模型实例的属性上。

假如 products 表由下面的 SQL 语句创建:

CREATE TABLE products ( id int(11) NOT NULL auto_increment, name varchar(255), PRIMARY KEY (id) ); 按照这样的数据表结构,可以编写下面的代码:

p = Product.new p.name = "Some Book" puts p.name # "Some Book" 创建

调用 create 方法会创建一个新记录,并将其存入数据库:

user = User.create(name: "David", occupation: "Code Artist") new 方法实例化一个新对象,但不保存:

user = User.new user.name = "David" user.occupation = "Code Artist" 调用 user.save 可以把记录存入数据库。

最后,如果在 create 和 new 方法中使用块,会把新创建的对象拉入块中,初始化对象:

user = User.new do |u| u.name = "David" u.occupation = "Code Artist" end

Active Record 数据验证:

数据验证概览

下面是一个非常简单的数据验证:

class Person < ApplicationRecord validates :name, presence: true end

Person.create(name: "John Doe").valid? # => true Person.create(name: nil).valid? # => false 可以看出,如果 Person 没有 name 属性,验证就会将其视为无效对象。第二个 Person 对象不会存入数据库。

为什么要做数据验证?

数据验证确保只有有效的数据才能存入数据库。例如,应用可能需要用户提供一个有效的电子邮件地址和邮寄地址。在模型中做验证是最有保障的,只有通过验证的数据才能存入数据库。数据验证和使用的数据库种类无关,终端用户也无法跳过,而且容易测试和维护。在 Rails 中做数据验证很简单,Rails 内置了很多辅助方法,能满足常规的需求,而且还可以编写自定义的验证方法。

在数据存入数据库之前,也有几种验证数据的方法,包括数据库原生的约束、客户端验证和控制器层验证。下面列出这几种验证方法的优缺点:

数据库约束和存储过程无法兼容多种数据库,而且难以测试和维护。然而,如果其他应用也要使用这个数据库,最好在数据库层做些约束。此外,数据库层的某些验证(例如在使用量很高的表中做唯一性验证)通过其他方式实现起来有点困难。 客户端验证很有用,但单独使用时可靠性不高。如果使用 JavaScript 实现,用户在浏览器中禁用 JavaScript 后很容易跳过验证。然而,客户端验证和其他验证方式相结合,可以为用户提供实时反馈。 控制器层验证很诱人,但一般都不灵便,难以测试和维护。只要可能,就要保证控制器的代码简洁,这样才有利于长远发展。 你可以根据实际需求选择使用合适的验证方式。Rails 团队认为,模型层数据验证最具普适性

数据在何时验证?

Active Record 对象分为两种:一种在数据库中有对应的记录,一种没有。新建的对象(例如,使用 new 方法)还不属于数据库。在对象上调用 save 方法后,才会把对象存入相应的数据库表。Active Record 使用实例方法 new_record? 判断对象是否已经存入数据库。假如有下面这个简单的 Active Record 类:

class Person < ApplicationRecord end

我们可以在 rails console 中看一下到底怎么回事:

$ bin/rails console

p = Person.new(name: "John Doe") => #<Person id: nil, name: "John Doe", created_at: nil, updated_数据验证:at: nil> p.new_record? => true p.save => true p.new_record? => false

新建并保存记录会在数据库中执行 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。前面我们已经用过了:

class Person < ApplicationRecord validates :name, presence: true end

Person.create(name: "John Doe").valid? # => true Person.create(name: nil).valid? # => false Active Record 执行验证后,所有发现的错误都可以通过实例方法 errors.messages 获取。该方法返回一个错误集合。如果数据验证后,这个集合为空,说明对象是有效的。

注意,使用 new 方法初始化对象时,即使无效也不会报错,因为只有保存对象时才会验证数据,例如调用 create 或 save 方法。

如果Active Record使用到了两个表的数据,则可能会在其中一个表中通过外键的形式引入另一个表的数据。我们一般用引入对象表的表名+下划线+“id”作为外键的列名来使用。

当使用rails generate migration命令创建表格的时候,表中会自动生成一个名为id的列,这一列就是该表的主键。

下面我们对生成的表中的一些常见列进行说明:

created_at - 表示该记录被生成的时间。

updated_at - 表示该记录被更新的时间。

lock_version - 对该模型添加的一个乐观锁。

type - 形容该模型单表继承。

(association_name)_type - 保存多态关联的类型。

CRUD:读写数据

CURD 是四种数据操作的简称:C 表示创建,R 表示读取,U 表示更新,D 表示删除。Active Record 自动创建了处理数据表中数据的方法。

创建

Active Record 对象可以使用 Hash 创建,在块中创建,或者创建后手动设置属性。new 方法会实例化一个对象,create 方法实例化一个对象,并将其存入数据库。

例如,User 模型中有两个属性,nameoccupation。调用 create 方法会实例化一个对象,并把该对象对应的记录存入数据库:

user = User.create(name: "David", occupation: "Code Artist")

使用 new 方法,可以实例化一个对象,但不会保存:

user = User.new
user.name = "David"
user.occupation = "Code Artist"

调用 user.save 可以把记录存入数据库。

createnew 方法从结果来看,都实现了下面代码的功能:

user = User.new do |u|
  u.name = "David"
  u.occupation = "Code Artist"
end

读取

Active Record 为读取数据库中的数据提供了丰富的 API。下面举例说明。

# return a collection with all users
users = User.all
# return the first user
user = User.first
# return the first user named David
david = User.find_by(name: 'David')
# find all users named David who are Code Artists and sort by created_at
# in reverse chronological order
users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC')

更新

得到 Active Record 对象后,可以修改其属性,然后再存入数据库。

user = User.find_by(name: 'David')
user.name = 'Dave'
user.save

还有个简写方式,使用 Hash,指定属性名和属性值,例如:

user = User.find_by(name: 'David')
user.update(name: 'Dave')

一次更新多个属性时使用这种方法很方便。如果想批量更新多个记录,可以使用类方法 update_all

User.update_all "max_login_attempts = 3, must_change_password = 'true'"

删除

类似地,得到 Active Record 对象后还可以将其销毁,从数据库中删除。

user = User.find_by(name: 'David')
user.destroy