# 7.1 显示用户的信息
![profile mockup profile name bootstrap](https://box.kancloud.cn/2016-05-11_5732bd102e129.png)图 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
```
![profile mockup bootstrap](https://box.kancloud.cn/2016-05-11_5732bd1049c33.png)图 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)会介绍其他调试信息的意思。
![home page with debug 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd106151c.png)图 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) 中的服务器日志所示。
![profile routing error 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd107e1a1.png)图 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)还会添加更多内容。
![user show unknown action 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd10a1b48.png)图 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 的用户。
![user show 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd10be549.png)图 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 这个域名是专门用来举例的)。
![profile with gravatar 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd10d4433.png)图 7.7:显示 Gravatar 默认头像的用户资料页面![profile custom gravatar 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd10ecb5d.png)图 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 练习