oyaji's Blog
REST on Rails指南4:路由
通过上一讲我们了解到,RESTful设计的关键就是定义系统中的资源,这一讲我们将学习在Rails中,如何将请求路由到我们的资源,以及我们应该如何来处理它。
不过,有一点需要先说明:REST并不是Rails的一部分,在Rails出现之前,REST的概念已经存在很多年了,并且REST的应用也并不局限于Web,事实上,它也可以应用到其它各种应用软件的开发中。
资源就是控制器
在我们正式开始之前,我们需要首先明确,在Rails中,资源和model并不总是一对一的关系,有时资源仅仅只是你应用逻辑中的一个实体的抽象,并不需要 映射到你的数据库。但资源跟控制器总是一对一的,也就是每个资源都必须有一个与它相对应的控制器,并且你需要重新理解控制器,现在控制器只是REST接口 的具体实现,它的全部作用就是根据客户的请求返回资源的某种表示(HTML,XML等)。
所以,就像第2章讲的,我们不在需要去设计那无穷尽的API了,现在我们的控制器只需要定义7个方法:
- show,处理针对单个资源的GET请求
- create,处理POST请求,并将创建一个新资源
- update,处理PUT请求,并更新指定的资源
- destroy,处理DELETE请求,销毁一个资源
- index,处理针对多个资源的GET请求
- new,GET请求,返回一个用于创建资源的表单,
- edit,GET请求,返回一个用于更新资源的表单
Rails 会帮助我们将用户的请求路由到某个合适的方法,当然,你并不需要实现这全部的7个方法,如果你的系统不允许用户创建和修改资源,那么你只需要实现 index和show方法就可以了。
不过更有可能的一种情况是你觉得这7个方法根本不够,你当然可以选择向控制器添加新的方法,但这其实是因为你的设计遗漏了一些资源,因为我建议,在你向控制器添加新方法之前,最好先重新考虑下你的设计。
方法已经定义好了,下一步的任务就是将用户的请求路由到指定的方法,在router.rb中,你可能会看到这样的路由:
map.connect '/airports/:action/:id', :controller = 'airports'
这条语句将映射/airports/open/45到airports控制器的open方法,你可以通过params[:id]获取URL中的参数45。但是REST路由有些特殊,它需要同时考虑URL和请求的类型,因此同样是发往/airports/1的请求,如果是GET请求,它需要被路由到airports的show方法,而DELETE请求则需要被路由到DELETE方法。
不过幸运的是,从Rails1.2开始,我们不再需要通过map.connect来手动的配置REST路由,map.resources会帮我们搞定一切:
map.resources 'airports'
这句话将创建如下的路由规则:
- 针对/airports/ 的POST请求将被路由到create方法
- 针对/airports/1 的GET请求将被路由到show方法
- 针对/airports/1的PUT请求被路由到update方法
- 针对/airports/1 的DELETE请求被路由到destroy方法
- 针对/airports/ 的GET请求被路由到index方法
- 针对/airports/new 的GET请求被路由到new方法
- 针对/airports/1;edit 的GET请求被路由到edit方法
注意:最后一条逗号分隔的URL看起来很丑陋,但它们在Rails1.2.3中是合法的,不过不用苦恼,它们将在Rails2.0中被去除
现在我们已经完成了URL的路由,下面我们需要做的就是实现这些方法:
不过先别着急着码代码,从Rails1.2开始,我们有了一个新的生成器(generator):scaffold_resource,使用它我们可以很轻松的生成一个符合REST规范的Rails框架,它包含:
- 资源所对应的model
- 资源的migration文件
- 资源所对应的控制器,控制器已经包含了REST所需的7个方法的实现
- 这7个方法所对应的RHTML文件
- 一条映射用户请求的路由
让我们仍然从第三讲的例子开始,首先创建一个新程序,然后为它添加一个airport资源:
D:\study>rails REST
D:\study>cd REST
D:\study\REST>ruby script/generate scaffold_resource airport name:string designator:string
修改database.yml文件,设置好你的数据库链接,然后执行:
D:\study\REST>rake db:migrate
D:\study\REST>ruby script/server
现在定位你的浏览器到http://localhost:3000/airports/new,你应该已经可以创建一个新机场了,是不是很神奇?现在,让我们来看看airports_controller.rb,所有的东西都在那了。
你应该会在控制器代码中看到一些奇怪的respond_to块,这正是我们整个REST实现的关键所在,我们将在下一讲详细探讨respond_to的细节。
EST on Rails指南3: RESTful Design
通过上一讲,我们明白了为什么Web需要按照REST的方式来设计,而不是传统的面向对象编程的RPC方式,这一讲我们将通过一个实例来演示如何进行REST方式的Web设计,也就是让我们的设计变的RESTful。
航空公司的需求
我们假设你为一家航空公司工作,你的任务是为他们设计一个航班管理系统,它的功能包括:
- 允许公司员工通过Web前端来输入航班信息。航班信息包括航班的起飞和降落城市,以及起飞时间等。
- 允许客户通过手机查询他的航班信息。
- 允许第三方通过我们提供的API来获取我们的航班信息。
很没有难度,不是吗?如果你是个急性子,你甚至可能都顾不上将你无懈可击的设计转换成UML,就已经在你IDE的编辑框里输入了如下字符:
class FlightSchedule
def CancelFlight
......
但是慢着,在REST的世界里,我们不再需要操心这些,我们需要做的只有一件事情:
定义你的资源!
是的,就这一件,因为REST已经为我们定义好了用于操作这些资源的方法。
在这个例子里,我们首先会想到这几个资源:airports,airplanes,flights。当然可能还会有其它,但就让我们先从这几个开始吧!
我们首先要做的就是为这些资源分配URL,原则只有一个:尽可能的简单明了。
- /airports,通过这个URL可以访问所有的机场资源
- /airplanes,通过这个URL可以访问所有的飞机资源
- /flights,通过这个URL可以访问所有航班资源
还有:
- /airports/pudong,通过这个URL可以获取浦东机场的相关信息
- /airplanes/ZJ3543,通过这个URL可以获取编号为ZJ3543的飞机信息
- /flights/451,通过这个URL咋可以了解到航班451的起飞,降落城市已经起飞时间等信息。
方法已经准备好了
一旦你定义好了你的资源,整个设计也就完成了,因为,REST已经为你准备好了以下四个方法(并且不再需要其它的了):
- GET,获取资源
- POST,创建一个新资源
- PUT,更新已存在的资源
- DELETE,删除资源
通过HTTP调用这些方法
同样的,我们也不需要关心客户端如何来调用我们的方法,浏览器会帮我们搞定一切。
如果你仅仅只是在地址栏敲了个地址,然后按了下回车,浏览器会生成一条HTTP消息,并通过它来调用你输入的URL所代表的资源的GET方法。
如果你填写了一个表单,并点击了提交按钮,那么浏览器会将你填在表单中的信息组装成一条HTTP POST消息,并通过它来调用你想访问资源的POST方法。
但不幸的是,由于HTML的限制,目前你无法通过浏览器来调用资源的PUT和DELETE方法,不过这不重要,GET和POST对我们已经足够了。
好了,我们的基于REST的设计就这么完成了,下一讲,我们将演示如何使用Rest on Rails来快速优美的实现我们的设计。
REST on Rails指南2:无穷尽的API
通过上一讲,我认为你树立了这个概念:即Web其实是一组资源而不是网页的集合(如果你还不这么认为,那请你先返回再次阅读第一讲)。这一讲我们将从另一个侧面来讲解为什么要有REST?
面向对象设计与分析
如果你曾经学习过面向对象程序设计,那么你很可能会这样开始构建你的新程序:
- 首先,你需要定义你的问题域——你的程序要解决什么问题
- 然后,你会定义一个类,这个类的名字一般是名词
- 接着你会为这个类定义一些方法,方法的名字一般是动词
- 最用,通过调用其它类的方法,你的这个类顺利完成了它的使命
这看起来不错,事实上我曾经这么干了好多年,这种名词加动词的编程方法被成为“RPC”(远程过程调用),虽然我不明白那个Remote(远程)是指什么,但RPC的确是构建面向对象软件的一个重要方法,不过这种方式却并不适合Web开发。
让我们回到远古,假设现在是1992年(或者Web出现之前的随便什么日子),假设有这样的三家公司,他们需要开发这样三种应用:书籍贩卖,机票贩卖以及 卫星地图浏览。并且他们都遵照了面向对象的设计思想,同时出于长远考虑,他们都认为总有一天会有第三方的软件需要同他们的系统进行交互,因此,他们都实现 了他们各自的API。
现在,假设你的老板分配给你一个任务:为这三个系统设计一个统一的前端,你会怎么做呢?
我想你首先需要学习这三种完全不同的API,然后为每一个API设计一个UI控件,当用户操作UI控件时,对应的API就会被调用,你可能会通过你学到的一些设计模式知识来简化你的工作量,并使你的代码看起来尽可能酷一些。
无穷尽的API
当然这只是假设,但即使真的如此,在Web时代,你也不需要去学习那些无穷尽的糟糕API,你所要做的就是在你的电脑上安装一个浏览器,不是吗?浏览器对 于你将要访问的网站一无所知,但它却能够准确的返回你想要的,你可以通过它购买音乐,预定机票,甚至从任意远的距离来欣赏你家的屋顶。
这很神奇,不是吗?但是让我们设想一下,如果每个网站都有它们自己的API会是什么样子?如果你想在Amazon买本书,浏览器必须知道如何调用 Amazong.buy(),如果你想查看航班信息,那么浏览器需要知道如何调用UnitedAirlines.CheckFlights(),事实上, 这样通吃所有API的程序永远也不可能被开发出来。
所以这就决定了Web不可能是RPC式的,它只能是REST式的。
以资源为中心的设计
那么REST究竟是什么呢?按照维基百科的解释,REST是指Representational State Transfer。这是什么意思呢,简单的说,就是现在每个名词都不再拥有它们各自独一无二的动词了,在REST的世界里,所有名词拥有的动词都是一样 的,并且数量也很有限。换句话说,也就是所有的资源都提供了一组相同的API,这些API的实质就是允许随便什么客户端:
- 获取资源的某种表示
- 创建一个新资源
- 更新已存在的资源
- 销毁一个资源
等一下!那么究竟上面那个API可以让我“购买一本书”呢?搜索“下周二从纽约飞往洛杉矶的航班”又是哪个API完成的呢?
我们将在下一讲回答这个问题,但是如果你已经改变思维,不再认为”买书“就是一个网页,而是开始思考这其实是某个资源的创建,那么我想你其实应该已经知道答案了。