2.2 用户资源
最后更新于:2022-04-01 22:27:58
# 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) 所示。
![demo blank user index 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb344c9c.png)图 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)会把这个页面打造成用户的资料页。
![demo new user 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb355387.png)图 2.5:新建用户页面([/users/new](http://localhost:3000/users/new))![demo show user 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb36b18a.png)图 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)为演示应用添加编辑和更新用户信息的功能。
![demo edit user 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb37fbd8.png)图 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)会为演示应用实现用户删除功能,而且仅限于管理员级别的用户才能执行这项操作。
![demo update user 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb393795.png)图 2.8:更新信息后的用户页面![demo user index two 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb3a55f2.png)图 2.9:创建第二个用户后的用户索引页([/users](http://localhost:3000/users))![demo destroy user 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb3ca15c.png)图 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)]
![mvc detailed](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcb3dd3dd.png)图 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
```
<%= link_to 'New User', new_user_path %> ``` 视图把代码转换成 HTML(第 7 步),然后控制器将其返回给浏览器,再显示出来(第 8 步)。 ## 2.2.3 这个用户资源的不足 脚手架生成的用户资源虽然能够让你大致了解 Rails,但也有一些不足: * **没验证数据**。用户模型会接受空名字和无效的电子邮件地址,而不报错。 * **没有认证机制**。没实现登录和退出功能,随意一个用户都可以进行任何操作。 * **没有测试**。也不是完全没有,脚手架会生成一些基本的测试,不过很粗糙也不灵便,没有针对数据验证和认证的测试,更别说针对其他功能的测试了。 * **没样式,没布局**。没有共用的样式和网站导航。 * **没真正理解**。如果你能读懂脚手架生成的代码,就不需要阅读这本书了。
';
Listing users
Name | ||||
---|---|---|---|---|
<%= user.name %> | <%= user.email %> | <%= link_to 'Show', user %> | <%= link_to 'Edit', edit_user_path(user) %> | <%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %> |
<%= link_to 'New User', new_user_path %> ``` 视图把代码转换成 HTML(第 7 步),然后控制器将其返回给浏览器,再显示出来(第 8 步)。 ## 2.2.3 这个用户资源的不足 脚手架生成的用户资源虽然能够让你大致了解 Rails,但也有一些不足: * **没验证数据**。用户模型会接受空名字和无效的电子邮件地址,而不报错。 * **没有认证机制**。没实现登录和退出功能,随意一个用户都可以进行任何操作。 * **没有测试**。也不是完全没有,脚手架会生成一些基本的测试,不过很粗糙也不灵便,没有针对数据验证和认证的测试,更别说针对其他功能的测试了。 * **没样式,没布局**。没有共用的样式和网站导航。 * **没真正理解**。如果你能读懂脚手架生成的代码,就不需要阅读这本书了。