# 7.1 顯示用戶的信息
圖 7.1:本節實現的用戶資料頁面構思圖
本節要實現的用戶資料頁面是完整頁面的一小部分,只顯示用戶的名字和頭像,構思圖如[圖 7.1](#fig-profile-mockup-profile-name) 所示。[[1](#fn-1)] 最終完成的用戶資料頁面會顯示用戶的頭像、基本信息和微博列表,構思圖如[圖 7.2](#fig-profile-mockup) 所示。[[2](#fn-2)](在圖 7.2 中,我們第一次用到了“lorem ipsum”占位文字,[這些文字背后的故事](http://www.straightdope.com/columns/read/2290/what-does-the-filler-text-lorem-ipsum-mean)很有意思,有空的話你可以了解一下。)這個資料頁面會和整個演示應用一起在[第 12 章](chapter12.html#following-users)完成。
如果你一直堅持使用版本控制,現在像之前一樣,新建一個主題分支:
```
$ git checkout master
$ git checkout -b sign-up
```
圖 7.2:最終實現的用戶資料頁面構思圖
## 7.1.1 調試信息和 Rails 環境
本節要實現的用戶資料頁面是第一個真正意義上的動態頁面。雖然視圖的代碼不會動態改變,不過每個用戶資料頁面顯示的內容卻是從數據庫中讀取的。添加動態頁面之前,最好做些準備工作,現在我們能做的就是在網站布局中加入一些調試信息,如[代碼清單 7.1](#listing-rails-debug) 所示。這段代碼使用 Rails 內置的 `debug` 方法和 `params` 變量([7.1.2 節](#a-users-resource)會詳細介紹),在各個頁面顯示一些對開發有幫助的信息。
##### 代碼清單 7.1:在網站布局中添加一些調試信息
app/views/layouts/application.html.erb
```
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
```
因為我們不想在線上網站中向用戶顯示調試信息,所以上述代碼使用 `if Rails.env.development?` 限制只在開發環境中顯示調試信息。開發環境是 Rails 默認支持的三個環境之一([旁注 7.1](#aside-rails-environments))。[[3](#fn-3)]`Rails.env.development?` 的返回值只在開發環境中才是 `true`,所以下面這行嵌入式 Ruby 代碼
```
<%= debug(params) if Rails.env.development? %>
```
不會在生產環境和測試環境中執行。(在測試環境中顯示調試信息雖然沒有壞處,但也沒什么好處,所以最好只在開發環境中顯示。)
##### 旁注 7.1:Rails 環境
Rails 定義了三個環境,分別是測試環境、開發環境和生產環境。Rails 控制臺默認使用的是開發環境:
```
$ rails console
Loading development environment
>> Rails.env
=> "development"
>> Rails.env.development?
=> true
>> Rails.env.test?
=> false
```
如前所示,`Rails` 對象有一個 `env` 屬性,屬性上還可以調用各環境對應的布爾值方法,例如,`Rails.env.test?`,在測試環境中的返回值是 `true`,在其他兩個環境中的返回值則是 `false`。
如果需要在其他環境中使用控制臺(例如,在測試環境中調試),只需把環境名傳給 `console` 命令即可:
```
$ rails console test
Loading test environment
>> Rails.env
=> "test"
>> Rails.env.test?
=> true
```
Rails 本地服務器和控制臺一樣,默認使用開發環境,不過也可以在其他環境中運行:
```
$ rails server --environment production
```
如果要在生產環境中運行應用,先要有一個生產數據庫。在生產環境中執行 `rake db:migrate` 命令可以生成這個數據庫:
```
$ bundle exec rake db:migrate RAILS_ENV=production
```
(我發現在控制臺、服務器和遷移命令中指定環境的方法不一樣,可能會混淆,所以特意演示了這三個命令的用法。)
順便說一下,把應用部署到 Heroku 后,可以使用 `heroku run console` 命令進入控制臺查看使用的環境:
```
$ heroku run console
>> Rails.env
=> "production"
>> Rails.env.production?
=> true
```
Heroku 是用來部署網站的平臺,自然會在生產環境中運行應用。
為了讓調試信息看起來漂亮一些,我們在[第 5 章](chapter5.html#filling-in-the-layout)創建的自定義樣式表文件中加入一些樣式規則,如[代碼清單 7.2](#listing-mixin-and-debug) 所示。
##### 代碼清單 7.2:添加美化調試信息的樣式,使用了一個 Sass 混入
app/assets/stylesheets/custom.css.scss
```
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } .
.
.
/* miscellaneous */
.debug_dump {
clear: both; float: left; width: 100%; margin-top: 45px; @include box_sizing; }
```
這段代碼用到了 Sass 的“混入”(mixin)功能,創建的這個混入名為 `box-sizing`。混入用來打包一系列樣式規則,可多次使用。預處理器會把
```
.debug_dump {
.
.
.
@include box_sizing;
}
```
轉換成
```
.debug_dump {
.
.
.
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
```
[7.2.1 節](#using-form-for)會再次用到這個混入。美化后的調試信息如[圖 7.3](#fig-home-page-with-debug) 所示。
[圖 7.3](#fig-home-page-with-debug) 中的調試信息顯示了當前頁面的一些有用信息:
```
---
controller: static_pages
action: home
```
這是 `params` 變量的 YAML [[4](#fn-4)]形式,和哈希類似,顯示當前頁面的控制器名和動作名。[7.1.2 節](#a-users-resource)會介紹其他調試信息的意思。
圖 7.3:顯示有調試信息的演示應用首頁
## 7.1.2 用戶資源
為了實現用戶資料頁面,數據庫中要有用戶記錄,這引出了“先有雞還是先有蛋”的問題:網站還沒有注冊頁面,怎么可能有用戶呢?其實這個問題在 [6.3.4 節](chapter6.html#creating-and-authenticating-a-user)已經解決了,那時我們自己動手在 Rails 控制臺中創建了一個用戶,所以數據庫中應該有一個用戶記錄:
```
$ rails console
>> User.count
=> 1
>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-08-29 02:58:28", updated_at: "2014-08-29 02:58:28",
password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW...">
```
(如果你的數據庫中現在沒有用戶記錄,回到 [6.3.4 節](chapter6.html#creating-and-authenticating-a-user),在繼續閱讀之前完成那里的操作。)從控制臺的輸出可以看出,這個用戶的 ID 是 `1`,我們現在的目標就是創建一個頁面,顯示這個用戶的信息。我們會遵從 Rails 使用的 REST 架構([旁注 2.2](chapter2.html#aside-rest)),把數據視為資源,可以創建、顯示、更新和刪除。這四個操作分別對應 [HTTP 標準](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol)中的 `POST`、`GET`、`PATCH` 和 `DELETE` 請求方法([旁注 3.2](chapter3.html#aside-get-etc))。
按照 REST 架構的規則,資源一般由資源名加唯一標識符表示。我們把用戶看做一個資源,若要查看 ID 為 1 的用戶,就要向 /users/1 發送 `GET` 請求。這里沒必要指明用哪個動作,Rails 的 REST 功能解析時,會自動把這個 `GET` 請求交給 `show` 動作處理。
[2.2.1 節](chapter2.html#a-user-tour)介紹過,ID 為 1 的用戶對應的 URL 是 /users/1,不過現在訪問這個 URL 的話,會顯示錯誤信息,如[圖 7.4](#fig-profile-routing-error) 中的服務器日志所示。
圖 7.4:訪問 /users/1 時服務器日志中顯示的錯誤
我們只需在路由文件 `config/routes.rb` 中添加如下的一行代碼就可以正常訪問 /users/1 了:
```
resources :users
```
修改后的路由文件如[代碼清單 7.3](#listing-users-resource) 所示。
##### 代碼清單 7.3:在路由文件中添加用戶資源的規則
config/routes.rb
```
Rails.application.routes.draw do
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
resources :users end
```
我們的目的只是為了顯示用戶資料頁面,可是 `resources :users` 不僅讓 /users/1 可以訪問,而且還為演示應用中的用戶資源提供了符合 REST 架構的所有動作,[[5](#fn-5)]以及用來獲取相應 URL 的具名路由([5.3.3 節](chapter5.html#using-named-routes))。最終得到的 URL、動作和具名路由的對應關系如[表 7.1](#table-restful-users) 所示(和[表 2.2](chapter2.html#table-demo-restful-users) 對比一下)。接下來的三章會介紹[表 7.1](#table-restful-users) 中的所有動作,并不斷完善,把用戶打造成完全符合 REST 架構的資源。
表 7.1:[代碼清單 7.3](#listing-users-resource) 中添加的用戶資源規則實現的 REST 架構路由
| HTTP 請求 | URL | 動作 | 具名路由 | 作用 |
| --- | --- | --- | --- | --- |
| `GET` | /users | `index` | `users_path` | 顯示所有用戶的頁面 |
| `GET` | /users/1 | `show` | `user_path(user)` | 顯示單個用戶的頁面 |
| `GET` | /users/new | `new` | `new_user_path` | 創建(注冊)新用戶的頁面 |
| `POST` | /users | `create` | `users_path` | 創建新用戶 |
| `GET` | /users/1/edit | `edit` | `edit_user_path(user)` | 編輯 ID 為 1 的用戶頁面 |
| `PATCH` | /users/1 | `update` | `user_path(user)` | 更新用戶信息 |
| `DELETE` | /users/1 | `destroy` | `user_path(user)` | 刪除用戶 |
添加[代碼清單 7.3](#listing-users-resource) 中的代碼之后,路由就生效了,但是頁面還不存在([圖 7.5](#fig-user-show-unknown-action))。下面我們在頁面中添加一些簡單的內容,[7.1.4 節](#a-gravatar-image-and-a-sidebar)還會添加更多內容。
圖 7.5:/users/1 的路由生效了,但頁面不存在
用戶資料頁面的視圖保存在標準的位置,即 `app/views/users/show.html.erb`。這個視圖和自動生成的 `new.html.erb`([代碼清單 5.28](chapter5.html#listing-generate-users-controller))不同,現在不存在,要手動創建,然后寫入[代碼清單 7.4](#listing-stub-user-view) 中的代碼。
##### 代碼清單 7.4:用戶資料頁面的臨時視圖
app/views/users/show.html.erb
```
<%= @user.name %>, <%= @user.email %>
```
在這段代碼中,我們假設存在一個 `@user` 變量,使用 ERb 代碼顯示這個用戶的名字和電子郵件地址。這和最終實現的視圖有點不一樣,屆時不會公開顯示用戶的電子郵件地址。
我們要在用戶控制器的 `show` 動作中定義 `@user` 變量,用戶資料頁面才能正常渲染。你可能猜到了,我們要在用戶模型上調用 `find` 方法([6.1.4 節](chapter6.html#finding-user-objects)),從數據庫中取出用戶記錄,如[代碼清單 7.5](#listing-user-show-action) 所示。
##### 代碼清單 7.5:含有 `show` 動作的用戶控制器
app/controllers/users_controller.rb
```
class UsersController < ApplicationController
def show
@user = User.find(params[:id]) end
def new
end
end
```
在這段代碼中,我們使用 `params` 獲取用戶的 ID。當我們向用戶控制器發送請求時,`params[:id]` 會返回用戶的 ID,即 1,所以這就和 [6.1.4 節](chapter6.html#finding-user-objects)中直接調用 `User.find(1)` 的效果一樣。(嚴格來說,`params[:id]` 返回的是字符串 `"1"`,`find` 方法會自動將其轉換成整數。)
定義視圖和動作之后,/users/1 就可以正常訪問了,如[圖 7.6](#fig-user-show-rails) 所示。(如果添加 bcrypt 之后沒重啟過 Rails 服務器,現在或許要重啟。)留意一下調試信息,證實了 `params[:id]` 的值和前面分析的一樣:
```
---
action: show
controller: users
id: '1'
```
所以,[代碼清單 7.5](#listing-user-show-action) 中的 `User.find(params[:id])` 才會取回 ID 為 1 的用戶。
圖 7.6:添加 `show` 動作后的用戶資料頁面
## 7.1.3 調試器
在 [7.1.2 節](#a-users-resource)看到,調試信息能幫助我們理解應用的運作方式。從 Rails 4.2 開始,可以使用 `byebug` gem([代碼清單 3.2](chapter3.html#listing-gemfile-sample-app))更直接地獲取調試信息。我們把 `debugger` 加到應用中,看一下這個 gem 的作用,如[代碼清單 7.6](#listing-debugger) 所示。
##### 代碼清單 7.6:在用戶控制器中使用調試器
app/controllers/users_controller.rb
```
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger end
def new
end
end
```
現在訪問 /users/1 時,會在 Rails 服務器的輸出中顯示 `byebug` 提示符:
```
(byebug)
```
我們可以把它當成 Rails 控制臺,在其中執行代碼,看一下應用的狀態:
```
(byebug) @user.name
"Example User"
(byebug) @user.email
"example@railstutorial.org"
(byebug) params[:id]
"1"
```
若想退出 `byebug`,繼續執行應用,可以按 Ctrl-D 鍵。然后把 `show` 動作中的 `debugger` 刪除,如[代碼清單 7.7](#listing-debugger-removed) 所示。
##### 代碼清單 7.7:刪除調試器后的用戶控制器
app/controllers/users_controller.rb
```
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
```
只要你覺得 Rails 應用中哪部分有問題,就可以在可能導致問題的代碼附近加上 `debugger`。`byebug` 很強大,可以查看系統的狀態,查找應用錯誤,以及交互式調試應用。
## 7.1.4 Gravatar 頭像和側邊欄
前面創建了一個略顯簡陋的用戶資料頁面,這一節要再添加一些內容:用戶頭像和側邊欄。首先,我們要在用戶資料頁面中添加一個“全球通用識別”的頭像,或者叫 [Gravatar](http://gravatar.com/)。[[6](#fn-6)]這是一個免費服務,讓用戶上傳圖片,將其關聯到自己的電子郵件地址上。使用 Gravatar 可以簡化在網站中添加用戶頭像的過程,開發者不必分心去處理圖片上傳、剪裁和存儲,只要使用用戶的電子郵件地址構成頭像的 URL 地址,用戶的頭像就會顯示出來。([11.4 節](chapter11.html#micropost-images)會介紹如何處理圖片上傳。)
我們的計劃是,定義一個名為 `gravatar_for` 的輔助方法,返回指定用戶的 Gravatar 頭像,如[代碼清單 7.8](#listing-user-show-view-with-gravatar) 所示。
##### 代碼清單 7.8:顯示用戶名字和 Gravatar 頭像的用戶資料頁面視圖
app/views/users/show.html.erb
```
<% provide(:title, @user.name) %>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
```
默認情況下,所有輔助方法文件中定義的方法都自動在任意視圖中可用,不過為了便于管理,我們會把 `gravatar_for` 方法放在用戶控制器對應的輔助方法文件中。根據 [Gravatar 的文檔](http://en.gravatar.com/site/implement/hash/),頭像的 URL 地址中要使用用戶電子郵件地址的 [MD5 哈希值](http://en.wikipedia.org/wiki/MD5)。在 Ruby 中,MD5 哈希算法由 `Digest` 庫中的 `hexdigest` 方法實現:
```
>> email = "MHARTL@example.COM".
>> Digest::MD5::hexdigest(email.downcase) => "1fda4469bcbec3badf5418269ffc5968"
```
電子郵件地址不區分大小寫,但是 MD5 哈希算法區分,所以我們要先調用 `downcase` 方法把電子郵件地址轉換成小寫形式,然后再傳給 `hexdigest` 方法。(在[代碼清單 6.31](chapter6.html#listing-email-downcase) 中的回調里我們已經把電子郵件地址轉換成小寫形式了,但這里最好也轉換,以防電子郵件地址來自其他地方。)我們定義的 `gravatar_for` 輔助方法如[代碼清單 7.9](#listing-gravatar-for-helper) 所示。
##### 代碼清單 7.9:定義 `gravatar_for` 輔助方法
app/helpers/users_helper.rb
```
module UsersHelper
# 返回指定用戶的 Gravatar
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
```
`gravatar_for` 方法的返回值是一個 `img` 元素,用于顯示 Gravatar 頭像。`img` 標簽的 CSS 類為 `gravatar`,`alt` 屬性的值是用戶的名字(對視覺障礙人士使用的屏幕閱讀器特別有用)。
用戶資料頁面如[圖 7.7](#fig-profile-with-gravatar) 所示,頁面中顯示的頭像是 Gravatar 的默認圖片,因為 `user@example.com` 不是真的電子郵件地址(example.com 這個域名是專門用來舉例的)。
圖 7.7:顯示 Gravatar 默認頭像的用戶資料頁面圖 7.8:顯示真實頭像的用戶資料頁面
我們調用 `update_attributes` 方法([6.1.5 節](chapter6.html#updating-user-objects))更新一下數據庫中的用戶記錄,然后就可以顯示用戶真正的頭像了:
```
$ rails console
>> user = User.first
>> user.update_attributes(name: "Example User",
?> email: "example@railstutorial.org",
?> password: "foobar",
?> password_confirmation: "foobar")
=> true
```
我們把用戶的電子郵件地址改成 `example@railstutorial.org`。我已經把這個地址的頭像設為了本書網站的 LOGO,修改后的結果如[圖 7.8](#fig-profile-custom-gravatar) 所示。
我們還要添加一個側邊欄,才能完成[圖 7.1](#fig-profile-mockup-profile-name) 中的構思圖。我們要使用 `aside` 標簽定義側邊欄。`aside` 中的內容一般是對主體內容的補充(例如側邊欄),不過也可以自成一體。我們要把 `aside` 標簽的類設為 `row col-md-4`,這兩個類都是 Bootstrap 提供的。在用戶資料頁面中添加側邊欄所需的代碼如[代碼清單 7.10](#listing-user-show-with-sidebar) 所示。
##### 代碼清單 7.10:在 `show` 動作的視圖中添加側邊欄
app/views/users/show.html.erb
```
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
```
添加 HTML 結構和 CSS 類之后,我們再用 SCSS 為資料頁面定義一些樣式,如[代碼清單 7.11](#listing-sidebar-css) 所示。[[7](#fn-7)](注意:因為 Asset Pipeline 使用 Sass 預處理器,所以樣式中才可以使用嵌套。)實現的效果如[圖 7.9](#fig-user-show-sidebar-css) 所示。
##### 代碼清單 7.11:用戶資料頁面的樣式,包括側邊欄的樣式
app/assets/stylesheets/custom.css.scss
```
.
.
.
/* sidebar */
aside {
section.user_info {
margin-top: 20px;
}
section {
padding: 10px 0;
margin-top: 20px;
&:first-child {
border: 0;
padding-top: 0;
}
span {
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0px;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
```
- 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 練習