如何使用Rails和ActiveResource进行REST。第三部分
第三部分展示了如何使用设计RESTful Web服务的原则和惯例,为使用ActiveResource的任务管理器开发一个客户端库。
使用OpenURI和Net::HTTP很容易。嗯,"容易 "是一个相对的术语。构建一个客户端库来访问我们的任务管理器服务仍然需要相当多的模板代码--比我们关心的编写、测试和维护要多。我们已经向你展示了设计RESTful网络服务的一些原则和惯例,在我们三部分系列的最后一部分,我们将更进一步,向你展示我们如何使用它们来开发一个使用ActiveResource的任务管理器的客户端库。
如果你错过了本系列的第一和第二部分,你可以在这里找到它们。
本系列文章基于Jeremy McAnally和Assaf Arkin的Ruby in Practice中的第5章。由Manning Publications提供。保留所有权利。
问题
随着任务管理器服务的建立和运行,是时候继续前进并开发我们的工作流应用程序了。作为该应用程序的一部分,我们需要创建和管理任务。我们想重新使用我们的任务管理器服务,并且我们想在一天结束前完成它。
解决方案
我们将使用ActiveResource构建一个使用任务管理器服务的客户端应用程序。我们首先要写一个类来表示处理任务列表和单个任务的资源。
class Task < ActiveResource::Base
self.site = 'https://john:secret@taskmanager.example.com/'
end
我们将使用URL来指定访问服务的用户名/密码。当我们需要通过公共网络访问它时,这些映射到使用HTTPS的HTTP基本认证。我们还没有写一行代码,但让我们先看看我们能用我们的新任务类做什么。让我们从创建一个新的任务开始。
task = Task.create(:title=>'Read about ActiveResource', :priority=>1)
puts 'Created task #{task.id}'
=> 'Created task 1'
这段代码看起来是不是很熟悉?我们在这里使用ActiveResource来对远程资源进行操作,但其模式与上一节相同,我们使用ActiveRecord来访问数据库。让我们看看在创建方法的幕后发生了什么。
task = Task.new
task.title = 'Read about ActiveResource'
task.priority = 1
task.save
它首先在内存中创建一个新对象并设置其属性。它通过向资源/任务发出POST请求来保存该对象,请求中包含了任务定义的XML文档。我们的简单实现接收XML文档,解析属性,并使用它们在数据库中创建一条记录。然后,它告诉客户端新的任务资源是什么,这就是我们的ActiveResource需要知道的一切。让我们接着来更新任务。
task.title << ' and try this example'
task.save
这一次,由于任务已经存在,我们向资源发出PUT请求并进行更新,因此我们可以创建和更新资源。我们还可以读取和删除它们。
task = Task.find(1)
task.delete
tasks = Task.find(:all)
Task.delete(tasks.first.id)
所有这些只是一个约定俗成的问题。ActiveResource遵循了我们在构建任务管理器服务时使用的相同约定,所以我们只需指定一个URL就能得到所有这些功能。我们怎么知道我们的任务类会把请求发送到正确的URL呢?我们假设它默认使用XML。有什么方法可以确定吗?让我们试试相当于rake routes任务的方法。
puts Task.collection_path
=> /tasks.xml
puts Task.element_path(1)
=> /tasks/1.xml
我们围绕所有常见的模式建立了我们的任务管理器,但也为我们的任务管理器添加了两个特定的资源。我们有一个资源用于列出所有已完成的任务。我们也想从我们的客户端使用这个,所以让我们列出这些。
Task.find(:all, :from=>:completed)
正如你所猜测的,这只是一个针对/tasks/completed.xml路径的请求。我们还有一个用于快速更新任务优先级的资源,我们设计这个资源是为了支持我们的Ajax控件。让我们也试试使用这个。
task.put(:priority, nil, 5)
这一次,请求转到/tasks/{id}/priority,在URL模板中替换任务标识符。put方法需要两个额外的参数。第一个是作为查询字符串参数传递的哈希值,第二个是消息的主体。我们在消息的主体中传递一个优先级数字。
正如你所期望的,你还可以使用其他的自定义方法,如get,post, 和delete 。我们将通过把put 的细节包装在一个方法中来隐藏应用程序;事实上,我们将再添加几个方法来创建一个ActiveResource类,代表我们的任务管理器服务。
class Task < ActiveResource::Base
self.site = 'https://taskmanager.example.com/'
def self.completed
find(:all, :from=>:completed)
end
def self.update_priority(id, value)
Task.new(:id=>id).priority!(value)
end
def priority!(value)
put(:priority, nil, value.to_i)
end
end
现在让我们来试试吧。
Task.site.user_info = 'john:secret'
puts 'Completed tasks'
puts Task.completed.map { |task| task.id }.to_sentence
=> "1, 2 and 3"
puts 'Changing priority for task 123'
Task.update_priority(123, 5)
puts Task.find(123).priority
=> 5
讨论
正如你从我们的例子中所看到的,Rails使得建立遵循REST原则的Web服务变得非常容易,并且在Web浏览器和可编程的Web上都能正常工作。事实上,这种简单性很大程度上直接来自于遵循这些原则。我们不需要告诉我们的客户如何创建、读取、更新或删除资源:这些都是通过使用适当的HTTP方法来实现的。我们所要做的就是把我们的客户指向正确的地方。同样,我们也不需要建立两个任务管理器(一个供人们使用,另一个供服务应用)。我们通过使用不同的内容类型同时管理两者。
如果你遵循Rails的惯例,你可以免费获得基本的CRUD操作。在实践中,这往往是不够的,你会发现你需要更多的特定资源,并将额外的操作分层到你的控制器。我们向你展示了在服务器和客户端添加这些自定义方法是多么容易。当然,还有其他你需要做的事情。一个功能齐全的任务管理器需要处理最后期限和例外情况,发送通知,甚至产生涉及更多任务并与其他服务互动的工作流。这些都是可以在REST的约束下完成的。
在过去的三个解决方案中,我们广泛地讨论了Rails,但我们希望这些是你在使用其他网络框架甚至其他编程语言时可以带走的教训。一个是构建RESTful网络服务的推荐做法,以及遵循REST架构风格带来的好处。另一个是学习惯例的好处,以及它们如何帮助你更好地设计,更快地开发,并最终获得更容易理解和维护的代码。如果不出意外的话,需要记录的东西会更少。惯例并不仅仅适用于Rails:当你在构建自己的应用程序时,想想惯例如何能帮助你减少工作并完成更多的工作。
SOAP消息传递协议是利用HTTP协议和建立跨语言、平台和应用的服务的另一种方式。