应用 Rails 进行 REST 开发

这篇文档翻译自《RESTful Rails Development》,只为了交流和经验分享
而用。因为并没有原文章版权,所以请切勿切勿用于任何商业目的。如果您对文章
有什么建议和要求,请发信给 yananay@126.com,我会尽量去满足您的要求。

前言
Http 协议除了 get 和 post ,还可以做更多的事情,一直以来,很多的开发
人员都忘了这一点。
但是,如果你知道浏览器其实只支持 get 和 post, 那么你就不会感到惊奇
了。
get 和 post 是 http 请求从客户端传到服务器端的两个方法。除了这两个,
http 协议还知道 put 和 delete 方法,这两个方法告诉服务器创建或者删除一个
WEB 的资源。
这个教程的目的,就是扩展开发人员的视线,去了解 http 协议的 put 和
delete 方法。我们常说的 REST 这个术语,精华就是 http 协议中 get, post,
put, delete 四个方法。Rails 从 1.2 版本开始,就支持 REST 这个技术了。
这个教程一开始会简短的介绍 REST 的背景和概念,接着介绍为什么要开发
REST 风格的 Rails 应用。
使用 scaffolding,这是可以帮助我们产生 controller 和 model 细节的工
具,对我们的进行 REST 应用的开发很有帮助。REST 的应用中作用非常重大的路由
技术,将会在接下来的章节介绍。“嵌套的资源”这个章节,会介绍一下 REST 的
高级应用,告诉大家资源如何以 父 - 子(继承关系)的关系组合在一起,同时不
违反的 REST 风格的路由设计。教程的后面,还会介绍一些 REST 的内容,AJAX,
REST 风格的应用的测试方法,还有“ActiveResource”-- REST 的客户端部分。

在我们开始之前,再啰嗦最后一句:要读这个教程,最少你要懂一点 Rails 开
发的基本知识,否则的话,先去学习学习吧:)

1.1 什么是 REST?
REST 这个术语,是 Roy Fielding 在 Ph.D.论文中提出来的,它的全称是
“Representational State Transfer.”
REST 描述了这么一个架构:利用标准的 http 协议中的 get, post, put,
delete 来请求和操作网络上的资源。
在 REST 中,资源的意思就是一个 基于 URL 实体,客户端可以通过 http 协
议来和它进行交互。这个资源可以用各种形式来展示给客户端,如 HTML,XML,
RSS,主要依赖于客户端的调用方式。并不像以往的 Rails 开发那样,用 REST 方
式,一个 url 不是指定一个 model 或者 action, 一个 url 仅仅是资源的本身而
已。

在图 1.1 中,三个资源的 URL 的开头都是相同的,通过后面的不同的数字
来保证这三个是不同的资源。
注意:URL 并没有表明要对这三个资源进行什么操作。

在 Rails 应用中,一个资源是由 controller 和 model 组成的。那么从
技术的角度来看,图 1.1 中的 3 个资源"project",就是针对 3 个请求,而表现出
来的 Project model(也就是 ActiveRecord 类了)的 3 个实例形式。

1.2 为什么使用 REST?
问得好!我们已经使用 MVC 模式开发 Rails 应用 2 年了,为什么要使用
REST?

REST 所带给我们的,是 Rails 的理论上的提升,下面的一些特性,将会使
我们清晰地了解这一点。

a) 简明的 Url.

REST 风格的 URL 是为了定位一个资源,而并不是调用一

个 action. 在 REST 中,URL 经常以这种方式出现:先是 controller 的名称,然后
是资源的 id。请求的操作隐藏在 URL 里,并且通过 http 协议来表示(get, post,
put, delete 四个动作)。
b) 传回给客户端不同格式的内容。我们通过一种方式来编写 controller,
其中的 action 应该都可以返回给客户端不同的格式的结果。对于同一个 action,
即可以返回给客户端 html, 也可以返回给 xml,也可以返回给 RSS,这依赖于客户
端想要什么。REST 应用就好像变得能处理客户端好多的命令。
c) 更少的代码。因为一个 action 可以返回客户端的各种需要格式,这就
减少了(DRY

don't repeat yourself),这也就让 controller 里的代码减少了。

d) 面向 CRUD 的 controller. 所谓 CRUD 就是
Create,Retrieve,Update,Delete.
controller 和 model 融合到一起,每个 controller 都会响应某个
model 的操作。
e) 让系统设计更简单。REST 风格的开发方式,会使系统结构更加清晰,而
且容易维护。

接下来的章节中,我们会用例子来逐步的让您明白上面所描述的这些特
性。

1.3 有什么新玩意?

如果你觉得 REST 让你之前所有的开发经验变得毫无作用,别担心,那是
不可能的~ 因为 REST 仍然是基于 MVC 风格的。从技术角度来看,REST 中的“新
玩意”可以归纳为以下几点:
a) 在 controller 中的 respond_to 的用法。
b) 对于 link 和 form 的新的 helper 方法。
c) 在 controller redirect 中的 url 方法。
d) 在 routes.rb 文件中,新的路由定义方法。

一旦你了解了 REST ,而且经常使用它,那么自然而然,你就会设计一个 REST
的应用了!

1.4 准备
接下来,我们要以我们的一本书《RapidWeb Development mit Ruby on
Rails》中的一个例子“项目管理应用”来描述 Rails 的 REST 方面的特性。我们并
不会编写整个应用,但是我们会使用相同的技术去描述 REST 的内容。
下面,我们开始吧!首先创建一个 rails 的应用:
> rails ontrack
然后,我们来创建 开发 和 测试 的数据库。
> mysql -u rails -p
Enter password: *****
mysql> create database ontrack_development;
mysql> create database ontrack_test;
mysql> quit

1.4.1 Rails 1.2

假设并不是所有的人都希望在系统里安装 rails 1.2――因为那样的话,所
有的应用都必须跑在 rails 1.2 之下了。所以我们建议只是让这个例子使用
rails 1.2。参考文档,rails 1.2 的 tag 是 rel _1-2-1。所以我们使用 rake
命令来让 ontrack 项目使用 1.2 的版本。
> cd ontrack
> rake rails:freeze:edge TAG=rel_1-2-1

1.5 Resource Scaffolding

基于 REST 的 rails 应用可以使用新的脚手架(scaffold)命令
scaffold_resource 来方便的创建。可以传递给生成器这样一些参数:在本例中,
就是资源的名字 “project”, 和 这个 model 的字段名字以及类型。字段的类型
是必须的,因为我们要使用 migration,以及在视图中显示这些字段。

> cd ontrack
> ruby script/generate scaffold_resource project name:string desc:text
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/projects
exists test/functional/
exists test/unit/

create app/views/projects/index.rhtml

create app/views/projects/show.rhtml
create app/views/projects/new.rhtml
create app/views/projects/edit.rhtml
create app/views/layouts/projects.rhtml
create public/stylesheets/scaffold.css
create app/models/project.rb
create app/controllers/projects_controller.rb
create test/functional/projects_controller_test.rb
create app/helpers/projects_helper.rb
create test/unit/project_test.rb
create test/fixtures/projects.yml
create db/migrate
create db/migrate/001_create_projects.rb
route map.resources :projects

生成器不仅生成了 model, controller, view, 还生成了完整的 migration
脚本,以及在 routes.rb 里生成了一个映射:map.resources :projects

,对

于新生成 controller 来说,这是用于 REST 方面的最后一个映射项目。我们现在先
不深入研究 routes.rb, 还是先一步一步地看看我们刚才用生成器说生成的东西
吧!

1.6 Model
就好像我们之前提过的,在 Rails 里,REST 的资源是由 controller 和
model 来组成的。所谓 model,就是一个普通的 ActiveReocrd 类,继承自
ActiveRecord::Base:

class Project < ActiveRecord::Base
end

所以,对于 model 这部分来说,没什么新鲜的东西。不过,别忘了创建这个表:
> rake db:migrate

1.7 Controller

生成的 controller “ProjectsController” 是一个具有 CRUD 行为的
controller,它可以操作 “Project” 这个资源(还记得刚才创建的 projects 表
吧?)。
这就表明,一个 controller 属于某一个特定的资源,并且具有一些符合标
准的动作来执行 CRUD 的操作。例如:

Listing 1.1: ontrack/app/controllers/projects controller.rb
class ProjectsController < ApplicationController
# GET /projects
# GET /projects.xml
def index...
# GET /projects/1
# GET /projects/1.xml
def show...
# GET /projects/new
def new...
# GET /projects/1;edit
def edit...
# POST /projects
# POST /projects.xml
def create...

# PUT /projects/1
# PUT /projects/1.xml
def update...
end
# DELETE /projects/1
# DELETE /projects/1.xml
def destroy...
end

如果我们来看看刚才生成的 ProjectController,会发现其实并没有什么新
鲜的东西,无非也就是这么一些操作:创建(create),读取(retrieve),更新
(update),删除(delete)这些操作。需要强调注意:这些操作都是针对 Project
这个资源的。Controller 和 Action 看起来都很普通,但是仔细看一下,每个
Action 都会有一些注释,这些注释表明了 url 和 http 所使用的动作。这些注释
所体现的,就是 REST 风格的 URL。接下来的章节,我们会仔细分析一下这些 URL
的内容。

1.7.1 REST 风格的 URL

我们之前已经十分强调过,REST 风格的 URL,并不像以往的 Rails 应用一
样,是由 controller/action/model id 所组成的,例如 /projects/show/1 。相
反,REST 风格的 URL 仅仅由 controller 和资源的 id 所组成,例如
/projects/1。
注意:我们一直再强调“资源”这个词。
URL 中没有了 action,我们也就看不到该对资源进行什么操作了。
“/projects/1”这个 URL 到底应该是显示一个资源,还是应该删除一个资源?答
案来自我们之前提到的 http 协议的 4 个动作。

下面的列表可以展示 http 协议的 4 个动作是如何和 REST 风格的 URL 所关
联的,并且什么样的组合,对应什么样的 action:

我们可以看出来,除了 POST 动作,其他三个 URL 都是相同的,原因很简
单,因为要创建的那个资源还不存在呢。既然有三个 URL 都是相同的,那该怎么区
分呢?其实是 http 协议的 4 个动作决定该调用哪个 action。我们没有使用
action,这就使得 我们不会写多余的 URL 和资源了。
现在我们只需要 2 个 URL: /projects/1 和 /projects ,如果是传统的方
式,我们需要 /projects/new , /projects/show/1 , /projects/delete/1,
/projects/update/1 4 个 URL.

有一点需要注意,之前我们也提到过,浏览器只能理解 POST 和 Get 两个
动作,所以,当输入 http://localhost:3000/projects/1 的时候,会调用

show

这个 Action.
所以,Rails 提供了一个辅助的方案来声称一个用于删除一个资源的链接:
Delete 这个动作被放在一个隐藏的提交字段里(hidden field)提交给服务器;在
创建新的资源的时候,也是适用相同的方法。这些内容都会在以下的章节里介绍。

1.7.2 在 Action 中使用 respond_to

我们已经知道,我们可以通过一个 指定资源 id 的 URL 和 http 协议的动作
的组合,来调用一个 action。这使得一个 URL 看起来非常简洁:一个 URL 就指定
了哪个资源要被操作,而不像以往那样去指定一个 Action。
那么到底有什么样的需求,会让我们去使用这种风格的 URL 呢?
一个 REST 的 action 可以应付不同的客户端所需要的不同的信息格式。对
于一个 WEB 迎来说,典型的客户端当然就是浏览器了,但是别忘了,对于一个 web
service 来说,它需要的则是 xml 格式的信息;对于一个 RSS 阅读器来说,它需
要的则是 RSS 格式的信息。
对于客户端的请求,我们已经使用 scaffold 生成器生成了 CRUD 4 个方法
来处理。下面的代码片断展示了 “show” 这个 action 中 respond_to 的使用方
法:

Listing 1.2: ontrack/app/controllers/projects controller.rb
# GET /projects/1
# GET /projects/1.xml
def show
@project = Project.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @project.to_xml }
end
end

respond_to 方法是用了代码块(block)技术,在这个例子中,代码块(block)
部分处理了 2 种格式的信息:html 和 xml。针对客户端不同的请求,会执行代码
块(block)中不同的部分。例如如果客户端请求的是 html 信息,那么会执行
“format.html”,如果客户端请求的是 xml 信息,那么会执行“format.xml”部
分。

如果 format.html 代码块里是空的,那么默认就显示 show.rhtml。
控制 respond_to 可以通过 2 种方式:一是在 http-header 里面;二是在
URL 后面追加一些东西,也就是改变 URL 的样式。
这两种方式我们都会介绍的。

1.7.3 在 http-header 中使用 accept 变量。

我们先来看看控制 respond_to 的第一种方式。设置 http-header 的方式
很简单,你可以使用一个工具“curl”。好,我们首先启动 webrick:

> ruby script/server webrick
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2006-12-30 18:10:50] INFO WEBrick 1.3.1
[2006-12-30 18:10:50] INFO ruby 1.8.4 (2005-12-24) [i686-darwin8.6.1]
[2006-12-30 18:10:50] INFO WEBrick::HTTPServer#start: pid=4709 port=3000

在浏览器中会看到如下的效果:

然后,我们使用 curl 这个工具来请求 project 这个资源,并且想要得到 xml 格
式的信息。

> curl -H "Accept: application/xml" \
-i -X GET http://localhost:3000/projects/1
=>
HTTP/1.1 200 OK
Connection: close
Date: Sat, 30 Dec 2006 17:31:50 GMT
Set-Cookie: _session_id=4545eabd9d1bebde367ecbadf015bcc2; path=/
Status: 200 OK
Cache-Control: no-cache
Server: Mongrel 0.3.13.4
Content-Type: application/xml; charset=utf-8
Content-Length: 160
<?xml version="1.0" encoding="UTF-8"?>
<project>
<desc>Future of Online Marketing</desc>
<id type="integer">1</id>
<name>Wunderloop</name>
</project>

Rails 的处理器调用了 show 这个 action。因为我们已经在 http-header 里通过
“Accept: application/xml”指定了需要 xml 的信息,所以,“show” 这个
action 就调用了 format.xml,然后返回给我们 xml 的信息。

Curl 这个工具不仅可以方便的测试返回不同格式的情况,我们还可以用它
来测试一些浏览器所不支持的命令,例如,我们要删除 id = 1 的这个资源:

> curl -X DELETE http://localhost:3000/projects/1
=>
<html><body>You are being
<a href="http://localhost:3000/projects">redirected</a>.
</body></html>

这次发送的请求使用了 http 协议的 DELETE 方法。Rails 得到了 http 的这个动
作,然后就会调用 destroy 方法。注意 URL 看起来没什么不同,不同的是 http
包含的动作。

1.7.4 改变 URL 的样式

第二种控制 action 返回不同格式的信息的方法,就是改变 URL 的样式。假
设我们没有删除 id=1 这个资源 project,那么我们通过以下这个方式来在浏览器
里显示这个资源:
http://localhost:3000/projects/1.xml

这里 MAC 的用户要注意,这种情况下 firefox 表现的不错,但是 Safari
就差些,因为 Safari 会忽略 xml 格式的信息。甚至 firefox 会把 xml 显示的非
常漂亮!

到这里,我们已经知道一个 controller 和 URL 是如何工作的,在接下来的
2 个章节里,我们会学习如何在 controller 和 view 里使用和构造这种 REST 风
格的 URL。

1.8 REST 风格的 URL 和 View

View 是系统界面和用户之间的一个表现,用户通过链接和按钮来和系统进
行交互。传统上 Rails 的开发人员使用 link_to 这个 helper 方法来构造一个链
接,这个方法需要一个 hashmap, hashmap 由 controller 和 action 组成;此
外,还可以传递一些其他的参数。例如:
link_to :controller => "projects", :action => "show", :id => project
=>
<a href="/projects/show/1">Show</a>

我们马上就意思到,这个 link_to 方法并不能很好的用于我们的 REST 思
想:REST 不会在 URL 里包含 action。
那么重要的就是通过链接和按钮,我们要把 http 协议的 4 个动作和 URL 一
起传递给服务器。
所以,我们会看到 Rails 的改进之处:我们仍然使用 link_to 去创建链
接,但是,我们不会再使用 hashmap, 而是使用一个“path”的方法。首先用一个
例子,来说明如何创建一个链接去调用 controller 的 show action。请看好,我
们不会再使用 controller, action, 和 id

link_to "Show", project_path(project)
=>

了:

<a href="/projects/1">Show</a>

关于 path 和下面要说的 url 这两类 helper 方法,请大家千万不要疑惑他
们是从哪来的。我们可以这么认为,是 Rails 动态地创造了他们。我们只要使用
就可以了!

传统的 link_to 所生成的链接中包含了 controller 和 action,相对比,使
用新的 “project_path” 所创建的链接,只包含了 controller 和 资源的 id –
毫无疑问,这是一个 REST 风格的 URL。因为链接默认的是一个“Get”请求,
Rails 能够知道这一点,所以就会去调用 show action。
对于每一个资源,rails 都会有 7 个标准的 path 方法,这些可以从表 1.2
中看到。

每一个 path 方法都关联一个 http 协议的动作,一些请求(例如 show,
create),可以通过 http 协议的 Get 或 Post 传递给服务器;但是有一些请求,
如 update,delete,则需要一些其他的方式(如使用隐藏的变量)传递给服务器,
因为浏览器并不知道 PUT 和 DELETE 动作。接下来的章节我们会仔细的介绍。

进一步看看这个表,我们也会发现 4 个 http 动作,并不足以包含全部的
CRUD 操作。前 2 个方法使用 Get 的方式会工作的很好,但是对于
new_project_path 和 edit_project_path 就不同了。

1.8.1 New 和 Edit

用户如果点击一个“新建”链接,那么会使用 Get 动作来对服务器发送一个
请求。下面的例子表明,生成的链接是由 controller 和一个“new”action 组成
的。
link_to "New", new_project_path
=>
<a href="/projects/new">New</a>

这是对 REST 思想的一种破坏?或许乍看之下确实如此。但是如果你仔细
看,那么一切都会清晰,“new”并不是一个 CURD 的 action,它更像一个建立一
个新的资源之前的准备的动作。真正的 CRUD 中的 create 被调用,是在新的 form
被提交的以后才执行的。这个链接当然也没有资源的 id—因为资源还没有被创
建。一个链接如果没有资源的 id,那么就不应该被称为 REST 的 URL,因为 REST 的
URL 总是会指定一个资源的 id。所以,这个 “new” action 仅仅因该用来显示一
个新的 form 的页面而已。
对于 edit_project_path ,也是同样的道理。它引用了一个资源,但是仅
仅是在调用 update action 之前的准备工作。真正的 update action 是在页面被
提交以后才执行的。edit_project_path 和 new_project_path 唯一的区别就是前
者需要使用一个资源的 id。按照 REST 的规则,资源的 id 放在 controller 的后
面:/project/1 。但是如果仅仅使用 Get 动作来提交这个 URL,那么 Rails 将会
认为你要调用的是 show action。为了防止这一点,edit_project_path 方法扩展
了一下生成的链接,例如:

link_to "Edit", edit_project_path(project)
=>
<a href="/projects/1;edit">Edit</a>

这样,我们就能理解为什么允许 edit_project_path 和 new_project_path
生成的链接里带有 action 了,因为他们两个都不是 REST 的 CRUD URL,他们仅仅
是准备工作。还有其它的一些 URL 和这两个很相似,我们会在后面的章节介绍。

1.8.2 在 form 中使用 path 方法:Create 和 Update

传统的方式上,我们使用 form_tag 或 form_for 来创建一个 form:
<% form_for :project, @project, :url => { :action => "create" } do |f|
%>
...
<% end %>

在 REST 应用中,这个 :url hashmap 会被 path 方法给取代:
“project_path” 创建新的资源所使用的 form
“project_path(:id)”编辑一个资源所使用的 form

a) 创建资源所使用的 form
form 使用 post 动作向服务器提交信息,“project_path”方法并不会有资源 id
作为参数,这样,生成的 URL 就应该是“/projects”这个样子。当提交到服务器
以后,就会调用 create action。
form_for(:project, :url => projects_path) do |f| ...
=>
<form action="/projects" method="post">

b) 编辑一个资源所使用的 form
按照 REST 的思想,一个更新的操作是使用 http 协议的 PUT 动作来发送的。
但是,正如我们所知道的,浏览器只明白 Post 和 Get 动作。解决的办法就是使用
form_for 方法里的 :html 参数。

form_for(:project, :url => project_path(@project),
:html => { :method => :put }) do |f| ...
=>
<form action="/projects/1" method="post">
<div style="margin:0;padding:0">
<input name="_method" type="hidden" value="put" />
</div>

Rails 生成了一个隐藏的字段来代替 http 的 put 动作。提交以后,Rails 会检查
这个变量,然后判断是否去调用 update 方法。

1.8.3 删除

恐怕我们已经发觉了,用于显示和删除一个资源,所使用的 path 方法都一
样:
link_to "Show", project_path(project)
link_to "Destroy", project_path(project), :method => :delete

唯一的不同就是 删除的时候,使用了一个变量 :method,用它来表示 http 的
DELETE 动作。因为浏览器不支持 DELETE 动作,所以,Rails 会生成一些
javascript 来解决这个问题:

link_to "Destroy", project_path(project), :method => :delete
=>
<a href="/projects/1"
onclick="var f = document.createElement(’form’);
f.style.display = ’none’; this.parentNode.appendChild(f);
f.method = ’POST’; f.action = this.href;
var m = document.createElement(’input’);
m.setAttribute(’type’, ’hidden’);
m.setAttribute(’name’, ’_method’);
m.setAttribute(’value’, ’delete’); f.appendChild(m);f.submit();
return false;">Destroy</a>

这段 javascript 会生成一个 form,把 http 的 DELETE 动作放在隐藏变量里传递
给服务器,然后,Rails 会判断这个变量,决定是否去调用 destroy 方法。

1.9 Controller 里的 URL 方法

在 View 中,我们已经使用了一些新的 helper 方法(也就是 path 方法)来
生成了 REST 风格的 URL,那么 controller 自然也需要一些新的东西来处理
redirect 等请求。在 controller 中,我们使用“url”helper 方法,来生成正确
的 REST 风格的 URL。
project_url 对应 project_path

projects_url 对应 projects_path
和 “path” 方法向对比,“url” 方法生成了一个完整的 URL 地址,包括
协议,主机,端口,以及路径。
project_url(1)
=>
"http://localhost:3000/projects/1"
projects_url
=>
"http://localhost:3000/projects"

在 Rails 应用的 controller 里,”url” 方法用在 redirect_to 方法里,
取代传统的 controller/action 的方式。
redirect_to :controller => "projects", :action => "show",
:id => @project.id
在REST应用中应该这么写:
redirect_to project_url(@project)

对于这一点,你可以把 destroy action 作为一个例子去看看:在一个资源
被删除以后,使用 project_url 去显示全部的资源,而不是像以往一样使用
controller,action 作为参数。
Listing 1.3: ontrack/app/controllers/projects controller.rb
def destroy
@project = Project.find(params[:id])
@project.destroy
respond_to do |format|
format.html { redirect_to projects_url }
format.xml { head :ok }
end

end

1.10 REST 风格的路由

到目前为止,我们介绍了REST的内容,以及在 链接,form,controller中所
适用的一些新的helper方法。但是我们没有解释那些helper方法是从哪来的?决定
那些方法存在的,以及指定那些方法会调用哪个controller的哪个action,就是一
个文件,那就是 /config/routes.rb。

map.resources :projects

这个配置是由我们上面适用 scaffold 生成器生成的。生成器生成了一个路
由,当处理请求时,controller 需要这个路由才能知道调用哪个 action。
此外,resources 生成了 path 和 url 的 helper 方法去操作“project”
这个资源。

map.resources :projects
=>
Route Generated Helper
----------------------------------------------------------projects projects_url, projects_path
project project_url(id), project_path(id)
new_project new_project_url, new_project_path
edit_project edit_project_url(id), edit_project_path(id)

1.10.1 习惯

要进行 REST 风格的开发,就必须遵循 REST 方式的命名习惯,多针对 CRUD
四个操作而言。下面的 link_to 将会产生如下的 html:
link_to "Show", project_path(project)
=>
<a href="/projects/1">Show</a>

不管是 link_to 方法中,还是生成的 html 中,都没有去指定要调用的
action,Rails 会知道,如果使用 Get 方式来调用这个 URL,那么就是去调用 show
这个 action。因此,controller 里就必须有一个名字为“show”的 action。对于
index, update, delete,create, destroy,new,edit,也都是相同的习惯,所以,
每一个 REST 的 controller 都必须实现这几个方法。

1.10.2 定制路由

通过以下一些选项,REST 的路由可以去适应应用的一些特殊需求:
:controller.

指定使用哪一个controller

:path prefix.

生成的URL的前缀。

:name prefix.

Helper方法的前缀。包括 url方法和path方法。

:singular. 对于一个路由,命名一个唯一的名字。

下面的例子创建了一个路由,用于新建一个 Sprint 资源,sprint 的信息我们会在
下面的章节中介绍。

map.resources :sprints,

:controller => "ontrack",
:path_prefix => "/ontrack/:project_id",
:name_prefix => "ontrack_"

在这个 URL 中,我们适用了 :path_prefix,意味着每一个 URL 都必须以
/ontrack/+project id 开始,对应的 controller 应该是 OntrackController。
因此这个URL http://localhost:3000/ontrack/1/sprints根据路由的规
则,会调用 OntrackController的index 方法,而这个URL
“http://localhost:3000/ontrack/1/sprints/1
”则会调用 show 方法。

:path_prefix 定制了 URL 的格式,那么 :name_prefix 则会修改 helper 的
方法的名字:
ontrack_sprints_path(1)
=>
/ontrack/1/sprints
or
ontrack_edit_sprint_path(1, 1)
=>
/ontrack/1/sprints/1;edit

1.11 嵌套的资源

当适用嵌套的资源的时候,REST 的开发会变得更加有趣。在这个章节,你
会更加明白简洁的 URL 的 URL 的重要性,也会对 REST 的理念有更清晰的理解。

嵌套的资源,也就是所说的父—子关系的资源。在 Rails 中,也就是一种
model 的关系:1 对多关系。在我们这个 ontrack 的例子项目中,就好像
projects 和 iterations 的关系一样。嵌套的 REST controller 仍然负责处理某
一个资源的操作,但是对于一个“子”controller 来说,它还必须获得“父”资
源的信息。
听起来很复杂,不过阅读完这个章节,你很快就会完全明白的。
根据 Rails 的 REST 方式,Rails 将资源的这种主—从关系反映到 URL 里,
并且保持了 URL 简洁这一重要的特性。在这个 ontrack 例子里,我们会通过两个资
源 project 和 iteration 来描述这一点。
首先,我们创建 iteration 这个资源,并且创建 iterations 这个表。

> ruby script/generate scaffold_resource iteration name:string \
start:date end:date project_id:integer
> rake db:migrate

Projects 和 Iterations 是“1 对多”的关系,所以我们要修改一下 model:

Listing 1.4: ontrack/app/models/project.rb
class Project < ActiveRecord::Base
has_many :iterations
end
Listing 1.5: ontrack/app/models/iteration.rb
class Iteration < ActiveRecord::Base
belongs_to :project
end

除了创建了 model, controller 和 view, 生成器同时在 config/routes.rb 里,
创建了一个路由的定义项:

map.resources :iterations

这个路由项和资源 project 的非常类似,不过我们别忘了 iteration 和 project
的关系。但是很明显,这个路由项并没有考虑这一点。例如,new_iteration_path
方法生成了一个 URL “/iterations/new”,并没有包含这样一个重要的信息:这
个 iteration 应该属于哪个 project?
所以,我们应该意识到,如果没有一个“父”资源,那么一个“子”资源是
没有任何意义的!Rails 会把这种 主—从的关系反映到 URL 里,所以,我们需要
修改一下默认生成的路由项:
map.resources :projects do |projects|
projects.resources :iterations
end

现在这个路由项成为了一个嵌套的资源了,而且你要操作 iteration 这个资
源,就必须基于 project 这个资源之上。与之相对应的 URL 应该是下面这个样
子:
/project/:project_id/iterations
/project/:project_id/iterations/:id

例如,如果我输入了这个URL
http://localhost:3000/projects/1/iterations
将会调用 IterationController 的 index 方法,在这个方法里,也可以通过所提
交的参数 :project_id 来得到资源project。
值得注意的是,URL关联一个资源的这个特性,其实就是等同于下面的关
系:

/projects/1/iterations <=> Project.find(1).iterations

嵌套的 URL 仍然是简洁的 URL――URL 中仍然只表明的资源,而没有
action。简单的说,如果一个资源使用 2 个 REST 风格的 URL 构成,那么它就是一
个嵌套的资源。下面这个调用 show action 的 URL 能让我们清晰的了解这一点:

http://localhost:3000/projects/1/iterations/1

1.11.1 嵌套资源在 controller 的代码

新生成的 IterationController 并不知道它现在已经要处理嵌套的资源
了――这意味着每个方法,都至少应该得到“父”资源 project。所以,现在的
index 方法,仍然是显示全部的 iterations,尽管 URL 已经表明应该显示的是某一
个 project 下的全部 iterations:

Listing 1.6: ontrack/app/controllers/iterations controller.rb
def index
@iterations = Iteration.find(:all)
respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @iterations.to_xml }
end
end

我们必须重新写 index 方法,以保证我们只拿某一个 project 下的
iterations。
Listing 1.7: ontrack/app/controllers/iterations controller.rb
def index
project = Project.find(params[:project_id])

@iterations = project.iterations.find(:all)
...
end

我们必须要让 controller 里全部的方法都能工作在 以
/projects/:project_id 为前缀的 URL 上。这就意味着,我们不仅要修改 index
方法,create, update 等等方法也必须进行修改。下面的章节我们会逐步介绍。

1.11.2 在 “path” 和 “url”

helper 方法里使用参数

在 config/routes.rb 里新增加的资源,不仅仅只是增加了一个新的路由的
定义,同时也自动地增加了新的 helper 方法。正如定义的路由那样,新的 helper
方法需要一个 project-id 作为参数。例如 通过 “iterations_path”这个
helper 方法,来得到某一个 project 下的全部的 iterations。Helper 方法的名
字并不是以嵌套的方式命名的,所不同的只是传递的参数不一样。对于嵌套式的资
源来说,“子”资源的 helper 方法的参数,通常都是“父”资源的资源 id,在这
个例子里就是 project 的 id。
下面作为例子,我们就来创建一个链接,这个链接可以显示一个 project 下
的全部 iterations。

link_to "Iterations", iterations_path(project)
=>
<a href="/projects/1/iterations">Iterations</a>

其中 iterations_path 的参数“project”就是一个资源的对象。
为了更好的理解这个方法的作用,我们把它放到一个页面里来看看:
Listing 1.8: ontrack/app/views/projects/index.rhtml

...
<% for project in @projects %>
<tr>
<td><%=h project.name %></td>
<td><%=h project.desc %></td>
<td><%= link_to "Iterations", iterations_path(project) %></td>
<td><%= link_to "Show", project_path(project) %></td>
<td><%= link_to "Edit", edit_project_path(project) %></td>
<td><%= link_to "Destroy", project_path(project),
:confirm => "Are you sure?", :method => :delete %></td>
</tr>
<% end %>
...

那么如果我们传递给 iterations_path 错误的参数会怎么样呢?那将会导
致所有的功能都实效,而且页面的显示也会不正常。例如下面这个现实全部
iterations 的页面:

Listing 1.9: ontrack/app/views/iterations/index.rhtml
...
<% for iteration in @iterations %>
<tr>
<td><%=h iteration.name %></td>
<td><%=h iteration.start %></td>
<td><%=h iteration.end %></td>
<td><%= link_to "Show", iteration_path(iteration) %></td>
<td><%= link_to "Edit", edit_iteration_path(iteration) %></td>

<td><%= link_to "Destroy", iteration_path(iteration),
:confirm => "Are you sure?", :method => :delete %></td>
</tr>
<% end %>
...

我们看到,第一个参数现在都是 iteration 对象。这就导致所有的方法都
失效了---原因很明显,因为在 /config/routes.rb 里,我们定义的是第一个参数
应该是 project id, 而不是 iteration id。如果想要这个页面显示正常,需要作
如下修改:

Listing 1.10: ontrack/app/views/projects/index.rhtml
...
<% for iteration in @iterations %>
<tr>
<td><%=h iteration.name %></td>
<td><%=h iteration.start %></td>
<td><%=h iteration.end %></td>
<td><%= link_to "Show", iteration_path(iteration.project,
iteration) %></td>
<td><%= link_to "Edit", edit_iteration_path(iteration.project,
iteration) %></td>
20 1 RESTful Rails
<td><%= link_to "Destroy", iteration_path(iteration.project,
iteration), :confirm => "Are you sure?",
:method => :delete %></td>
</tr>
<% end %>

...

为了让参数的顺序正确,我们还可以用另一种显示指定参数的方式:
iteration_path(:project_id => iteration.project, :id => iteration)

如果您觉得用对象作为参数不够清晰,那么就可以考虑一下这个方式。

1.11.3 增加新的 Iteration

我们仍然是在当前的例子中增加这个功能。为了实现这个功能,我们只需要
简单的修改一下 ProjectController 的 index.rhtml :

Listing 1.11: ontrack/app/views/projects/index.rhtml
...
<% for project in @projects %>
<tr>
<td><%=h project.name %></td>
<td><%=h project.desc %></td>
<td><%= link_to "Iterations", iterations_path(project) %></td>
<td><%= link_to "Show", project_path(project) %></td>
<td><%= link_to "Edit", edit_project_path(project) %></td>
<td><%= link_to "Destroy", project_path(project),
:confirm => "Are you sure?", :method => :delete %></td>
<td><%= link_to "New Iteration", new_iteration_path(project)
%></td>

</tr>
<% end %>
...

这里我们使用了 “new_iteration_path”这个 helper 方法,并且把
project 这个对象作为参数传了进去。这个 helper 方法会生成如下的 html 语
句:

link_to "New Iteration", new_iteration_path(project)
=>
<a href="/projects/1/iterations/new">New iteration</a>

如果您点击这个链接,那么就会调用 IterationController 的 new 方法,
在这个方法里,你就可以得到 project id ( 在这个例子里就是“1”)。这样,用
于创建新的 iteration 的 form 就可以使用这个 project id 了:

Listing 1.12: ontrack/app/views/iterations/new.rhtml
<% form_for(:iteration,
:url => iterations_path(params[:project_id])) do |f| %>
...
<% end %>
=>
<form action="/projects/1/iterations" method="post">

这个“params[:project_id]”其实也可以省略,Rails 会自动处理这个变
量,也就是说,上面的代码,和下面的是等效的:
form_for(:iteration, :url => iterations_path)

因为我们之前在 /config/routes.rb 里定义了路由,这样就确保 使用
post 方式提交 “/projects/1/iterations”链接时,一定会调用
IterationController 的 create 方法。
下面,我们要修改一下 IterationController 里的 create 方法,以保证我
们创建的 iteration 是基于某一个 project 之上的:
Listing 1.13: ontrack/app/controllers/iterations controller.rb
1 def create
2

@iteration = Iteration.new(params[:iteration])

3

@iteration.project = Project.find(params[:project_id])

4
5

respond_to do |format|

6

if @iteration.save

7

flash[:notice] = "Iteration was successfully created."

8

format.html { redirect_to iteration_url(@iteration.project,

9

@iteration) }

10

format.xml { head :created, :location =>

11

iteration_url(@iteration.project, @iteration) }

12

else

13

format.html { render :action => "new" }

14

format.xml { render :xml => @iteration.errors.to_xml }

15
16

end
end

17 end

在第“3”行,我们使用了“project_id”这个参数,在第“8”行和第
“11”行,我们使用了 “url”helper 方法。
下面我们还需要修改一些显示,编辑 iteration 的链接—因为我们必须把
iteration 和 project 关联在一起。

Listing 1.14: ontrack/app/views/iterations/show.rhtml
...
<%= link_to "Edit", edit_iteration_path(@iteration.project, @iteration)
%>
<%= link_to "Back", iterations_path(@iteration.project) %>

1.11.4 编辑 Iteration

为了能够编辑 iteration,至少需要修改 2 个地方。1〉在视图中的
form_for 方法的参数,目前的参数只有 iteration 一个,还需要增加 project
id。
form_for(:iteration,
:url => iteration_path(@iteration),
:html => { :method => :put }) do |f|

需要修改成:

form_for(:iteration,
:url => iteration_path(params[:project_id], @iteration),
:html => { :method => :put }) do |f|

我们还需要修改 update 方法,修改的目的是一样的。

Listing 1.15: ontrack/app/controllers/iterations controller.rb
1 def update
2

@iteration = Iteration.find(params[:id])

3
4

respond_to do |format|

5

if @iteration.update_attributes(params[:iteration])

6

flash[:notice] = "Iteration was successfully updated."

7

format.html { redirect_to iteration_url(@iteration) }

8

format.xml { head :ok }

9

else

10

format.html { render :action => "edit" }

11

format.xml { render :xml => @iteration.errors.to_xml }

12

end

13

end

14 end

第“7”行需要修改成:

format.html { redirect_to iteration_url(@iteration.project,
@iteration) }

到目前为止,新增加的资源 Iteration 的操作大部分都可以正常工作了,
但是还有一些细节的地方我们没有处理,这个就留给您作为“练习”来完成吧!

1.12 自定义 Action

我们已经知道,在 /config/routes.rb 里定义的路由,会自动生成对资源
的 CRUD 的操作。但是我们如何处理那些并不是 CRUD 的操作?下面我们就用一个例
子来说明这一点。例如我们在 ProjectController 里有一个 close 的方法。这个

close 并不是真正的删除一个资源,而只是把给这个资源设置一个标志:表示这个
资源被关闭了。
首先修改一下数据库:

> ruby script/generate migration add_closed_to_projects
exists db/migrate
create db/migrate/003_add_closed_to_projects.rb
Listing 1.16: ontrack/db/migrate/003 add closed to projects.rb
class AddClosedToProjects < ActiveRecord::Migration
def self.up
add_column :projects, :closed, :boolean, :default => false
end
def self.down
remove_column :projects, :closed
end
end
rake db:migrate

现在,我们在 IteratinController 的 index.rhtml 上创建一个 close 的链
接。

Listing 1.17: ontrack/app/views/projects/index.rhtml
<% for project in @projects %>
<tr id="project_<%= project.id %>">
<td><%=h project.name %></td>
<td><%= link_to "Show", project_path(project) %></td>

...
<td><%= link_to "Close", <WHICH_HELPER?> %></td>
</tr>
<% end %>

现在有2个问题摆在我们面前:
1.使用 http 协议的哪个动作来发送这个请求呢?
2.对于这个链接,该如何生成那些 helper方法呢?

因为这个 close 动作并不是CRUD中的任何一个,所以Rails 也不知道该用
http的哪个来做这个事情。不过既然 close 也是 update 中的一种,所以应该使
用post来发送这个请求。
我们还是得在 /config/routes.rb 里定义这个路由,当然定义完路由之
后,就会有相应的path和url的helper方法了。
因为这个close的操作,仍然是针对projects 这个资源的,所以,我们可以
在定义路由的时候,使用一个名字叫“member”的hashmap,这个hashmap 的key,
就是自定义action的名字,hashmap的value,就是所使用的http的动作。

map.resources :projects, :member => { :close => :post }

hashmap 的value可以使用 :get, :put, :post, :delete, :any。如果使用
了:any,那么可以用http的任何动作来发送这个请求。

定义完这个路由后,我们就可以使用helper方法了:
<td><%= link_to "Close", close_project_path(project) %></td>

因为我们定义的是“:member => { :close => :post }”,所以,这个请求
只能以post的方式来发送,如果使用其它方式如“get”,那么请求就是无效的。

为了安全起见,我们还是把它改成用按钮的方式来发送,幸运的是我们可以
使用Rails 提供的button_to 来做这件事情:
<td><%= button_to "Close", close_project_path(project) %></td>
=>
<td>
<form method="post" action="/projects/1;close" class="button-to">
<div><input type="submit" value="Close" /></div>
</form>
</td>

现在我们要做的就是写完 ProjectController中的 close 方法:

Listing 1.18: ontrack/app/controllers/projects controller.rb
def close
respond_to do |format|
if Project.find(params[:id]).update_attribute(:closed, true)
flash[:notice] = "Project was successfully closed."
format.html { redirect_to projects_path }
format.xml { head :ok }
else
flash[:notice] = "Error while closing project."
format.html { redirect_to projects_path }
format.xml { head 500 }
end
end
end

除了“:member”,我们还可以使用“:collection”,“:new”。
“:collection”的用途是:所操作的资源不是一个,而是很多个。下面是一个用
“:collection”方式得到一个资源的列表的例子:
map.resources :projects, :collection => { :rss => :get }
--> GET /projects;rss (maps onto the #rss action)

所以,有的时候,“:member”更多的是更新一个资源,而“:collection”
是得到一堆资源。

对于“:new”,一般用于那些还没有被保存的资源:
map.resources :projects, :new => { :validate => :post }
--> POST /projects/new;validate (maps onto the #validate action)

1.12.1 我们是否仍然“DRY”(Don’t Repeat Yourself)?

我们是否为了“DRY”原则?似乎是这样的:我们不仅在controller里定义
了action,同时在 /config/routes.rb 里也定义了一遍。
作为替换REST风格的调用的方式,您可以用传统的方式来调用一个方法:

<%= link_to "Close", :action => "close", :id => project %>

但是别忘了,即使用传统的方式,你也得在/config/routes.rb里定义一个
路由:“map.connect ’:controller/:action/:id’”。

1.13 自定义信息格式

目前 respond_to 可以返回如下的信息格式:

respond_to do |wants|
wants.text
wants.html
wants.js
wants.ics
wants.xml
wants.rss
wants.atom
wants.yaml
end

你可以通过增加新的MIME类型的信息来扩展这个功能。假设您已经开发了一
个“PIM”应用系统,现在你希望把地址信息用”vcard” 格式来传送。
要实现这一共能,首先你需要注册新的信息格式在
/config/environment.rb。

Mime::Type.register "application/vcard", :vcard

然后,我们来修改一下show action,使得返回的信息以vcard 的格式来传
送。

def show
@address = Address.find(params[:id])
respond_to do |format|
format.vcard { render :xml => @address.to_vcard }
...
end
end

这个 to_vcard 方法不是 ActiveRecord 的标准方法,所以必须按照 vcard
的标准来实现(RFC2426)。如果实现正确的话,那么通过下面的URL,就可以得到
正确的信息:“http://localhost:3000/addresses/1.vcard”。

1.14 在 REST 中使用 AJAX

在REST风格的系统中使用AJAX?非常简单,可以说这一小节没什么新鲜的玩
意要学习。您还是使用以前所使用的 remote 系列的helper 方法,只不过传递的
参数需要改变,现在使用 path helper 方法,而不是以前所使用的 contoller,
action 。下面的例子会让您更清晰的明白这一点:

link_to_remote "Destroy", :url => project_path(project),
:method => :delete
=>
<a href="#" onclick="new Ajax.Request("/projects/1",
{asynchronous:true, evalScripts:true, method:"delete"});
return false;">Async Destroy</a>

给您提醒一下:千万千万记得导入相应的ajax javascript 文件,不然当您
的ajax无效,而气得把键盘砸坏的时候,我们就无能为力了。导入相应的
javascript 文件相当的简单:
Listing 1.19: ontrack/app/views/layouts/projects.rhtml
<head>
<%= javascript_include_tag :defaults %>
...
</head>

这个“Destroy”链接将会调用ProjectsController的destroy方法。从逻辑
上来说现在一切正常:用户点这个链接,系统删除相应的资源。不过我们还是漏了
一点:在 respond_to 中,我们应该增加新的返回类新,也就是javascript类型。
Listing 1.20: ontrack/app/controllers/projects controller.rb
def destroy
@project = Project.find(params[:id])
@project.destroy
respond_to do |format|
format.html { redirect_to projects_url }
format.js # default template destroy.rjs
format.xml { head :ok }
end
end

可以看出来,唯一的改变就是增加了“format.js”。因为这个
“format.js”并不是一个要被执行的代码块,所以,Rails 会按照标准显示
destroy.rjs。

Listing 1.21: ontrack/app/views/projects/destroy.rjs

page.remove "project_#{@project.id}"

这个 rjs 文件从当前的浏览页面中删除了 “project_ID”这个DOM元素,
为了让这个删除起到效果,我们就需要在显示 project 上进行修改:

Listing 1.22: ontrack/app/views/projects/index.rhtml
...
<% for project in @projects %>
<tr id="project_<%= project.id %>">

这是一个遵循DRY原则和减少对当前系统的修改的一个好例子!也体现了
REST的优势,只需要在controller里增加一行,就可以处理javascript请求了。

同时也告诉了我们一个REST编程的原则:在 respond_to 外实现逻辑处理,
能够极大地降低重复的代码。

1.15 测试

不管开发REST风格的应用是多么的让我们激动,我们也不能忘记最重要的一
个朋友:测试!
之前我们写了那么多代码,但是一次单元测试测试都没运行过!下面我们来
运行一下吧!
> rake
...
Started

EEEEEEE.......

好消息是,所有的单元测试和功能测试都可以运行。坏消息是,关于
IterationsController的7个功能测试,全部失败!
如果测试用例抛出异常,那么很明显的—这里存在一些错误。我们遇到的错
误也很明显:所有的IterationsController的测试用例都是scaffold 来生成的,
并没有一个“父”资源的关联—还记得吗,我们已经让iterations 资源成为了
projects 资源的“子”资源。
为了让我们的测试用例通过,我们必须给每个测试方法都增加
project_id。
例如:
Listing 1.23: ontrack/test/functional/iterations controller test.rb
def test_should_get_edit
get :edit, :id => 1, :project_id => projects(:one)
assert_response :success
end

当然了,你需要加载必要的fixtures:
fixtures :iterations, :projects

改完全部的测试用例以后,我们发现还是有2个测试用例无法通过:
test_should_create_iteration
test_should_update_iteration

失败的代码来自这行“assert_redirected_to
iteration_path(assigns(:iteration))
”。错误是非常显然的:我们已经知道iteration_path的第一个参数应该是
project id。我们同样需要修改一下:

assert_redirected_to iteration_path(projects(:one), assigns(:iteration))

另外,在使用 redirect 断言的时候,path helper 方法的使用,是REST和
非REST风格应用的唯一区别。

1.16 REST 风格的客户端:ActiveResource

我们总是把 ActiveResource 和 REST一起提及。ActiveResource 是一个
Rails 的库,用来开发基于REST的WEB服务客户端。这种基于REST的客户端,也是
适用 http 的4个标准的动作来和服务器通信。
ActiveResource 并不是Rails 1.2 的一部分,但是您可以使用svn 从网站
下载它的代码:

> cd ontrack/vendor
> mv rails rails-1.2
> svn co http://dev.rubyonrails.org/svn/rails/trunk rails

ActiveResource把客户端资源抽象成一个类,这个类继承自
ActiveResource::Base。例如通过下面的例子,我们来调用服务器上的project 资
源:

require "activeresource/lib/active_resource"
class Project < ActiveResource::Base
self.site = "http://localhost:3000"
end

可以看到,我们导入了ActiveResource 的库,然后,服务器的地址赋给了
类的变量 site。这个 Project 类,把服务器上的资源抽象成了一个客户端的类,
这就让开发人员觉得他们就好像操作一个ActiveRecord 一样。
例如,我们用一个project id 和 find 方法去请求服务器上的一个资源:
wunderloop = Project.find 1
puts wunderloop.name

这个 find 方法会执行标准的GET动作:
GET /projects/1.xml

然后服务器返回xml格式的信息。客户端把xml信息转化成一个
ActiveResource对象 wunderloop,就好像一个ActiveRecord对象,可以得到和改
变它的任何属性。那么我们如何去更新一个资源呢?
wunderloop.name = "Wunderloop Connect"
wunderloop.save

save 方法会是用put 动作向服务器传递信息。
PUT /projects/1.xml

刷新一下你的浏览器看看,那条记录肯定被改变了。
和 find,save一样简单,创建一个新的资源也是非常方便:
bellybutton = Project.new(:name => "Bellybutton")
bellybutton.save

新的资源将会以post方式传递给服务器,并且保存到数据库里。

POST /projects.xml

刷新浏览器,会看到新建立的资源。最后,我们来看一下删除一个资源。
bellybutton.destroy

destroy方法将会以DELETE方式发送给服务器,并且删除这个资源。
DELETE /projects/2.xml

ActiveResource 使用http的4个动作来和服务器交互。对于REST的资源,它
提供了非常好的抽象,此外,在ActiveRecord中的许多方法,在ActiveResource中
仍然找得到。例如查找一个资源的全部记录:
Project.find(:all).each do |p|
puts p.name
end

相信使用ActiveResource可以开发出很好的松耦合的系统,我们不如马上去
下载ActiveResource的代码,亲自体验一下吧!

1.17 大结局

这个世界并不是非得需要REST。有很多的解决方案可以考虑,并且可以很容
易实现。大概更多的时候,是您可能现在正处于某个项目的中期,这时,您发现了
Rails这个新特性。我想如果您此时就开发一个单独的模块,并且使用REST的风
格,是毫无问题的。如果您要准备开始一个全新的项目,那么不妨考虑一下使用
REST,理由十分明显:清晰的架构,更少的代码,多个客户端的支持。

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.