模型
一、Active Record 基础
介绍Models,数据库持久性以及Active Record模式
- Active Record是什么?
Active Record 是 MVC 中的 M(模型),负责处理数据和业务逻辑。
注意:这里业务逻辑在Model处理。
Active Record负责创建和使用需要持久存入数据库的数据。Active Record实现了Active Record模式,是一种对象关系映射系统。
1-1)Active Record模式
在Active Record模式中,对象中既有持久存储的数据,也有针对数据库的操作。Active Record模式吧数据存取逻辑作为对象的一部分,处理对象的用户知道如何把数据写入数据库,还知道如何从数据库取出数据。
1-2)对象关系映射
对象关系映射(ORM)Object-Relational-Mapping
是一种技术手段,吧应用中的对象和关系型数据库中的数据表连接起来。使用ORM,应用中对象的属性和对象之间的关系可以通过一种简单的方法从数据库中获取,无需直接编写SQL语句,也不过度依赖特定的数据库种类。
1-3)用作ORM框架的Active Record
Active Record作用:
- 表示模型和其中的数据
- 表示模型之间的关系
- 通过相关联的模型表示继承层次结构
- 持久存入数据库之前,验证模型
- 以面向对象的方式处理数据库操作
2)Active Record中的约定大于配置
原则
使用其他编程语言或框架开发应用时,可能必须要编写很多配置代码。大多数 ORM 框架都是这样。但是,如果遵循 Rails 的约定,创建 Active Record 模型时不用做多少配置(有时甚至完全不用配置)。Rails 的理念是,如果大多数情况下都要使用相同的方式配置应用,那么就应该把这定为默认的方式。所以,只有约定无法满足要求时,才要额外配置。
2-1)命名约定
默认情况下,Active Record使用一些命名约定,查找模型和数据库表之间的映射关系。Rails吧模型的类名转换为复数,然后查找对应的数据表。
eg:
模型类名为Book, 数据库表 books
Rails提供的单复数转换功能很强大,常见和不常见的转换方式都能处理。
模型类:单数,每个单词的首写字母应该大写 BookClub
数据库表: 复数,下划线分隔单词 book_clubs
2-2)模式约定
根据字段的作用不同,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”,这也能表明数据的作用。
3)创建Active Record模型
创建 Active Record 模型的过程很简单,只要继承 ApplicationRecord 类就行了:
class Product < ApplicationRecord
end
这代码会创建Product模型,对应于数据库中的products表。
上面的代码会创建 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"
4)覆盖命名约定
如果想使用其他的命名约定,或者在Rails应用张使用即有的数据库可以吗?没问题,默认的约定能够轻易覆盖。
ApplicationRecord 继承自 ActiveRecord::Base,后者定义了一系列有用的方法。使用 ActiveRecord::Base.table_name= 方法可以指定要使用的表名:
class Product < ApplicationRecord
self.table_name = "my_products"
end
如果这么做,还要调用 set_fixture_class 方法,手动指定固件(my_products.yml)的类名:
class ProductTest < ActiveSupport::TestCase
set_fixture_class my_products: Product
fixtures :my_products
...
end
还可以使用 ActiveRecord::Base.primary_key= 方法指定表的主键:
class Product < ApplicationRecord
self.primary_key = "product_id"
end
5)CRUD:读写数据
CURD 是四种数据操作的简称:C 表示创建,R 表示读取,U 表示更新,D 表示删除。Active Record 自动创建了处理数据表中数据的方法。增删改查。
5-1)create增
Active Record 对象可以使用散列创建,在块中创建,或者创建后手动设置属性。new 方法创建一个新对象,create 方法创建新对象,并将其存入数据库。
例如,User 模型中有两个属性,name 和 occupation。调用 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
5-2)query查
Active Record 为读取数据库中的数据提供了丰富的 API。
返回所有集合:
users=User.all
返回第一个:
user=User.first
返回第一个名为David用户:
davidUser = User.find_by(name:'David')
查找所有名字为David,职业为Code Artists的用户,而且按照created_at 反向排序:
users=User.find_by(name:'David', occupation:'Code Artists').order(created_at::desc)
5-3)Update更新
检索到Active Record对象后,可以修改其属性,然后再将其存入数据库。
user=User.find_by(name:'David')
user.name=‘Dave’
user.save
还有种使用散列的简写方式,指定属性名和属性值,例如:
user=User.find_by(name:'David')
user.update(name:'Dave')
一次更新多个属性时使用这种方法最方便。如果想批量更新多个记录,可以使用类方法update_all
:
User.update_all "max_login_attempts=3, must_change_password='true'"
5-4)Delete删除
检索到Active Record对象后还可以将其销毁,从数据库中删除。
user=User.find_by(name:'David')
user.destroy
6)数据验证
在存入数据库之前,Active Record还可以验证模型。模型验证有很多方法,可以检查属性值是否不为空,是否是唯一,没有在数据库中出现过,等等。
吧数据存入数据库之前进行验证是是否重要的步骤,所以调用save和update方法时都会做数据验证。验证失败时返回false,此时不会对数据库做任何操作。这2个方法都有对应的爆炸方法(save!和update!)。爆炸方法要严格一些,如果验证失败,就会抛出ActiveRecord::Recordnvalid
异常。
class User < ApplicationRecord
validates :name, presence: true
end
user = User.new
user.save # => false
user.save! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
注意:验证是在写class的时候,预定了validates
7)回调
Active Record回调用于在模型生命周期的特定时间上绑定代码,相应的事件发生时,执行绑定的代码。例如创建新记录,更新记录时,删除记录时,等等。
8)迁移
Rails提供了一个DSL(Domain-Special Language)用来处理数据库模式,叫作”迁移“。
注意:什么是数据库模式?
模式(schema)是数据库体系结构中的一个节点
对于SQL Server数据库来说:
访问一个具体的表,可以由4个部分组成:
服务器名+数据库名+模式名+表名
对于访问本地的数据库:
因为服务器已经连接上了,因此不用指定
数据库名,通过use 数据库名 指定了
模式名,如果不指定的话,数据库默认使用dbo
模式
模式(schema) 是用于 在一个 大项目中的 各个 小项目
每个 小项目的表, 放在 各自的 模式(schema) 下面.
这样, 遇到 小项目里面. 有 相同名字的 表的话, 不会发生冲突.
例如一个 公司的 系统.
里面分2个 子系统, 分别为 财务系统
和 人力资源系统
.
这2个 子系统, 共用一个数据库
那么 财务系统的表, 可以放在 财务的 模式(schema).
人力资源系统的表,放在 人力资源系统的模式里面。
这2个 子系统, 能够 互相访问 对方的表
但是又不因为 表重名 的问题,影响对方。
迁移的代码存储在特定的文件中,通过 rails 命令执行。可以用在 Active Record 支持的所有数据库上。下面这个迁移新建一个表:
class CreatePublications < ActiveRecord::Migration[5.0]
def change
create_table :publications do |t|
t.string :title
t.text :description
t.references :publication_type
t.integer :publisher_id
t.string :publisher_type
t.boolean :single_issue
t.timestamps
end
add_index :publications, :publication_type_id
end
end
Rails 会跟踪哪些迁移已经应用到数据库上,还提供了回滚功能。为了创建表,要执行 rails db:migrate 命令。如果想回滚,则执行 rails db:rollback 命令。
注意,上面的代码与具体的数据库种类无关,可用于 MySQL、PostgreSQL、Oracle 等数据库。
二、Active Record 数据库迁移
迁移是 Active Record 的一个特性,允许我们按时间顺序
管理数据库模式。有了迁移,就不必再用纯 SQL 来修改数据库模式,而是可以使用简单的 Ruby DSL 来描述对数据表的修改。
1)迁移概述
迁移是以一致和轻松的方式按时间顺序修改数据库模式的实用方法。它使用 Ruby DSL,因此不必手动编写 SQL,从而实现了数据库无关的数据库模式的创建和修改。
我们可以把迁移看做数据库的新“版本”。数据库模式一开始并不包含任何内容,之后通过一个个迁移来添加或删除数据表、字段和记录。Active Record
知道如何沿着时间线更新数据库模式,使其从任何历史版本更新为最新版本。Active Record
还会更新 db/schema.rb
文件,以匹配最新的数据库结构。
示例:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
这个迁移用于添加 products 数据表,数据表中包含 name 字符串字段和 description 文本字段。同时隐式添加了 id 主键字段,这是所有 Active Record 模型的默认主键。timestamps 宏添加了 created_at 和 updated_at 两个字段。后面这几个特殊字段只要存在就都由 Active Record 自动管理。
对于支持事务并提供了用于修改数据库模式的语句的数据库,迁移被包装在事务中。如果数据库不支持事务,那么当迁移失败时,已成功的那部分操作将无法回滚。这种情况下只能手动完成相应的回滚操作。
某些查询不能在事务内部运行。如果数据库适配器支持 DDL 事务,就可以使用 disable_ddl_transaction! 方法在某个迁移中临时禁用事务。
如果想在迁移中完成一些 Active Record 不知如何撤销的操作,可以使用 reversible 方法:
class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def change
reversible do |dir|
change_table :products do |t|
dir.up { t.change :price, :string }
dir.down { t.change :price, :integer }
end
end
end
end
或者用 up 和 down 方法来代替 change 方法:
class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
2)创建迁移
2-1)创建独立的迁移
迁移文件储存在 db/migrate 文件夹中,一个迁移文件包含一个迁移类。文件名采用 YYYYMMDDHHMMSS_create_products.rb 形式,即 UTC 时间戳加上下划线再加上迁移的名称。迁移类的名称(驼峰式)应该匹配文件名中迁移的名称。例如,在 20080906120000_create_products.rb 文件中应该定义 CreateProducts 类,在 20080906120001_add_details_to_products.rb 文件中应该定义 AddDetailsToProducts 类。Rails 根据文件名的时间戳部分确定要运行的迁移和迁移运行的顺序,因此当需要把迁移文件复制到其他 Rails 应用,或者自己生成迁移文件时,一定要注意迁移运行的顺序。
当然,计算时间戳不是什么有趣的事,因此 Active Record 提供了生成器:
$ bin/rails generate migration AddPartNumberToProducts
上面的命令会创建空的迁移,并进行适当命名:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
end
end
2-2)模型生成器
模型和脚手架生成器会生成适用于添加新模型的迁移。这些迁移中已经包含用于创建有关数据表的指令。如果我们告诉 Rails 想要哪些字段,那么添加这些字段所需的语句也会被创建。例如,运行下面的命令:
$ bin/rails generate model Product name:string description:text
上面的命令会创建下面的迁移:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
我们可以根据需要添加“字段名称/类型”对,没有数量限制。
2-3)传递修饰符
可以直接在命令行
中传递常用的类型修饰符
。这些类型修饰符用大括号括起来,放在字段类型之后。例如,运行下面的命令:
$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
上面的命令会创建下面的迁移:
class AddDetailsToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true, index: true
end
end
3)编写迁移
使用生成器创建迁移后,就可以开始写代码了。
3-1)创建数据表
create_table
方法是最基础、最常用的方法,其代码通常是由模型或脚手架生成器生成的。典型的用法像下面这样:
create_table :products do |t|
t.string :name
end
上面的命令会创建包含 name 字段的 products 数据表(后面会介绍,数据表还包含自动创建的 id 字段)。
默认情况下,create_table 方法会创建 id 主键。可以用 :primary_key 选项来修改主键名称,还可以传入 id: false 选项以禁用主键。如果需要传递数据库特有的选项,可以在 :options 选项中使用 SQL 代码片段。例如:
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
上面的代码会在用于创建数据表的 SQL 语句末尾加上 ENGINE=BLACKHOLE(如果使用 MySQL 或 MarialDB,默认选项是 ENGINE=InnoDB)。
还可以传递带有数据表描述信息的 :comment 选项,这些注释会被储存在数据库中,可以使用 MySQL Workbench、PgAdmin III 等数据库管理工具查看。对于大型数据库,强列推荐在应用的迁移中添加注释。目前只有 MySQL 和 PostgreSQL 适配器支持注释功能。
3-2)创建联结数据表
create_join_table 方法用于创建 HABTM(has and belongs to many)联结数据表。典型的用法像下面这样:
create_join_table :products, :categories
上面的代码会创建包含 category_id 和 product_id 字段的 categories_products 数据表。这两个字段的 :null 选项默认设置为 false,可以通过 :column_options 选项覆盖这一设置:
create_join_table :products, :categories, column_options: { null: true }
联结数据表的名称默认由 create_join_table 方法的前两个参数按字母顺序组合而来。可以传入 :table_name 选项来自定义联结数据表的名称:
create_join_table :products, :categories, table_name: :categorization
上面的代码会创建 categorization 数据表。
create_join_table 方法也接受块作为参数,用于添加索引(默认未创建的索引)或附加字段:
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end