Active Resource
Active Resource (ARes) connects business objects and Representational State
Transfer (REST) web services. It implements object-relational mapping for
REST webservices to provide transparent proxying capabilities between a
client (ActiveResource) and
a RESTful service (which is provided by Simply RESTful routing in ActionController::Resources).
Philosophy
Active Resource attempts to provide a coherent wrapper object-relational
mapping for REST web services. It follows the same philosophy as Active
Record, in that one of its prime aims is to reduce the amount of code
needed to map to these resources. This is made possible by relying on a
number of code- and protocol-based conventions that make it easy for Active
Resource to infer complex relations and structures. These conventions are
outlined in detail in the documentation for ActiveResource::Base.
Overview
Model classes are mapped to remote REST resources by Active Resource much
the same way Active Record maps model classes to database tables. When a
request is made to a remote resource, a REST XML request is generated,
transmitted, and the result received and serialized into a usable Ruby
object.
Configuration and Usage
Putting ActiveResource to
use is very similar to ActiveRecord. It‘s as simple as creating a
model class that inherits from ActiveResource::Base and
providing a site class variable to it:
class Person < ActiveResource::Base
self.site = "http://api.people.com:3000/"
end
Now the Person class is REST enabled and can invoke REST services very
similarly to how ActiveRecord invokes lifecycle methods that operate
against a persistent store.
# Find a person with id = 1
ryan = Person.find(1)
Person.exists?(1) #=> true
As you can see, the methods are quite similar to Active Record‘s
methods for dealing with database records. But rather than dealing directly
with a database record, you‘re dealing with HTTP resources (which may
or may not be database records).
Protocol
Active Resource is built on a standard XML format for requesting and
submitting resources over HTTP. It mirrors the RESTful routing built into
ActionController but will also work with any other REST service that
properly implements the protocol. REST uses HTTP, but unlike
"typical" web applications, it makes use of all the verbs
available in the HTTP specification:
- GET requests are used for finding and retrieving resources.
- POST requests are used to create new resources.
- PUT requests are used to update existing resources.
- DELETE requests are used to delete resources.
For more information on how this protocol works with Active Resource, see
the ActiveResource::Base
documentation; for more general information on REST web services, see the
article here.
Find
GET Http requests expect the XML form of whatever resource/resources is/are
being requested. So, for a request for a single element - the XML of that
item is expected in response:
# Expects a response of
#
# <person><id type="integer">1</id><attribute1>value1</attribute1><attribute2>..</attribute2></person>
#
# for GET http://api.people.com:3000/people/1.xml
#
ryan = Person.find(1)
The XML document that is received is used to build a new object of type
Person, with each XML element becoming an attribute on the object.
ryan.is_a? Person #=> true
ryan.attribute1 #=> 'value1'
Any complex element (one that contains other elements) becomes its own
object:
# With this response:
#
# <person><id>1</id><attribute1>value1</attribute1><complex><attribute2>value2</attribute2></complex></person>
#
# for GET http://api.people.com:3000/people/1.xml
#
ryan = Person.find(1)
ryan.complex #=> <Person::Complex::xxxxx>
ryan.complex.attribute2 #=> 'value2'
Collections can also be requested in a similar fashion
# Expects a response of
#
# <people type="array">
# <person><id type="integer">1</id><first>Ryan</first></person>
# <person><id type="integer">2</id><first>Jim</first></person>
# </people>
#
# for GET http://api.people.com:3000/people.xml
#
people = Person.find(:all)
people.first #=> <Person::xxx 'first' => 'Ryan' ...>
people.last #=> <Person::xxx 'first' => 'Jim' ...>
Create
Creating a new resource submits the xml form of the resource as the body of
the request and expects a ‘Location’ header in the response
with the RESTful URL location of the newly created resource. The id of the
newly created resource is parsed out of the Location response header and
automatically set as the id of the ARes object.
# <person><first>Ryan</first></person>
#
# is submitted as the body on
#
# POST http://api.people.com:3000/people.xml
#
# when save is called on a new Person object. An empty response is
# is expected with a 'Location' header value:
#
# Response (201): Location: http://api.people.com:3000/people/2
#
ryan = Person.new(:first => 'Ryan')
ryan.new? #=> true
ryan.save #=> true
ryan.new? #=> false
ryan.id #=> 2
Update
‘save’ is also used to update an existing resource - and
follows the same protocol as creating a resource with the exception that no
response headers are needed - just an empty response when the update on the
server side was successful.
# <person><first>Ryan</first></person>
#
# is submitted as the body on
#
# PUT http://api.people.com:3000/people/1.xml
#
# when save is called on an existing Person object. An empty response is
# is expected with code (204)
#
ryan = Person.find(1)
ryan.first #=> 'Ryan'
ryan.first = 'Rizzle'
ryan.save #=> true
Delete
Destruction of a resource can be invoked as a class and instance method of
the resource.
# A request is made to
#
# DELETE http://api.people.com:3000/people/1.xml
#
# for both of these forms. An empty response with
# is expected with response code (200)
#
ryan = Person.find(1)
ryan.destroy #=> true
ryan.exists? #=> false
Person.delete(2) #=> true
Person.exists?(2) #=> false
You can find more usage information in the ActiveResource::Base
documentation.