# Active Record 基礎
本文介紹 Active Record。
讀完本文,你將學到:
* 對象關系映射(Object Relational Mapping,ORM)和 Active Record 是什么,以及如何在 Rails 中使用;
* Active Record 在 MVC 中的作用;
* 如何使用 Active Record 模型處理保存在關系型數據庫中的數據;
* Active Record 模式(schema)命名約定;
* 數據庫遷移,數據驗證和回調;
### Chapters
1. [Active Record 是什么?](#active-record-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F)
* [Active Record 模式](#active-record-%E6%A8%A1%E5%BC%8F)
* [對象關系映射](#%E5%AF%B9%E8%B1%A1%E5%85%B3%E7%B3%BB%E6%98%A0%E5%B0%84)
* [Active Record 用作 ORM 框架](#active-record-%E7%94%A8%E4%BD%9C-orm-%E6%A1%86%E6%9E%B6)
2. [Active Record 中的“多約定少配置”原則](#active-record-%E4%B8%AD%E7%9A%84%E2%80%9C%E5%A4%9A%E7%BA%A6%E5%AE%9A%E5%B0%91%E9%85%8D%E7%BD%AE%E2%80%9D%E5%8E%9F%E5%88%99)
* [命名約定](#%E5%91%BD%E5%90%8D%E7%BA%A6%E5%AE%9A)
* [模式約定](#%E6%A8%A1%E5%BC%8F%E7%BA%A6%E5%AE%9A)
3. [創建 Active Record 模型](#%E5%88%9B%E5%BB%BA-active-record-%E6%A8%A1%E5%9E%8B)
4. [不用默認的命名約定](#%E4%B8%8D%E7%94%A8%E9%BB%98%E8%AE%A4%E7%9A%84%E5%91%BD%E5%90%8D%E7%BA%A6%E5%AE%9A)
5. [CRUD:讀寫數據](#crud%EF%BC%9A%E8%AF%BB%E5%86%99%E6%95%B0%E6%8D%AE)
* [創建](#%E5%88%9B%E5%BB%BA)
* [讀取](#%E8%AF%BB%E5%8F%96)
* [更新](#%E6%9B%B4%E6%96%B0)
* [刪除](#%E5%88%A0%E9%99%A4)
6. [數據驗證](#%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81)
7. [回調](#%E5%9B%9E%E8%B0%83)
8. [遷移](#%E8%BF%81%E7%A7%BB)
### 1 Active Record 是什么?
Active Record 是 [MVC](getting_started.html#the-mvc-architecture) 中的 M(模型),處理數據和業務邏輯。Active Record 負責創建和使用需要持久存入數據庫中的數據。Active Record 實現了 Active Record 模式,是一種對象關系映射系統。
#### 1.1 Active Record 模式
Active Record 模式出自 [Martin Fowler](http://www.martinfowler.com/eaaCatalog/activeRecord.html) 的《企業應用架構模式》一書。在 Active Record 模式中,對象中既有持久存儲的數據,也有針對數據的操作。Active Record 模式把數據存取邏輯作為對象的一部分,處理對象的用戶知道如何把數據寫入數據庫,以及從數據庫中讀出數據。
#### 1.2 對象關系映射
對象關系映射(ORM)是一種技術手段,把程序中的對象和關系型數據庫中的數據表連接起來。使用 ORM,程序中對象的屬性和對象之間的關系可以通過一種簡單的方法從數據庫獲取,無需直接編寫 SQL 語句,也不過度依賴特定的數據庫種類。
#### 1.3 Active Record 用作 ORM 框架
Active Record 提供了很多功能,其中最重要的幾個如下:
* 表示模型和其中的數據;
* 表示模型之間的關系;
* 通過相關聯的模型表示繼承關系;
* 持久存入數據庫之前,驗證模型;
* 以面向對象的方式處理數據庫操作;
### 2 Active Record 中的“多約定少配置”原則
使用其他編程語言或框架開發程序時,可能必須要編寫很多配置代碼。大多數的 ORM 框架都是這樣。但是,如果遵循 Rails 的約定,創建 Active Record 模型時不用做多少配置(有時甚至完全不用配置)。Rails 的理念是,如果大多數情況下都要使用相同的方式配置程序,那么就應該把這定為默認的方法。所以,只有常規的方法無法滿足要求時,才要額外的配置。
#### 2.1 命名約定
默認情況下,Active Record 使用一些命名約定,查找模型和數據表之間的映射關系。Rails 把模型的類名轉換成復數,然后查找對應的數據表。例如,模型類名為 `Book`,數據表就是 `books`。Rails 提供的單復數變形功能很強大,常見和不常見的變形方式都能處理。如果類名由多個單詞組成,應該按照 Ruby 的約定,使用駝峰式命名法,這時對應的數據表將使用下劃線分隔各單詞。因此:
* 數據表名:復數,下劃線分隔單詞(例如 `book_clubs`)
* 模型類名:單數,每個單詞的首字母大寫(例如 `BookClub`)
| 模型 / 類 | 數據表 / 模式 |
| --- | --- |
| `Post` | `posts` |
| `LineItem` | `line_items` |
| `Deer` | `deers` |
| `Mouse` | `mice` |
| `Person` | `people` |
#### 2.2 模式約定
根據字段的作用不同,Active Record 對數據表中的字段命名也做了相應的約定:
* **外鍵** - 使用 `singularized_table_name_id` 形式命名,例如 `item_id`,`order_id`。創建模型關聯后,Active Record 會查找這個字段;
* **主鍵** - 默認情況下,Active Record 使用整數字段 `id` 作為表的主鍵。使用 [Active Record 遷移](%7B%7B%20site.baseurl%7D%7D/migrations.html)創建數據表時,會自動創建這個字段;
還有一些可選的字段,能為 Active Record 實例添加更多的功能:
* `created_at` - 創建記錄時,自動設為當前的時間戳;
* `updated_at` - 更新記錄時,自動設為當前的時間戳;
* `lock_version` - 在模型中添加[樂觀鎖定](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html)功能;
* `type` - 讓模型使用[單表繼承](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance);
* `(association_name)_type` - [多態關聯](association_basics.html#polymorphic-associations)的類型;
* `(table_name)_count` - 緩存關聯對象的數量。例如,`posts` 表中的 `comments_count` 字段,緩存每篇文章的評論數;
雖然這些字段是可選的,但在 Active Record 中是被保留的。如果想使用相應的功能,就不要把這些保留字段用作其他用途。例如,`type` 這個保留字段是用來指定數據表使用“單表繼承”(STI)的,如果不用 STI,請使用其他的名字,例如“context”,這也能表明該字段的作用。
### 3 創建 Active Record 模型
創建 Active Record 模型的過程很簡單,只要繼承 `ActiveRecord::Base` 類就行了:
```
class Product < ActiveRecord::Base
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"
```
### 4 不用默認的命名約定
如果想使用其他的命名約定,或者在 Rails 程序中使用即有的數據庫可以嗎?沒問題,不用默認的命名約定也很簡單。
使用 `ActiveRecord::Base.table_name=` 方法可以指定數據表的名字:
```
class Product < ActiveRecord::Base
self.table_name = "PRODUCT"
end
```
如果這么做,還要在測試中調用 `set_fixture_class` 方法,手動指定固件(`class_name.yml`)的類名:
```
class FunnyJoke < ActiveSupport::TestCase
set_fixture_class funny_jokes: Joke
fixtures :funny_jokes
...
end
```
還可以使用 `ActiveRecord::Base.primary_key=` 方法指定數據表的主鍵:
```
class Product < ActiveRecord::Base
self.primary_key = "product_id"
end
```
### 5 CRUD:讀寫數據
CURD 是四種數據操作的簡稱:C 表示創建,R 表示讀取,U 表示更新,D 表示刪除。Active Record 自動創建了處理數據表中數據的方法。
#### 5.1 創建
Active Record 對象可以使用 Hash 創建,在塊中創建,或者創建后手動設置屬性。`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 讀取
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 查詢](active_record_querying.html)一文會詳細介紹查詢 Active Record 模型的方法。
#### 5.3 更新
得到 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'"
```
#### 5.4 刪除
類似地,得到 Active Record 對象后還可以將其銷毀,從數據庫中刪除。
```
user = User.find_by(name: 'David')
user.destroy
```
### 6 數據驗證
在存入數據庫之前,Active Record 還可以驗證模型。模型驗證有很多方法,可以檢查屬性值是否不為空、是否是唯一的,或者沒有在數據庫中出現過,等等。
把數據存入數據庫之前進行驗證是十分重要的步驟,所以調用 `create`、`save`、`update` 這三個方法時會做數據驗證,驗證失敗時返回 `false`,此時不會對數據庫做任何操作。這三個方法都有對應的爆炸方法(`create!`,`save!`,`update!`),爆炸方法要嚴格一些,如果驗證失敗,會拋出 `ActiveRecord::RecordInvalid` 異常。下面是個簡單的例子:
```
class User < ActiveRecord::Base
validates :name, presence: true
end
User.create # => false
User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
```
[Active Record 數據驗證](active_record_validations.html)一文會詳細介紹數據驗證。
### 7 回調
Active Record 回調可以在模型聲明周期的特定事件上綁定代碼,相應的事件發生時,執行這些代碼。例如創建新紀錄時,更新記錄時,刪除記錄時,等等。[Active Record 回調](active_record_callbacks.html)一文會詳細介紹回調。
### 8 遷移
Rails 提供了一個 DSL 用來處理數據庫模式,叫做“遷移”。遷移的代碼存儲在特定的文件中,通過 `rake` 調用,可以用在 Active Record 支持的所有數據庫上。下面這個遷移會新建一個數據表:
```
class CreatePublications < ActiveRecord::Migration
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 會跟蹤哪些遷移已經應用到數據庫中,還提供了回滾功能。創建數據表要執行 `rake db:migrate` 命令;回滾操作要執行 `rake db:rollback` 命令。
注意,上面的代碼和具體的數據庫種類無關,可用于 MySQL、PostgreSQL、Oracle 等數據庫。關于遷移的詳細介紹,參閱 [Active Record 遷移](active_record_migrations.html)一文。
### 反饋
歡迎幫忙改善指南質量。
如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。
翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。
文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。
最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
- Ruby on Rails 指南 (651bba1)
- 入門
- Rails 入門
- 模型
- Active Record 基礎
- Active Record 數據庫遷移
- Active Record 數據驗證
- Active Record 回調
- Active Record 關聯
- Active Record 查詢
- 視圖
- Action View 基礎
- Rails 布局和視圖渲染
- 表單幫助方法
- 控制器
- Action Controller 簡介
- Rails 路由全解
- 深入
- Active Support 核心擴展
- Rails 國際化 API
- Action Mailer 基礎
- Active Job 基礎
- Rails 程序測試指南
- Rails 安全指南
- 調試 Rails 程序
- 設置 Rails 程序
- Rails 命令行
- Rails 緩存簡介
- Asset Pipeline
- 在 Rails 中使用 JavaScript
- 引擎入門
- Rails 應用的初始化過程
- Autoloading and Reloading Constants
- 擴展 Rails
- Rails 插件入門
- Rails on Rack
- 個性化Rails生成器與模板
- Rails應用模版
- 貢獻 Ruby on Rails
- Contributing to Ruby on Rails
- API Documentation Guidelines
- Ruby on Rails Guides Guidelines
- Ruby on Rails 維護方針
- 發布記
- A Guide for Upgrading Ruby on Rails
- Ruby on Rails 4.2 發布記
- Ruby on Rails 4.1 發布記
- Ruby on Rails 4.0 Release Notes
- Ruby on Rails 3.2 Release Notes
- Ruby on Rails 3.1 Release Notes
- Ruby on Rails 3.0 Release Notes
- Ruby on Rails 2.3 Release Notes
- Ruby on Rails 2.2 Release Notes