ActiveResource指南

 

尽管ActiveResource(以下简称ARes)在Rails2.0庞大的ChangeLog中只占了短短的几行,但我们不应该因此就认为这只是一个小改动,实际上,ARes可以算作是Rails1.2中引入的Resource概念的一个后续,利用Resource,你可以轻松构建基于REST风格的Web Service,而ARes则让你可以像使用ActiveRecord对象那样简单的使用这些Web Service。

理论总是过于抽象,让我们通过一个例子来看看ARes究竟是如何工作的,假设我们要开发一个类似于豆瓣那样的书评网站(我们为它申请了一个 shuping.com的域名),但为了不在一开始就让事情变得不可收拾,在第一个迭代周期内,我们只打算让用户做两件事情:提交/编辑书目,提交/编辑 /删除评论,也就是说我们只需要创建2个Resource:book和comment:

map.resources :books do |book|

  book.resources :comments

end

现在,我们已经具有了一组REST风格的URI,用户可以通过/books/new来创建新书目,或者通过/books/: book_id/comments/new来提交对书目的新评论,这对于使用浏览器的普通用户来说没什么问题,只要我们有一位足够优秀的UI设计师,用户应该不会在这一过程中感到任何的不快。但我们的目标并不局限于此,我们希望我们的数据能够被应用到更广泛的领域,同时,我们也希望用户能够通过更多的方式来向我们提交数据,而不仅仅只是通过我们自己的网站,因此,我们向第三方开发者开放了我们的REST API,他们可以通过:

  • GET /books,获取所有书目
  • POST /books,创建新书目
  • GET /books/1.xml,获取编号为1的书目
  • PUT /books/1.xml,修改编号为1的书目
  • DELETE /books/1.xml,删除编号为1的书目

 

但是作为应用的开发者,他们应该如何来使用我们的API呢,要使用我们的API,他们需要:

  • 创建一条到我们服务器的HTTP连接
  • 构造一个请求报文
  • 发送请求报文到我们的REST URI
  • 解析得到的响应,处理可能的错误

 

不过幸运的是,有了ARes,他们不需要真的这么做,现在让我们将角色切换到为我们的书评网站开发应用的第三方开发者,假设我们要使用书评网的数据,那么,我们首先需要定义一个ARes类:

class Book < ActiveResource::Base

  self.site = "http://shuping"

end

接下来,我们就可以像ActiveRecord那样来使用我们的ActiveResource类了。

获取书目信息

要从书评网获取书目信息,可以使用find方法:

books = Book.find(:all)

现在,我们已经得到了书评网最新的书目信息,那么ARes是如何帮助我们做到这些的呢?实际上它替我们向书评网发送了一条GET请求:

GET http://shuping.com/books.xml

<books type="array">

  <book>

    <title>Agile Web Development with Rails</title>

  </book>

</books>

并将得到的XML报文重新组装为本地的Book对象。

但要注意,ARes没有提供ActiveRecord的find_by函数,不过我们可以通过向find传递:params来实现同样的功能,下面是一些复杂的find操作:

Book.find(:first, :params => {:title => 'TDD'})

# GET http://shuping/books.xml?title=TDD

Book.find(:first, :from => :popular)

# GET http://shuping/books/popular.xml

Book.find(:first, :from => '/yzhang/books.xml')

# GET http://shuping/yzhang/books.xml

同样的,我们也可以使用save方法创建一条新书目:

book = Book.new(:title => "Programming Ruby")

book.new? # true

book.save # true

book.id # 2

这相当于向书评网发送了一条 POST请求:

POST http://shuping.com/books

POST的内容就是我们新建的书目的XML表示,而返回的XML响应则通过Location字段指示了新资源在远端服务器上的位置:

Location: http://shuping.com/books/3.xml

ARes会根据返回的响应自动为我们更新新书目的id字段,因此,我们可以看到,在保存成功后,新书目的id变为了2,除了find和save,我们也可以更新一个已存在的书目或者删除错误添加的书目:

book = Book.find(2)

book.authors = "Dave Thomas, Andrew Hunter"

book.new? # false

book.save # PUT http://shuping.com/books/1.xml

book.destroy # DELETE http://shuping.com/books/1.xml

book.exist? # false

Book.exist?(2) # false

错误处理

上面的代码,我们假设了所有的数据都是正确的,但在现实世界中,错误往往是难以避免的,因此,一个稳固的应用必须考虑到可能的错误,首先我们来看看如果我们试图保存一个没有名称的新书目会发生什么情况:

book = Book.new

book.save # false

book.errors.full_messages # => ["Title can't be blank"]

book.errors.on :title # => Title can't be blank

就像我们所想的,保存失败了,并且同ActiveRecord一样,我们可以通过errors来向告诉用户失败的原因,但除了Validation失败,我们还需要面对其它可能出现的错误,比如请求的资源不存在,我们将会遭遇一个404响应,因此,要正确的处理这些错误,我们必须先了解它们,ARes可能产生的错误包括:

  • 200 - 399,有效的响应,没有异常产生
  • 404,请求的资源不存在,引发ActiveResource::ResourceNotFound异常
  • 409,资源存在冲突,引发ActiveResource::ResourceConflict异常
  • 422,无效的资源(如Validation失败),引发ActiveResource::ResourceInvalid异常
  • 401 - 499,其它客户端错误,引发ActiveResource::ClientError异常
  • 500 - 599,其它服务端错误,引发ActiveResource::ServerError异常

 

通常情况下,我们只需要考虑404,409,422错误即可,因此安全的代码看起来应该是下面这个样子:

begin

  book = Book.find(1)

rescue ActiveResource::ResourceNotFound

  redirect_to :action => 'not_found'

rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid

  redirect_to :action => 'new'

end

资源嵌套

大家一定已经注意到了,我们的书评网定义了两个资源,但是前面的例子都只演示了如何操作Book资源,而没有涉及嵌套在Book中的comment资源,实际上,这相当的简单,我们只需这样定义comment.rb:

class Comment < ActiveResource::Base

  self.site = "http://shuping/books/:book_id"

end

大家应该注意到,在Site URI中包含了一个:book_id的symbol,ARes会负责将它替换为真实的user_id,当然我们需要将它传递给ARes:

comment = Comment.new(:body => 'test', :book_id => 1)

comment.save #true, POST http://shuping/books/1/comments

comment.id # 1

# GET http://shuping/books/1/comments/1

comment = Comment.find(1, :params => {:book_id => 1})

comment.body # test

自定义方法

现在,让我们再次回到我们的书评网站,假设我们的网站大受欢迎(虽然这不太可能),那么,相信我们很快就需要面临这样一个问题:垃圾评论。为了应对垃圾评论,我们决定采用最为稳妥的方式,人工审核,因此我们需要为comment controller扩展一个moderate方法:

book.resources :comments :member => {:moderate => :put}

现在,我们可以通过PUT /books/1/comments/1/moderate来人工批准一条评论,但问题随之也就来了,毕竟我们自己的人手有限,因此我们计划让我们的联盟网站来和我们一起审核评论,但是它们应该如何通过ARes来调用moderate方法呢,其实很简单:

comment = Comment.find(1, :params => {:book_id => 1})

comment.put(:moderate) # PUT http://shuping/books/1/comments/1/moderate

# true

最后,我们的书评网站必然是需要认证的,ARes提供了简单的方式来支持HTTP认证:

class Book < ActiveResource::Base

  self.site = "http://yzhang:password@shuping/"

end

参考:REST & ActiveResource

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章