2007-06-23 13:29:54 -04:00
|
|
|
= Active Resource
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
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).
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
== Philosophy
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
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 <tt>site</tt> class variable to it:
|
2007-01-16 01:34:10 -05:00
|
|
|
|
|
|
|
class Person < ActiveResource::Base
|
|
|
|
self.site = "http://api.people.com:3000/"
|
|
|
|
end
|
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
Now the Person class is REST enabled and can invoke REST services very similarly to how ActiveRecord invokes
|
2007-01-16 01:34:10 -05:00
|
|
|
lifecycle methods that operate against a persistent store.
|
|
|
|
|
|
|
|
# Find a person with id = 1
|
|
|
|
ryan = Person.find(1)
|
|
|
|
Person.exists?(1) #=> true
|
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
As you can see, the methods are quite similar to Active Record's methods for dealing with database
|
|
|
|
records. But rather than dealing with
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
==== Protocol
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
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:
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
* 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.
|
2007-01-16 01:34:10 -05:00
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
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[http://en.wikipedia.org/wiki/Representational_State_Transfer].
|
2007-01-16 01:34:10 -05:00
|
|
|
|
|
|
|
==== 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
|
|
|
|
#
|
2007-11-06 13:33:45 -05:00
|
|
|
# <person><id type="integer">1</id><attribute1>value1</attribute1><attribute2>..</attribute2></person>
|
2007-01-16 01:34:10 -05:00
|
|
|
#
|
|
|
|
# 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
|
|
|
|
#
|
2007-11-06 13:33:45 -05:00
|
|
|
# <people type="array">
|
|
|
|
# <person><id type="integer">1</id><first>Ryan</first></person>
|
|
|
|
# <person><id type="integer">2</id><first>Jim</first></person>
|
2007-01-16 01:34:10 -05:00
|
|
|
# </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:
|
|
|
|
#
|
2007-01-16 19:46:32 -05:00
|
|
|
# Response (201): Location: http://api.people.com:3000/people/2
|
2007-01-16 01:34:10 -05:00
|
|
|
#
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2007-06-23 13:29:54 -04:00
|
|
|
You can find more usage information in the ActiveResource::Base documentation.
|
2007-01-16 01:34:10 -05:00
|
|
|
|