How To REST With Rails and ActiveResource: Part Three
Part 3 shows how to use principles and conventions for designing RESTful Web services to develop a client library for the task manager using ActiveResource.
Join the DZone community and get the full member experience.
Join For FreeIt’s easy to use OpenURI and Net::HTTP. Well, "easy" is a relative term. Building a client library to access our task manager service still requires a fair amount of boilerplate code — more than we care to write, test, and maintain. We have shown you some principles and conventions for designing RESTful web services, and in this final part of our three-part series, we'll take it a step further and show you how we can use them to develop a client library for the task manager using ActiveResource.
If you missed parts one and two of the series you can find them here:
How To REST With Rails and ActiveResource: Part One
How To REST With Rails and ActiveResource: Part Two
This article series is based on chapter 5 from Ruby in Practice by Jeremy McAnally and Assaf Arkin. Courtesy of Manning Publications. All rights reserved.
Problem
With the task manager service up and running, it's time to move forward and develop our workflow application. And as part of that application, we'll need to create and manage tasks. We want to reuse our task manager service, and we want to get it done before the day is over.
Solution
We're going to build a client application that uses the task manager service using ActiveResource. We'll start by writing a class to represent the resources for handling a task list and individual tasks:
class Task < ActiveResource::Base self.site = 'https://john:secret@taskmanager.example.com/' end
We're using the URL to specify the username/password for accessing the service. These map to HTTP Basic Authentication using HTTPS when we need to access it over public networks. We've yet to write a single line of code, but let's first see what we can do with our new Task class. Let's start by creating a new task:
task = Task.create(:title=>'Read about ActiveResource', :priority=>1) puts 'Created task #{task.id}' => 'Created task 1'
Doesn't this code look awfully familiar? We're using ActiveResource here to operate against remote resources, but the patterns are the same as in the previous section, where we used ActiveRecord to access the database. Let's see what happens behind the scenes of the create method:
task = Task.new task.title = 'Read about ActiveResource' task.priority = 1 task.save
It starts by creating a new object in memory and setting its attributes. It saves the object by making a POST request to the resource/tasks with an XML document containing the task definition. Our simple implementation receives the XML document, parses the attributes, and uses them to create a record in the database. It then tells the client what the new task resource is, which is all our ActiveResource needs to know. Let's follow up by updating the task:
task.title << ' and try this example' task.save
This time, since the task already exists, we make a PUT request to the resource and update, so we can create and update resources. We can also read and delete them:
task = Task.find(1) task.delete tasks = Task.find(:all) Task.delete(tasks.first.id)
All of this is just a matter of conventions. ActiveResource follows the same conventions we used when we built the task manager service, so we got all this functionality just by specifying a URL. How do we know our Task class sends requests to the right URL? We assumed it uses XML by default. Is there a way to find out for sure? Let's try the equivalent of the rake routes task:
puts Task.collection_path => /tasks.xml puts Task.element_path(1) => /tasks/1.xml
We built our task manager around all the common patterns but also added two resources specific to our task manager. We had one resource for listing all the completed tasks. We'll want to use that from our client as well, so let's list those:
Task.find(:all, :from=>:completed)
As you can guess, this just is a request against the /tasks/completed.xml path. We also had a resource for quickly updating the task priority, which we designed to support our Ajax controls. Let's try to use that as well:
task.put(:priority, nil, 5)
This time, the request goes to /tasks/{id}/priority, substituting the task identifier in the URL template. The put method takes two additional arguments. The first is a hash that is passed along as query string parameters, and the second is the body of the message. We're passing a priority number in the body of the message.
As you can expect, there are other custom methods you can use like get
, post
, and delete
. We're going to hide the details of put
from the application by wrapping it in a method; in fact, we'll add a couple more to create an ActiveResource class that represents our task manager service.
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
Now let's try it out:
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
Discussion
As you’ve seen from our examples, Rails makes it extremely easy to build web services that follow the REST principles and work equally well with web browsers and the programmable Web. In fact, a lot of that simplicity comes directly from following these principles. We didn’t have to tell our client how to create, read, update or delete the resource: those all followed by using the proper HTTP methods. All we had to do is point our client at the right place. Likewise, we didn’t have to build two task manager applications (one that people can use and another for service applications). We managed both at the same time by using different content types.
If you follow Rails conventions, you get the basic CRUD operations for free. In practice, that’s often not enough, and you’ll find that you need more specific resources and layering additional actions into your controllers. We showed you how easy it is to add these custom methods on both the server and the client-side. There are, of course, other things you’ll need to do. A fully functional task manager would need to handle deadlines and exceptions, send notifications, and even spawn workflows that would involve even more tasks and interact with other services. Those are all possible to do within the constraints of REST.
In the last three solutions, we talked extensively about Rails, but we want those to be lessons you can take with you if you use other web frameworks or even other programming languages. One was the recommended practice for building RESTful web services and the benefit that comes from following the REST architecture style. The other was the benefit of picking up on conventions and how they can help you design better, develop faster and end up with code that’s easier to understand and maintain. If nothing else, there would be less to document. Conventions are not just for Rails: when you’re building your own application, think about how conventions could help you work less and get more done.
The SOAP messaging protocol is another way to harness the HTTP protocol and build services that cross languages, platforms, and applications.
Opinions expressed by DZone contributors are their own.
Comments