7.1 显示用户的信息

profile mockup profile name bootstrap图 7.1:本节实现的用户资料页面构思图

本节要实现的用户资料页面是完整页面的一小部分,只显示用户的名字和头像,构思图如图 7.1 所示。[1] 最终完成的用户资料页面会显示用户的头像、基本信息和微博列表,构思图如图 7.2 所示。[2](在图 7.2 中,我们第一次用到了“lorem ipsum”占位文字,这些文字背后的故事很有意思,有空的话你可以了解一下。)这个资料页面会和整个演示应用一起在第 12 章完成。

如果你一直坚持使用版本控制,现在像之前一样,新建一个主题分支:

$ git checkout master
$ git checkout -b sign-up

profile mockup bootstrap图 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 节会介绍其他调试信息的意思。

home page with debug 3rd edition图 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 标准中的 POSTGETPATCHDELETE 请求方法(旁注 3.2)。

按照 REST 架构的规则,资源一般由资源名加唯一标识符表示。我们把用户看做一个资源,若要查看 ID 为 1 的用户,就要向 /users/1 发送 GET 请求。这里没必要指明用哪个动作,Rails 的 REST 功能解析时,会自动把这个 GET 请求交给 show 动作处理。

2.2.1 节介绍过,ID 为 1 的用户对应的 URL 是 /users/1,不过现在访问这个 URL 的话,会显示错误信息,如图 7.4 中的服务器日志所示。

profile routing error 3rd edition图 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 节还会添加更多内容。

user show unknown action 3rd edition图 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 的用户。

user show 3rd edition图 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 应用中哪部分有问题,就可以在可能导致问题的代码附近加上 debuggerbyebug 很强大,可以查看系统的状态,查找应用错误,以及交互式调试应用。

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 类为 gravataralt 属性的值是用户的名字(对视觉障碍人士使用的屏幕阅读器特别有用)。

用户资料页面如图 7.7 所示,页面中显示的头像是 Gravatar 的默认图片,因为 [email protected] 不是真的电子邮件地址(example.com 这个域名是专门用来举例的)。

profile with gravatar 3rd edition图 7.7:显示 Gravatar 默认头像的用户资料页面profile custom gravatar 3rd edition图 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;
}