7.1 显示用户的信息
图 7.1:本节实现的用户资料页面构思图
本节要实现的用户资料页面是完整页面的一小部分,只显示用户的名字和头像,构思图如图 7.1 所示。[1] 最终完成的用户资料页面会显示用户的头像、基本信息和微博列表,构思图如图 7.2 所示。[2](在图 7.2 中,我们第一次用到了“lorem ipsum”占位文字,这些文字背后的故事很有意思,有空的话你可以了解一下。)这个资料页面会和整个演示应用一起在第 12 章完成。
如果你一直坚持使用版本控制,现在像之前一样,新建一个主题分支:
$ git checkout master
$ git checkout -b sign-up
图 7.2:最终实现的用户资料页面构思图
7.1.1 调试信息和 Rails 环境
本节要实现的用户资料页面是第一个真正意义上的动态页面。虽然视图的代码不会动态改变,不过每个用户资料页面显示的内容却是从数据库中读取的。添加动态页面之前,最好做些准备工作,现在我们能做的就是在网站布局中加入一些调试信息,如代码清单 7.1 所示。这段代码使用 Rails 内置的 debug
方法和 params
变量(7.1.2 节会详细介绍),在各个页面显示一些对开发有帮助的信息。
代码清单 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)。[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 章创建的自定义样式表文件中加入一些样式规则,如代码清单 7.2 所示。
代码清单 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 节会再次用到这个混入。美化后的调试信息如图 7.3 所示。
图 7.3 中的调试信息显示了当前页面的一些有用信息:
---
controller: static_pages
action: home
这是 params
变量的 YAML [4]形式,和哈希类似,显示当前页面的控制器名和动作名。7.1.2 节会介绍其他调试信息的意思。
图 7.3:显示有调试信息的演示应用首页
7.1.2 用户资源
为了实现用户资料页面,数据库中要有用户记录,这引出了“先有鸡还是先有蛋”的问题:网站还没有注册页面,怎么可能有用户呢?其实这个问题在 6.3.4 节已经解决了,那时我们自己动手在 Rails 控制台中创建了一个用户,所以数据库中应该有一个用户记录:
$ rails console
>> User.count
=> 1
>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "[email protected]",
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 节,在继续阅读之前完成那里的操作。)从控制台的输出可以看出,这个用户的 ID 是 1
,我们现在的目标就是创建一个页面,显示这个用户的信息。我们会遵从 Rails 使用的 REST 架构(旁注 2.2),把数据视为资源,可以创建、显示、更新和删除。这四个操作分别对应 HTTP 标准中的 POST
、GET
、PATCH
和 DELETE
请求方法(旁注 3.2)。
按照 REST 架构的规则,资源一般由资源名加唯一标识符表示。我们把用户看做一个资源,若要查看 ID 为 1 的用户,就要向 /users/1 发送 GET
请求。这里没必要指明用哪个动作,Rails 的 REST 功能解析时,会自动把这个 GET
请求交给 show
动作处理。
2.2.1 节介绍过,ID 为 1 的用户对应的 URL 是 /users/1,不过现在访问这个 URL 的话,会显示错误信息,如图 7.4 中的服务器日志所示。
图 7.4:访问 /users/1 时服务器日志中显示的错误
我们只需在路由文件 config/routes.rb
中添加如下的一行代码就可以正常访问 /users/1 了:
resources :users
修改后的路由文件如代码清单 7.3 所示。
代码清单 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]以及用来获取相应 URL 的具名路由(5.3.3 节)。最终得到的 URL、动作和具名路由的对应关系如表 7.1 所示(和表 2.2 对比一下)。接下来的三章会介绍表 7.1 中的所有动作,并不断完善,把用户打造成完全符合 REST 架构的资源。
表 7.1:代码清单 7.3 中添加的用户资源规则实现的 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 中的代码之后,路由就生效了,但是页面还不存在(图 7.5)。下面我们在页面中添加一些简单的内容,7.1.4 节还会添加更多内容。
图 7.5:/users/1 的路由生效了,但页面不存在
用户资料页面的视图保存在标准的位置,即 app/views/users/show.html.erb
。这个视图和自动生成的 new.html.erb
(代码清单 5.28)不同,现在不存在,要手动创建,然后写入代码清单 7.4 中的代码。
代码清单 7.4:用户资料页面的临时视图
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>
在这段代码中,我们假设存在一个 @user
变量,使用 ERb 代码显示这个用户的名字和电子邮件地址。这和最终实现的视图有点不一样,届时不会公开显示用户的电子邮件地址。
我们要在用户控制器的 show
动作中定义 @user
变量,用户资料页面才能正常渲染。你可能猜到了,我们要在用户模型上调用 find
方法(6.1.4 节),从数据库中取出用户记录,如代码清单 7.5 所示。
代码清单 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 节中直接调用 User.find(1)
的效果一样。(严格来说,params[:id]
返回的是字符串 "1"
,find
方法会自动将其转换成整数。)
定义视图和动作之后,/users/1 就可以正常访问了,如图 7.6 所示。(如果添加 bcrypt 之后没重启过 Rails 服务器,现在或许要重启。)留意一下调试信息,证实了 params[:id]
的值和前面分析的一样:
---
action: show
controller: users
id: '1'
所以,代码清单 7.5 中的 User.find(params[:id])
才会取回 ID 为 1 的用户。
图 7.6:添加 show
动作后的用户资料页面
7.1.3 调试器
在 7.1.2 节看到,调试信息能帮助我们理解应用的运作方式。从 Rails 4.2 开始,可以使用 byebug
gem(代码清单 3.2)更直接地获取调试信息。我们把 debugger
加到应用中,看一下这个 gem 的作用,如代码清单 7.6 所示。
代码清单 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
"[email protected]"
(byebug) params[:id]
"1"
若想退出 byebug
,继续执行应用,可以按 Ctrl-D 键。然后把 show
动作中的 debugger
删除,如代码清单 7.7 所示。
代码清单 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。[6]这是一个免费服务,让用户上传图片,将其关联到自己的电子邮件地址上。使用 Gravatar 可以简化在网站中添加用户头像的过程,开发者不必分心去处理图片上传、剪裁和存储,只要使用用户的电子邮件地址构成头像的 URL 地址,用户的头像就会显示出来。(11.4 节会介绍如何处理图片上传。)
我们的计划是,定义一个名为 gravatar_for
的辅助方法,返回指定用户的 Gravatar 头像,如代码清单 7.8 所示。
代码清单 7.8:显示用户名字和 Gravatar 头像的用户资料页面视图
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
默认情况下,所有辅助方法文件中定义的方法都自动在任意视图中可用,不过为了便于管理,我们会把 gravatar_for
方法放在用户控制器对应的辅助方法文件中。根据 Gravatar 的文档,头像的 URL 地址中要使用用户电子邮件地址的 MD5 哈希值。在 Ruby 中,MD5 哈希算法由 Digest
库中的 hexdigest
方法实现:
>> email = "[email protected]".
>> Digest::MD5::hexdigest(email.downcase) => "1fda4469bcbec3badf5418269ffc5968"
电子邮件地址不区分大小写,但是 MD5 哈希算法区分,所以我们要先调用 downcase
方法把电子邮件地址转换成小写形式,然后再传给 hexdigest
方法。(在代码清单 6.31 中的回调里我们已经把电子邮件地址转换成小写形式了,但这里最好也转换,以防电子邮件地址来自其他地方。)我们定义的 gravatar_for
辅助方法如代码清单 7.9 所示。
代码清单 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 所示,页面中显示的头像是 Gravatar 的默认图片,因为 [email protected]
不是真的电子邮件地址(example.com 这个域名是专门用来举例的)。
图 7.7:显示 Gravatar 默认头像的用户资料页面图 7.8:显示真实头像的用户资料页面
我们调用 update_attributes
方法(6.1.5 节)更新一下数据库中的用户记录,然后就可以显示用户真正的头像了:
$ rails console
>> user = User.first
>> user.update_attributes(name: "Example User",
?> email: "[email protected]",
?> password: "foobar",
?> password_confirmation: "foobar")
=> true
我们把用户的电子邮件地址改成 [email protected]
。我已经把这个地址的头像设为了本书网站的 LOGO,修改后的结果如图 7.8 所示。
我们还要添加一个侧边栏,才能完成图 7.1 中的构思图。我们要使用 aside
标签定义侧边栏。aside
中的内容一般是对主体内容的补充(例如侧边栏),不过也可以自成一体。我们要把 aside
标签的类设为 row col-md-4
,这两个类都是 Bootstrap 提供的。在用户资料页面中添加侧边栏所需的代码如代码清单 7.10 所示。
代码清单 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 所示。[7](注意:因为 Asset Pipeline 使用 Sass 预处理器,所以样式中才可以使用嵌套。)实现的效果如图 7.9 所示。
代码清单 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;
}