# 2.2 用戶資源
這一節我們要實現 [2.1.1 節](#a-toy-model-for-users)設定的用戶數據模型,還會為這個模型創建 Web 界面。二者結合起來就是一個“用戶資源”(Users Resource)。“資源”的意思是把用戶設想為對象,可以通過 [HTTP 協議](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol)在網頁中創建(create)、讀取(read)、更新(update)和刪除(delete)。正如前面提到的,用戶資源使用 Rails 內置的腳手架生成。我建議你先不要細看腳手架生成的代碼,這時看只會讓你更困惑。
把 `scaffold` 傳給 `rails generate` 就可以使用 Rails 的腳手架了。傳給 `scaffold` 的參數是資源名的單數形式(這里是 `User`)[[3](#fn-3)],后面可以再跟著一些可選參數,指定數據模型中的字段:
```
$ rails generate scaffold User name:string email:string
invoke active_record
create db/migrate/20140821011110_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
invoke resource_route
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
create app/views/users/edit.html.erb
create app/views/users/show.html.erb
create app/views/users/new.html.erb
create app/views/users/_form.html.erb
invoke test_unit
create test/controllers/users_controller_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
create test/helpers/users_helper_test.rb
invoke jbuilder
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/users.js.coffee
invoke scss
create app/assets/stylesheets/users.css.scss
invoke scss
create app/assets/stylesheets/scaffolds.css.scss
```
我們在執行的命令中加入了 `name:string` 和 `email:string`,這樣就可以實現[圖 2.2](#fig-demo-user-model) 中的用戶模型了。注意,沒必要指定 `id` 字段,Rails 會自動創建并將其設為表的主鍵(primary key)。
接下來我們要用 Rake(參見[旁注 2.1](#aside-rake))來遷移(migrate)數據庫:
```
$ bundle exec rake db:migrate == CreateUsers: migrating ==============================================
-- create_table(:users)
-> 0.0017s
== CreateUsers: migrated (0.0018s) =====================================
```
上面的命令會使用新定義的用戶數據模型更新數據庫。([6.1.1 節](chapter6.html#database-migrations)會詳細介紹數據庫遷移)注意,為了使用 `Gemfile` 中指定的 Rake 版本,我們要通過 `bundle exec` 執行 `rake`。在很多系統中,包括云端 IDE,都不必使用 `bundle exec`,但某些系統必須使用,所以為了命令的完整,我會一直使用 `bundle exec`。
然后,執行下面的命令,在另一個選項卡中運行本地 Web 服務器([圖 1.7](chapter1.html#fig-rails-server-new-tab)):[[4](#fn-4)]
```
$ rails server -b $IP -p $PORT # 在本地設備中只需執行 `rails server`
```
現在,這個玩具應用應該可以通過本地服務器訪問了([1.3.2 節](chapter1.html#rails-server))。如果使用云端 IDE,要在一個新的瀏覽器選項卡中打開網頁,別在 IDE 中打開。
##### 旁注 2.1:Rake
在 Unix 中,把源碼編譯成可執行的程序時,[make](http://en.wikipedia.org/wiki/Make_(software)) 扮演了很重要的角色。很多程序員的身體甚至已經對下面的代碼產生了條件反射:
```
$ ./configure && make && sudo make install
```
在 Unix 中(包括 Linux 和 Mac OS X),這個命令一般用來編譯代碼。
Rake 是 Ruby 版的 make,用 Ruby 語言編寫的類 make 程序。Rails 靈活的運用了 Rake 的功能,提供了很多開發基于數據庫的 Web 應用所需的管理任務。`rake db:migrate` 或許是最常用的。除此之外還有很多其他命令,運行 `rake -T db` 可以查看所有數據庫相關的任務:
```
$ bundle exec rake -T db
```
如果想查看所有 Rake 任務,運行:
```
$ bundle exec rake -T
```
任務列表看起來有點讓人摸不著頭腦,不過現在無需擔心,你不需要知道所有(甚至大多數)命令。學完本教程后,你會知道所有重要的任務。
## 2.2.1 瀏覽用戶相關的頁面
如果訪問根 URL [http://localhost:3000/](http://localhost:3000/) 看到的還是 Rails 默認頁面([圖 1.9](chapter1.html#fig-riding-rails))。不過使用腳手架生成用戶資源時生成了很多用來處理用戶的頁面。例如,列出所有用戶的頁面地址是 [/users](http://localhost:3000/users),創建新用戶的地址是 [/users/new](http://localhost:3000/users/new)。本節的目的就是走馬觀花地瀏覽一下這些用戶相關的頁面。瀏覽時你會發現[表 2.1](#table-user-urls) 很有用,表中顯示了頁面和 URL 之間的對應關系。
表 2.1:用戶資源中頁面和 URL 的對應關系
| URL | 動作 | 作用 |
| --- | --- | --- |
| [/users](http://localhost:3000/users) | `index` | 列出所有用戶 |
| [/users/1](http://localhost:3000/users/1) | `show` | 顯示 ID 為 1 的用戶 |
| [/users/new](http://localhost:3000/users/new) | `new` | 創建新用戶 |
| [/users/1/edit](http://localhost:3000/users/1/edit) | `edit` | 編輯 ID 為 1 的用戶 |
我們先來看一下顯示所有用戶的頁面,這個頁面叫“[索引頁](http://localhost:3000/users)”。和預期一樣,目前還沒有用戶,如[圖 2.4](#fig-demo-blank-user-index-rails-3) 所示。
圖 2.4:用戶資源的索引頁([/users](http://localhost:3000/users))
如果想創建新用戶要訪問“[新建用戶](http://localhost:3000/users/new)”頁面,如[圖 2.5](#fig-demo-new-user-rails_3) 所示。(在本地開發時,地址的前面部分都是 [http://localhost:3000](http://localhost:3000) 或云端 IDE 分配的地址,因此在后面的內容中我會省略這一部分。)[第 7 章](chapter7.html#sign-up)會把這個頁面改造成用戶注冊頁面。
我們可以在表單中填入名字和電子郵件地址,然后點擊“Create User”(創建用戶)按鈕創建一個用戶。然后就會顯示[這個用戶的頁面](http://localhost:3000/users/1),如[圖 2.6](#fig-demo-show-user-rails-3) 所示。頁面中顯示的綠色文字是“閃現消息”(flash message),[7.4.2 節](chapter7.html#the-flash)會介紹。注意,這個頁面的 URL 是 [/users/1](http://localhost:3000/users/1)。你可能猜到了,這里的 `1` 就是[圖 2.2](#fig-demo-user-model) 中的用戶 `id`。[7.1 節](chapter7.html#showing-users)會把這個頁面打造成用戶的資料頁。
圖 2.5:新建用戶頁面([/users/new](http://localhost:3000/users/new))圖 2.6:顯示某個用戶的頁面([/users/1](http://localhost:3000/users/1))
如果想修改用戶的信息,要訪問“[編輯頁面](http://localhost:3000/users/1/edit)”([圖 2.7](#fig-demo-edit-user-rails-3))。修改用戶信息后點擊“Update User”(更新用戶)按鈕就更改了這個玩具應用中該用戶的信息([圖 2.8](#fig-demo-update-user-rails-3))。[第 6 章](chapter6.html#modeling-users)會詳細介紹,用戶的信息存儲在后端的數據庫中。我們會在 [9.1 節](chapter9.html#updating-users)為演示應用添加編輯和更新用戶信息的功能。
圖 2.7:編輯用戶信息的頁面([/users/1/edit](http://localhost:3000/users/1/edit))
現在回到創建新用戶的頁面,提交表單創建第二個用戶。然后訪問用戶索引頁,結果如[圖 2.9](#fig-demo-user-index-two-rails-3) 所示。[7.1 節](chapter7.html#showing-users)會美化這個顯示所有用戶的頁面。
我們已經看了創建、顯示和編輯用戶的頁面,最后要看刪除用戶的頁面([圖 2.10](#fig-demo-destroy-user))。點擊圖 2.10 中所示的鏈接后,會刪除第二個用戶,索引頁面就只剩一個用戶了。如果這個操作不成功,確認瀏覽器是否啟用了 JavaScript。Rails 通過 JavaScript 發起刪除用戶的請求。[9.4 節](chapter9.html#deleting-users)會為演示應用實現用戶刪除功能,而且僅限于管理員級別的用戶才能執行這項操作。
圖 2.8:更新信息后的用戶頁面圖 2.9:創建第二個用戶后的用戶索引頁([/users](http://localhost:3000/users))圖 2.10:刪除一個用戶
## 2.2.2 MVC 實戰
我們已經快速概覽了用戶資源,下面我們從 MVC([1.3.3 節](chapter1.html#model-view-controller))的視角出發,審視其中某些特定部分。我們要分析在瀏覽器中訪問用戶索引頁的過程,了解一下 MVC([圖 2.11](#fig-mvc-detailed))。
圖中各步的說明如下:
1. 瀏覽器向 /users 發起一個請求;
2. Rails 的路由把 /users 交給 `UsersController` 中的 `index` 動作處理;
3. `index` 動作要求用戶模型讀取所有用戶(`User.all`);
4. 用戶模型從數據庫中讀取所有用戶;
5. 用戶模型把所有用戶組成的列表返回給控制器;
6. 控制器把所有用戶賦值給 `@users` 變量,然后傳入 `index` 視圖;
7. 視圖使用嵌入式 Ruby 把頁面渲染成 HTML;
8. 控制器把 HTML 發送回瀏覽器。[[5](#fn-5)]
圖 2.11:Rails 中的 MVC 架構詳解
下面詳細分析這個過程。首先,從瀏覽器中發起一個請求(第 1 步)。可以直接在瀏覽器地址欄中輸入地址,也可以點擊網頁中的鏈接。請求到達 Rails 路由(第 2 步),根據 URL(以及請求的類型,參見[旁注 3.2](chapter3.html#aside-get-etc))將其分發給合適的控制器動作。把用戶資源中相關的 URL 映射到控制器動作的代碼如[代碼清單 2.2](#listing-rails-routes) 所示。這行代碼會按照[表 2.1](#table-user-urls) 中的對應關系做映射。`:users` 這個符號很奇怪,它是一個符號(Symbol),[4.3.3 節](chapter4.html#hashes-and-symbols)會介紹。
##### 代碼清單 2.2:Rails 路由,其中定義了用戶資源的規則
config/routes.rb
```
Rails.application.routes.draw do
resources :users .
.
.
end
```
既然打開了路由文件,那就花點兒時間把根路由改為用戶索引頁吧,修改之后,訪問根地址就會顯示 /users 頁面。在[代碼清單 1.10](chapter1.html#listing-hello-root-route) 中,我們把
```
# root 'welcome#index'
```
改成了
```
root 'application#hello'
```
讓根路由指向 `ApplicationController` 中的 `hello` 動作。現在我們想使用 `UsersController` 中的 `index` 動作,要按照[代碼清單 2.3](#listing-rails-routes-root-route) 所示的方式修改。如果本章開頭在 `ApplicationController` 中添加了 `hello` 動作,我建議現在把這個動作刪除。
##### 代碼清單 2.3:把根路由指向 `UsersController` 中的動作
config/routes.rb
```
Rails.application.routes.draw do
resources :users
root 'users#index' .
.
.
end
```
[2.2.1 節](#a-user-tour)中瀏覽的頁面對應于 `UsersController` 中的不同動作。腳手架生成的控制器代碼摘要如[代碼清單 2.4](#listing-demo-users-controller) 所示。注意 `class UsersController < ApplicationController` 這種寫法,在 Ruby 中表示類繼承。[2.3.4 節](#inheritance-hierarchies)會簡要介紹繼承,[4.4 節](chapter4.html#ruby-classes)再做詳細介紹。
##### 代碼清單 2.4:用戶控制器代碼摘要
app/controllers/users_controller.rb
```
class UsersController < ApplicationController
.
.
.
def index
.
.
.
end
def show
.
.
.
end
def new
.
.
.
end
def edit
.
.
.
end
def create
.
.
.
end
def update
.
.
.
end
def destroy
.
.
.
end
end
```
你可能注意到了,動作的數量比我們看過的頁面數量多,`index`、`show`、`new` 和 `edit` 對應于 [2.2.1 節](#a-user-tour)介紹的頁面。不過還有一些其他動作,`create`、`update` 和 `destroy` 等。這些動作一般不直接渲染頁面(不過有時也會),只會修改數據庫中保存的用戶數據。[表 2.2](#table-demo-restful-users) 列出了控制器的全部動作,這些動作就是 Rails 對 REST 架構(參見[旁注 2.2](#aside-rest))的實現。REST 架構由計算機科學家 [Roy Fielding](http://en.wikipedia.org/wiki/Roy_Fielding) 提出,意思是“表現層狀態轉化”(Representational State Transfer)。[[6](#fn-6)]注意表 2.2 中的內容,有些部分有重疊。例如 `show` 和 `update` 兩個動作都映射到 /users/1 這個地址上。二者的區別是,使用的 [HTTP 請求方法](http://en.wikipedia.org/wiki/HTTP_request#Request_methods)不同。[3.3 節](chapter3.html#getting-started-with-testing)會更詳細地介紹 HTTP 請求方法。
表 2.2:代碼清單 2.2 生成的符合 REST 架構的路由
| HTTP 請求 | URL | 動作 | 作用 |
| --- | --- | --- | --- |
| `GET` | /users | `index` | 列出所用用戶 |
| `GET` | /users/1 | `show` | 顯示 ID 為 1 的用戶 |
| `GET` | /users/new | `new` | 顯示創建新用戶頁面 |
| `POST` | /users | `create` | 創建新用戶 |
| `GET` | /users/1/edit | `edit` | 顯示編輯 ID 為 1 的用戶頁面 |
| `PATCH` | /users/1 | `update` | 更新 ID 為 1 的用戶 |
| `DELETE` | /users/1 | `destroy` | 刪除 ID 為 1 的用戶 |
##### 旁注 2.2:表現層狀態轉化(REST)
如果你閱讀過一些 Ruby on Rails Web 開發相關的資料,會看到很多地方都提到了“REST”,它是“表現層狀態轉化”(REpresentational State Transfer)的簡稱。REST 是一種架構方式,用來開發分布式、基于網絡的系統和軟件程序,例如 WWW 和 Web 應用。REST 理論很抽象,在 Rails 應用中,REST 意味著大多數組件(例如用戶和微博)都會被模型化,變成資源(resource),可以創建(create)、讀取(read)、更新(update)和刪除(delete)。這些操作與[關系型數據庫中的 CRUD 操作](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)和 [HTTP 請求方法](http://en.wikipedia.org/wiki/HTTP_request#Request_methods)(`POST`,`GET`,`PATCH` [[7](#fn-7)] 和 `DELETE`)對應。[3.3 節](chapter3.html#getting-started-with-testing),特別是[旁注 3.2](chapter3.html#aside-get-etc),將更詳細地介紹 HTTP 請求。
作為 Rails 應用開發者,REST 開發方式能幫助你決定編寫哪些控制器和動作:你只需簡單的把可以創建、讀取、更新和刪除的資源理清就可以了。對本章的“用戶”和“微博”來說,這一過程非常明確,因為它們都是很自然的資源形式。在[第 12 章](chapter12.html#following-users)將看到,使用 REST 架構可以通過一種自然而便捷的方式解決很棘手的問題(“關注用戶”功能)。
為了探明用戶控制器和用戶模型之間的關系,我們看一下簡化后的 `index` 動作,如[代碼清單 2.5](#listing-demo-index-action) 所示。(腳手架生成的代碼很粗糙,所以我做了簡化。)
##### 代碼清單 2.5:這個玩具應用中簡化后的 `index` 動作
app/controllers/users_controller.rb
```
class UsersController < ApplicationController
.
.
.
def index
@users = User.all end
.
.
.
end
```
`index` 動作中有一行代碼,`@users = User.all`([圖 2.11](#fig-mvc-detailed) 中的第 3 步),要求用戶模型從數據庫中取出所有用戶(第 4 步),然后把結果賦值給 `@users` 變量(讀作“at-users”,第 5 步)。用戶模型的代碼參見[代碼清單 2.6](#listing-demo-user-model)。代碼看似簡單,但是通過繼承具備了很多功能(參見 [2.3.4 節](#inheritance-hierarchies) 和 [4.4 節](chapter4.html#ruby-classes))。具體而言,調用 Rails 中的 Active Record 庫后,`User.all` 就能獲取數據庫中的所有用戶。
##### 代碼清單 2.6:玩具應用中的用戶模型
app/models/user.rb
```
class User < ActiveRecord::Base
end
```
定義 `@users` 變量后,控制器再調用視圖(第 6 步)。視圖的代碼如[代碼清單 2.7](#listing-demo-index-view) 所示。以 `@` 開頭的變量是“實例變量”(instance variable),在視圖中自動可用。在本例中,`index.html.erb` 視圖的代碼([代碼清單 2.7](#listing-demo-index-view))遍歷 `@users`,為每個用戶生成一行 HTML。(你現在可能讀不懂這些代碼,這里只是讓你看一下視圖代碼是什么樣子。)
##### 代碼清單 2.7:用戶索引頁的視圖代碼
app/views/users/index.html.erb
```
<h1>Listing users</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th colspan="3"></th>
</tr>
</thead>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'New User', new_user_path %>
```
視圖把代碼轉換成 HTML(第 7 步),然后控制器將其返回給瀏覽器,再顯示出來(第 8 步)。
## 2.2.3 這個用戶資源的不足
腳手架生成的用戶資源雖然能夠讓你大致了解 Rails,但也有一些不足:
* **沒驗證數據**。用戶模型會接受空名字和無效的電子郵件地址,而不報錯。
* **沒有認證機制**。沒實現登錄和退出功能,隨意一個用戶都可以進行任何操作。
* **沒有測試**。也不是完全沒有,腳手架會生成一些基本的測試,不過很粗糙也不靈便,沒有針對數據驗證和認證的測試,更別說針對其他功能的測試了。
* **沒樣式,沒布局**。沒有共用的樣式和網站導航。
* **沒真正理解**。如果你能讀懂腳手架生成的代碼,就不需要閱讀這本書了。
- Ruby on Rails 教程
- 致中國讀者
- 序
- 致謝
- 作者譯者簡介
- 版權和代碼授權協議
- 第 1 章 從零開始,完成一次部署
- 1.1 簡介
- 1.2 搭建環境
- 1.3 第一個應用
- 1.4 使用 Git 做版本控制
- 1.5 部署
- 1.6 小結
- 1.7 練習
- 第 2 章 玩具應用
- 2.1 規劃應用
- 2.2 用戶資源
- 2.3 微博資源
- 2.4 小結
- 2.5 練習
- 第 3 章 基本靜態的頁面
- 3.1 創建演示應用
- 3.2 靜態頁面
- 3.3 開始測試
- 3.4 有點動態內容的頁面
- 3.5 小結
- 3.6 練習
- 3.7 高級測試技術
- 第 4 章 Rails 背后的 Ruby
- 4.1 導言
- 4.2 字符串和方法
- 4.3 其他數據類型
- 4.4 Ruby 類
- 4.5 小結
- 4.6 練習
- 第 5 章 完善布局
- 5.1 添加一些結構
- 5.2 Sass 和 Asset Pipeline
- 5.3 布局中的鏈接
- 5.4 用戶注冊:第一步
- 5.5 小結
- 5.6 練習
- 第 6 章 用戶模型
- 6.1 用戶模型
- 6.2 用戶數據驗證
- 6.3 添加安全密碼
- 6.4 小結
- 6.5 練習
- 第 7 章 注冊
- 7.1 顯示用戶的信息
- 7.2 注冊表單
- 7.3 注冊失敗
- 7.4 注冊成功
- 7.5 專業部署方案
- 7.6 小結
- 7.7 練習
- 第 8 章 登錄和退出
- 8.1 會話
- 8.2 登錄
- 8.3 退出
- 8.4 記住我
- 8.5 小結
- 8.6 練習
- 第 9 章 更新,顯示和刪除用戶
- 9.1 更新用戶
- 9.2 權限系統
- 9.3 列出所有用戶
- 9.4 刪除用戶
- 9.5 小結
- 9.6 練習
- 第 10 章 賬戶激活和密碼重設
- 10.1 賬戶激活
- 10.2 密碼重設
- 10.3 在生產環境中發送郵件
- 10.4 小結
- 10.5 練習
- 10.6 證明超時失效的比較算式
- 第 11 章 用戶的微博
- 11.1 微博模型
- 11.2 顯示微博
- 11.3 微博相關的操作
- 11.4 微博中的圖片
- 11.5 小結
- 11.6 練習
- 第 12 章 關注用戶
- 12.1 “關系”模型
- 12.2 關注用戶的網頁界面
- 12.3 動態流
- 12.4 小結
- 12.5 練習