2.3 微博资源

我们已经生成并浏览了用户资源,现在要生成微博资源。阅读本节时,我推荐你和 2.2 节对比一下。你会发现两个资源在很多方面都是一致的。通过这样重复生成资源,我们可以更好地理解 Rails 中的 REST 架构。在这样的早期阶段看一下用户资源和微博资源的相同之处,也是本章的主要目的之一。

2.3.1 概览微博资源

和用户资源一样,我们使用 rails generate scaffold 命令生成微博资源的代码,不过这一次要实现图 2.3 中的数据模型:[8]

$ rails generate scaffold Micropost content:text user_id:integer
      invoke  active_record
      create    db/migrate/20140821012832_create_microposts.rb
      create    app/models/micropost.rb
      invoke    test_unit
      create      test/models/micropost_test.rb
      create      test/fixtures/microposts.yml
      invoke  resource_route
       route    resources :microposts
      invoke  scaffold_controller
      create    app/controllers/microposts_controller.rb
      invoke    erb
      create      app/views/microposts
      create      app/views/microposts/index.html.erb
      create      app/views/microposts/edit.html.erb
      create      app/views/microposts/show.html.erb
      create      app/views/microposts/new.html.erb
      create      app/views/microposts/_form.html.erb
      invoke    test_unit
      create      test/controllers/microposts_controller_test.rb
      invoke    helper
      create      app/helpers/microposts_helper.rb
      invoke      test_unit
      create        test/helpers/microposts_helper_test.rb
      invoke    jbuilder
      create      app/views/microposts/index.json.jbuilder
      create      app/views/microposts/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/microposts.js.coffee
      invoke    scss
      create      app/assets/stylesheets/microposts.css.scss
      invoke  scss
   identical    app/assets/stylesheets/scaffolds.css.scss

如果看到 Spring 相关的错误,再次执行这个命令即可。

然后,和 2.2 节一样,我们要执行迁移,更新数据库,使用新建的数据模型:

$ bundle exec rake db:migrate ==  CreateMicroposts: migrating ===============================================
-- create_table(:microposts)
   -> 0.0023s
==  CreateMicroposts: migrated (0.0026s) ======================================

现在我们就可以使用类似 2.2.1 节中介绍的方法来创建微博了。你可能猜到了,脚手架还会更新 Rails 的路由文件,为微博资源加入一条规则,如代码清单 2.8 所示。[9]和用户资源类似,resources :micropsts 把微博相关的 URL 映射到 MicropostsController,如表 2.3 所示。

代码清单 2.8:Rails 的路由,有一条针对微博资源的新规则

config/routes.rb

Rails.application.routes.draw do
 resources :microposts  resources :users
  .
  .
  .
end

表 2.3:代码清单 2.8 中微博资源生成的符合 REST 架构的路由

HTTP 请求 URL 动作 作用
GET /microposts index 列出所有微博
GET /microposts/1 show 显示 ID 为 1 的微博
GET /microposts/new new 显示创建新微博的页面
POST /microposts create 创建新微博
GET /microposts/1/edit edit 显示编辑 ID 为 1 的微博页码
PATCH /microposts/1 update 更新 ID 为 1 的微博
DELETE /microposts/1 destroy 删除 ID 为 1 的微博

MicropostsController 的代码简化后如代码清单 2.9 所示。注意,除了把 UsersController 换成 MicropostsController 之外,这段代码和代码清单 2.4 没什么区别。这说明了两个资源在 REST 架构中的共同之处。

代码清单 2.9:简化后的 MicropostsController

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  .
  .
  .
  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

我们在发布微博的页面(/microposts/new)输入一些内容,发布一篇微博,如图 2.12 所示。

既然已经打开这个页面了,那就多发布几篇微博,并且确保至少把一篇微博的 user_id 设为 1,把微博赋予 2.2.1 节中创建的第一个用户。结果应该和图 2.13 类似。

demo new micropost 3rd edition图 2.12:发布微博的页面demo micropost index 3rd edition图 2.13:微博索引页(/microposts

2.3.2 限制微博内容的长度

如果要称得上“微博”这个名字,就要限制内容的长度。在 Rails 中实现这种限制很简单,使用验证(validation)功能即可。要限制微博的长度最大字数为 140 个字符(就像 Twitter 一样),我们可以使用长度验证。在文本编辑器或 IDE 中打开 app/models/micropost.rb,写入代码清单 2.10 中的代码。

代码清单 2.10:限制微博的长度最多为 140 个字符

app/models/micropost.rb

class Micropost < ActiveRecord::Base
 validates :content, length: { maximum: 140 } end

这段代码看起来可能很神秘,我们会在 6.2 节详细介绍验证。如果我们在发布微博的页面输入超过 140 个字符的内容,就能看出这个验证的作用了。如图 2.14 所示,Rails 会渲染错误信息,提示微博的内容太长了。(7.3.3 节会详细介绍错误信息。)

micropost length error 3rd edition图 2.14:发布微博失败时显示的错误消息

2.3.3 一个用户拥有多篇微博

Rails 最强大的功能之一是,可以在不同的数据模型之间建立关联(association)。对本例中的用户模型而言,每个用户可以拥有多篇微博。我们可以更新用户模型(参见代码清单 2.11)和微博模型(参见代码清单 2.12)的代码实现这种关联。

代码清单 2.11:一个用户拥有多篇微博

app/models/user.rb

class User < ActiveRecord::Base
 has_many :microposts end
代码清单 2.12:一篇微博属于一个用户

app/models/micropost.rb

class Micropost < ActiveRecord::Base
 belongs_to :user  validates :content, length: { maximum: 140 }
end

我们可以把这种关联用图 2.15 中的图标表示出来。因为 microposts 表中有 user_id 这一列,所以 Rails(通过 Active Record)能把微博和各个用户关联起来。

micropost user association图 2.15:微博和用户之间的关联

第 11 章第 12 章,我们会使用微博和用户之间的关联显示一个用户的所有微博,还会生成一个和 Twitter 类似的微博列表。现在,我们可以在控制台(console)中检查用户与微博之间的关联。控制台是和 Rails 应用交互很有用的工具。在命令行中执行 rails console 命令,启动控制台。然后输入 User.first,从数据库中读取第一个用户,并把得到的数据赋值给 first_user 变量:[10]

$ rails console >> first_user = User.first
=> #<User id: 1, name: "Michael Hartl", email: "[email protected]",
created_at: "2014-07-21 02:01:31", updated_at: "2014-07-21 02:01:31">
>> first_user.microposts
=> [#<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2014-07-21 02:37:37", updated_at: "2014-07-21 02:37:37">, #<Micropost id: 2,
content: "Second micropost", user_id: 1, created_at: "2014-07-21 02:38:54",
updated_at: "2014-07-21 02:38:54">]
>> micropost = first_user.microposts.first    # Micropost.first would also work.
=> #<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2014-07-21 02:37:37", updated_at: "2014-07-21 02:37:37">
>> micropost.user
=> #<User id: 1, name: "Michael Hartl", email: "[email protected]",
created_at: "2014-07-21 02:01:31", updated_at: "2014-07-21 02:01:31">
>> exit

我在这段代码的最后一行加上了 exit,告诉你如何退出终端。在大多数系统中也可以按 Ctrl-D 键。[11] 我们使用 first_user.microposts 获取这个用户发布的微博。Active Record 会自动返回 user_idfirst_user 的 ID(1)相同的所有微博。在第 11 章第 12 章中,我们会更深入地学习关联的这种用法。

2.3.4 继承体系

接下来简要介绍 Rails 中控制器和模型的类继承。 如果你有面向对象编程(Object-oriented Programming,简称 OOP)的经验,能更好地理解这些内容。如果你未接触过 OOP 的话,可以跳过这一节。一般来说,如果你不熟悉类的概念(4.4 节中会介绍),我建议你以后再回过头来读这一节。

我们先介绍模型的继承结构。对比一下代码清单 2.13代码清单 2.14 ,可以看出,UserMicropost 都(通过 &lt; 符号)继承自 ActiveRecord::Base,这是 Active Record 为模型提供的基类。图 2.16 列出了这种继承关系。通过继承 ActiveRecord::Base,模型对象才能与数据库通讯,才能把数据库中的列看做 Ruby 中的属性,等等。

代码清单 2.13:User 类中的继承

app/models/user.rb

class User < ActiveRecord::Base
  .
  .
  .
end
代码清单 2.14:Mcropost 类中的继承

app/models/micropost.rb

class Micropost < ActiveRecord::Base
  .
  .
  .
end

demo model inheritance图 2.16:用户模型和微博模型中的继承体系

控制器的继承结构稍微复杂一些。对比代码清单 2.15代码清单 2.16,可以看出,UsersControllerMicroposts Controller 都继承自 ApplicationController。如代码清单 2.17 所示,ApplicationController 继承自 ActionController::BaseActionController::Base 是 Rails 中 Action Pack 库为控制器提供的基类。这些类之间的关系如图 2.17 所示。

代码清单 2.15:UsersController 类中的继承

app/controllers/users_controller.rb

class UsersController < ApplicationController
  .
  .
  .
end
代码清单 2.16:MicropostsController 类中的继承

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  .
  .
  .
end
代码清单 2.17:ApplicationController 类中的继承

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  .
  .
  .
end

demo controller inheritance图 2.17:UsersControllerMicropostsController 中的继承体系

和模型的继承类似,通过继承 ActionController::BaseUsersControllerMicropostsController 获得了很多功能。例如,处理模型对象的能力,过滤输入的 HTTP 请求,以及把视图渲染成 HTML。Rails 应用中的所有控制器都继承 ApplicationController,所以其中定义的规则会自动运用于应用中的的每个动作。例如,8.4 节会介绍如何在 ApplicationController 中引入辅助方法,为整个应用的所有控制器都加上登录和退出功能。

2.3.5 部署这个玩具应用

完成微博资源之后,是时候把代码推送到 Bitbucket 的仓库中了:

$ git status
$ git add -A
$ git commit -m "Finish toy app"
$ git push

通常情况下,你应该经常做一些很小的提交,不过对于本章来说,最后做一次大提交也无妨。

然后,你也可以按照 1.5 节介绍的方法,把这个应用部署到 Heroku:

$ git push heroku

执行这个命令的前提是,你已经按照 2.1 节中的说明创建了 Heroku 应用。否则,应该先执行 hreoku create,然后再执行 git push heroku master

然后还要执行下面的命令迁移生产环境的数据库,这样应用才能使用数据库:

$ heroku run rake db:migrate

这个命令会按照用户和微博的数据模型更新 Heroku 中的数据库。迁移数据库之后,就可以在生产环境中使用这个应用了,如图 2.18 所示,而且这个应用使用 PostgreSQL 数据库。

toy app production图 2.18:运行在生产环境中的玩具应用