模型
一、Active Record 基礎(chǔ)
介紹Models,數(shù)據(jù)庫持久性以及Active Record模式
- Active Record是什么?
Active Record 是 MVC 中的 M(模型),負責處理數(shù)據(jù)和業(yè)務邏輯。
注意:這里業(yè)務邏輯在Model處理。
Active Record負責創(chuàng)建和使用需要持久存入數(shù)據(jù)庫的數(shù)據(jù)。Active Record實現(xiàn)了Active Record模式,是一種對象關(guān)系映射系統(tǒng)。
1-1)Active Record模式
在Active Record模式中,對象中既有持久存儲的數(shù)據(jù),也有針對數(shù)據(jù)庫的操作。Active Record模式吧數(shù)據(jù)存取邏輯作為對象的一部分,處理對象的用戶知道如何把數(shù)據(jù)寫入數(shù)據(jù)庫,還知道如何從數(shù)據(jù)庫取出數(shù)據(jù)。
1-2)對象關(guān)系映射
對象關(guān)系映射(ORM)Object-Relational-Mapping
是一種技術(shù)手段,吧應用中的對象和關(guān)系型數(shù)據(jù)庫中的數(shù)據(jù)表連接起來。使用ORM,應用中對象的屬性和對象之間的關(guān)系可以通過一種簡單的方法從數(shù)據(jù)庫中獲取,無需直接編寫SQL語句,也不過度依賴特定的數(shù)據(jù)庫種類。
1-3)用作ORM框架的Active Record
Active Record作用:
- 表示模型和其中的數(shù)據(jù)
- 表示模型之間的關(guān)系
- 通過相關(guān)聯(lián)的模型表示繼承層次結(jié)構(gòu)
- 持久存入數(shù)據(jù)庫之前,驗證模型
- 以面向?qū)ο蟮姆绞教幚頂?shù)據(jù)庫操作
2)Active Record中的約定大于配置
原則
使用其他編程語言或框架開發(fā)應用時,可能必須要編寫很多配置代碼。大多數(shù) ORM 框架都是這樣。但是,如果遵循 Rails 的約定,創(chuàng)建 Active Record 模型時不用做多少配置(有時甚至完全不用配置)。Rails 的理念是,如果大多數(shù)情況下都要使用相同的方式配置應用,那么就應該把這定為默認的方式。所以,只有約定無法滿足要求時,才要額外配置。
2-1)命名約定
默認情況下,Active Record使用一些命名約定,查找模型和數(shù)據(jù)庫表之間的映射關(guān)系。Rails吧模型的類名轉(zhuǎn)換為復數(shù),然后查找對應的數(shù)據(jù)表。
eg:
模型類名為Book, 數(shù)據(jù)庫表 books
Rails提供的單復數(shù)轉(zhuǎn)換功能很強大,常見和不常見的轉(zhuǎn)換方式都能處理。
模型類:單數(shù),每個單詞的首寫字母應該大寫 BookClub
數(shù)據(jù)庫表: 復數(shù),下劃線分隔單詞 book_clubs
2-2)模式約定
根據(jù)字段的作用不同,Active Record對數(shù)據(jù)庫表中的字段命名也做了相應的約定:
外鍵:使用 singularized_table_name_id 形式命名,例如 item_id,order_id。創(chuàng)建模型關(guān)聯(lián)后,Active Record 會查找這個字段;
主鍵:默認情況下,Active Record使用整數(shù)字段id作為表的主鍵。使用Active Record遷移創(chuàng)建數(shù)據(jù)庫表時,會自動創(chuàng)建這個字段。
還有一些可選的字段,能為 Active Record 實例添加更多的功能:
created_at:創(chuàng)建記錄時,自動設為當前的日期和時間;
updated_at:更新記錄時,自動設為當前的日期和時間;
lock_version:在模型中添加樂觀鎖;
type:讓模型使用單表繼承;
(association_name)_type:存儲多態(tài)關(guān)聯(lián)的類型;
(table_name)_count:緩存所關(guān)聯(lián)對象的數(shù)量。比如說,一個 Article 有多個 Comment,那么 comments_count 列存儲各篇文章現(xiàn)有的評論數(shù)量;
雖然這些字段是可選的,但在 Active Record 中是被保留的。如果想使用相應的功能,就不要把這些保留字段用作其他用途。例如,type 這個保留字段是用來指定數(shù)據(jù)庫表使用單表繼承(Single Table Inheritance,STI)的。如果不用單表繼承,請使用其他的名稱,例如“context”,這也能表明數(shù)據(jù)的作用。
3)創(chuàng)建Active Record模型
創(chuàng)建 Active Record 模型的過程很簡單,只要繼承 ApplicationRecord 類就行了:
class Product < ApplicationRecord
end
這代碼會創(chuàng)建Product模型,對應于數(shù)據(jù)庫中的products表。
上面的代碼會創(chuàng)建 Product 模型,對應于數(shù)據(jù)庫中的 products 表。同時,products 表中的字段也映射到 Product 模型實例的屬性上。假如 products 表由下面的 SQL 語句創(chuàng)建:
CREATE TABLE products (
id int(11) NOT NULL auto_increment,
name varchar(255),
PRIMARY KEY (id)
);
按照這樣的數(shù)據(jù)表結(jié)構(gòu),可以編寫下面的代碼:
p = Product.new
p.name = "Some Book"
puts p.name # "Some Book"
4)覆蓋命名約定
如果想使用其他的命名約定,或者在Rails應用張使用即有的數(shù)據(jù)庫可以嗎?沒問題,默認的約定能夠輕易覆蓋。
ApplicationRecord 繼承自 ActiveRecord::Base,后者定義了一系列有用的方法。使用 ActiveRecord::Base.table_name= 方法可以指定要使用的表名:
class Product < ApplicationRecord
self.table_name = "my_products"
end
如果這么做,還要調(diào)用 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:讀寫數(shù)據(jù)
CURD 是四種數(shù)據(jù)操作的簡稱:C 表示創(chuàng)建,R 表示讀取,U 表示更新,D 表示刪除。Active Record 自動創(chuàng)建了處理數(shù)據(jù)表中數(shù)據(jù)的方法。增刪改查。
5-1)create增
Active Record 對象可以使用散列創(chuàng)建,在塊中創(chuàng)建,或者創(chuàng)建后手動設置屬性。new 方法創(chuàng)建一個新對象,create 方法創(chuàng)建新對象,并將其存入數(shù)據(jù)庫。
例如,User 模型中有兩個屬性,name 和 occupation。調(diào)用 create 方法會創(chuàng)建一個新記錄,并將其存入數(shù)據(jù)庫:
user = User.create(name: "David", occupation: "Code Artist")
new 方法實例化一個新對象,但不保存:
user = User.new
user.name = "David"
user.occupation = "Code Artist"
調(diào)用 user.save 可以把記錄存入數(shù)據(jù)庫。
如果在 create 和 new 方法中使用塊,會把新創(chuàng)建的對象拉入塊中,初始化對象:
user = User.new do |u|
u.name = "David"
u.occupation = "Code Artist"
end
5-2)query查
Active Record 為讀取數(shù)據(jù)庫中的數(shù)據(jù)提供了豐富的 API。
返回所有集合:
users=User.all
返回第一個:
user=User.first
返回第一個名為David用戶:
davidUser = User.find_by(name:'David')
查找所有名字為David,職業(yè)為Code Artists的用戶,而且按照created_at 反向排序:
users=User.find_by(name:'David', occupation:'Code Artists').order(created_at::desc)
5-3)Update更新
檢索到Active Record對象后,可以修改其屬性,然后再將其存入數(shù)據(jù)庫。
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對象后還可以將其銷毀,從數(shù)據(jù)庫中刪除。
user=User.find_by(name:'David')
user.destroy
6)數(shù)據(jù)驗證
在存入數(shù)據(jù)庫之前,Active Record還可以驗證模型。模型驗證有很多方法,可以檢查屬性值是否不為空,是否是唯一,沒有在數(shù)據(jù)庫中出現(xiàn)過,等等。
吧數(shù)據(jù)存入數(shù)據(jù)庫之前進行驗證是是否重要的步驟,所以調(diào)用save和update方法時都會做數(shù)據(jù)驗證。驗證失敗時返回false,此時不會對數(shù)據(jù)庫做任何操作。這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)回調(diào)
Active Record回調(diào)用于在模型生命周期的特定時間上綁定代碼,相應的事件發(fā)生時,執(zhí)行綁定的代碼。例如創(chuàng)建新記錄,更新記錄時,刪除記錄時,等等。
8)遷移
Rails提供了一個DSL(Domain-Special Language)用來處理數(shù)據(jù)庫模式,叫作”遷移“。
注意:什么是數(shù)據(jù)庫模式?
模式(schema)是數(shù)據(jù)庫體系結(jié)構(gòu)中的一個節(jié)點
對于SQL Server數(shù)據(jù)庫來說:
訪問一個具體的表,可以由4個部分組成:
服務器名+數(shù)據(jù)庫名+模式名+表名
對于訪問本地的數(shù)據(jù)庫:
因為服務器已經(jīng)連接上了,因此不用指定
數(shù)據(jù)庫名,通過use 數(shù)據(jù)庫名 指定了
模式名,如果不指定的話,數(shù)據(jù)庫默認使用dbo
模式
模式(schema) 是用于 在一個 大項目中的 各個 小項目
每個 小項目的表, 放在 各自的 模式(schema) 下面.
這樣, 遇到 小項目里面. 有 相同名字的 表的話, 不會發(fā)生沖突.
例如一個 公司的 系統(tǒng).
里面分2個 子系統(tǒng), 分別為 財務系統(tǒng)
和 人力資源系統(tǒng)
.
這2個 子系統(tǒng), 共用一個數(shù)據(jù)庫
那么 財務系統(tǒng)的表, 可以放在 財務的 模式(schema).
人力資源系統(tǒng)的表,放在 人力資源系統(tǒng)的模式里面。
這2個 子系統(tǒng), 能夠 互相訪問 對方的表
但是又不因為 表重名 的問題,影響對方。
遷移的代碼存儲在特定的文件中,通過 rails 命令執(zhí)行。可以用在 Active Record 支持的所有數(shù)據(jù)庫上。下面這個遷移新建一個表:
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 會跟蹤哪些遷移已經(jīng)應用到數(shù)據(jù)庫上,還提供了回滾功能。為了創(chuàng)建表,要執(zhí)行 rails db:migrate 命令。如果想回滾,則執(zhí)行 rails db:rollback 命令。
注意,上面的代碼與具體的數(shù)據(jù)庫種類無關(guān),可用于 MySQL、PostgreSQL、Oracle 等數(shù)據(jù)庫。
二、Active Record 數(shù)據(jù)庫遷移
遷移是 Active Record 的一個特性,允許我們按時間順序
管理數(shù)據(jù)庫模式。有了遷移,就不必再用純 SQL 來修改數(shù)據(jù)庫模式,而是可以使用簡單的 Ruby DSL 來描述對數(shù)據(jù)表的修改。
1)遷移概述
遷移是以一致和輕松的方式按時間順序修改數(shù)據(jù)庫模式的實用方法。它使用 Ruby DSL,因此不必手動編寫 SQL,從而實現(xiàn)了數(shù)據(jù)庫無關(guān)的數(shù)據(jù)庫模式的創(chuàng)建和修改。
我們可以把遷移看做數(shù)據(jù)庫的新“版本”。數(shù)據(jù)庫模式一開始并不包含任何內(nèi)容,之后通過一個個遷移來添加或刪除數(shù)據(jù)表、字段和記錄。Active Record
知道如何沿著時間線更新數(shù)據(jù)庫模式,使其從任何歷史版本更新為最新版本。Active Record
還會更新 db/schema.rb
文件,以匹配最新的數(shù)據(jù)庫結(jié)構(gòu)。
示例:
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 數(shù)據(jù)表,數(shù)據(jù)表中包含 name 字符串字段和 description 文本字段。同時隱式添加了 id 主鍵字段,這是所有 Active Record 模型的默認主鍵。timestamps 宏添加了 created_at 和 updated_at 兩個字段。后面這幾個特殊字段只要存在就都由 Active Record 自動管理。
對于支持事務并提供了用于修改數(shù)據(jù)庫模式的語句的數(shù)據(jù)庫,遷移被包裝在事務中。如果數(shù)據(jù)庫不支持事務,那么當遷移失敗時,已成功的那部分操作將無法回滾。這種情況下只能手動完成相應的回滾操作。
某些查詢不能在事務內(nèi)部運行。如果數(shù)據(jù)庫適配器支持 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)創(chuàng)建遷移
2-1)創(chuàng)建獨立的遷移
遷移文件儲存在 db/migrate 文件夾中,一個遷移文件包含一個遷移類。文件名采用 YYYYMMDDHHMMSS_create_products.rb 形式,即 UTC 時間戳加上下劃線再加上遷移的名稱。遷移類的名稱(駝峰式)應該匹配文件名中遷移的名稱。例如,在 20080906120000_create_products.rb 文件中應該定義 CreateProducts 類,在 20080906120001_add_details_to_products.rb 文件中應該定義 AddDetailsToProducts 類。Rails 根據(jù)文件名的時間戳部分確定要運行的遷移和遷移運行的順序,因此當需要把遷移文件復制到其他 Rails 應用,或者自己生成遷移文件時,一定要注意遷移運行的順序。
當然,計算時間戳不是什么有趣的事,因此 Active Record 提供了生成器:
$ bin/rails generate migration AddPartNumberToProducts
上面的命令會創(chuàng)建空的遷移,并進行適當命名:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
end
end
2-2)模型生成器
模型和腳手架生成器會生成適用于添加新模型的遷移。這些遷移中已經(jīng)包含用于創(chuàng)建有關(guān)數(shù)據(jù)表的指令。如果我們告訴 Rails 想要哪些字段,那么添加這些字段所需的語句也會被創(chuàng)建。例如,運行下面的命令:
$ bin/rails generate model Product name:string description:text
上面的命令會創(chuàng)建下面的遷移:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
我們可以根據(jù)需要添加“字段名稱/類型”對,沒有數(shù)量限制。
2-3)傳遞修飾符
可以直接在命令行
中傳遞常用的類型修飾符
。這些類型修飾符用大括號括起來,放在字段類型之后。例如,運行下面的命令:
$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
上面的命令會創(chuàng)建下面的遷移:
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)編寫遷移
使用生成器創(chuàng)建遷移后,就可以開始寫代碼了。
3-1)創(chuàng)建數(shù)據(jù)表
create_table
方法是最基礎(chǔ)、最常用的方法,其代碼通常是由模型或腳手架生成器生成的。典型的用法像下面這樣:
create_table :products do |t|
t.string :name
end
上面的命令會創(chuàng)建包含 name 字段的 products 數(shù)據(jù)表(后面會介紹,數(shù)據(jù)表還包含自動創(chuàng)建的 id 字段)。
默認情況下,create_table 方法會創(chuàng)建 id 主鍵。可以用 :primary_key 選項來修改主鍵名稱,還可以傳入 id: false 選項以禁用主鍵。如果需要傳遞數(shù)據(jù)庫特有的選項,可以在 :options 選項中使用 SQL 代碼片段。例如:
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
上面的代碼會在用于創(chuàng)建數(shù)據(jù)表的 SQL 語句末尾加上 ENGINE=BLACKHOLE(如果使用 MySQL 或 MarialDB,默認選項是 ENGINE=InnoDB)。
還可以傳遞帶有數(shù)據(jù)表描述信息的 :comment 選項,這些注釋會被儲存在數(shù)據(jù)庫中,可以使用 MySQL Workbench、PgAdmin III 等數(shù)據(jù)庫管理工具查看。對于大型數(shù)據(jù)庫,強列推薦在應用的遷移中添加注釋。目前只有 MySQL 和 PostgreSQL 適配器支持注釋功能。
3-2)創(chuàng)建聯(lián)結(jié)數(shù)據(jù)表
create_join_table 方法用于創(chuàng)建 HABTM(has and belongs to many)聯(lián)結(jié)數(shù)據(jù)表。典型的用法像下面這樣:
create_join_table :products, :categories
上面的代碼會創(chuàng)建包含 category_id 和 product_id 字段的 categories_products 數(shù)據(jù)表。這兩個字段的 :null 選項默認設置為 false,可以通過 :column_options 選項覆蓋這一設置:
create_join_table :products, :categories, column_options: { null: true }
聯(lián)結(jié)數(shù)據(jù)表的名稱默認由 create_join_table 方法的前兩個參數(shù)按字母順序組合而來。可以傳入 :table_name 選項來自定義聯(lián)結(jié)數(shù)據(jù)表的名稱:
create_join_table :products, :categories, table_name: :categorization
上面的代碼會創(chuàng)建 categorization 數(shù)據(jù)表。
create_join_table 方法也接受塊作為參數(shù),用于添加索引(默認未創(chuàng)建的索引)或附加字段:
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end