| 1 |
= Active Resource |
|---|
| 2 |
|
|---|
| 3 |
Active Resource (ARes) connects business objects and Representational State Transfer (REST) |
|---|
| 4 |
web services. It implements object-relational mapping for REST webservices to provide transparent |
|---|
| 5 |
proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing |
|---|
| 6 |
in ActionController::Resources). |
|---|
| 7 |
|
|---|
| 8 |
== Philosophy |
|---|
| 9 |
|
|---|
| 10 |
Active Resource attempts to provide a coherent wrapper object-relational mapping for REST |
|---|
| 11 |
web services. It follows the same philosophy as Active Record, in that one of its prime aims |
|---|
| 12 |
is to reduce the amount of code needed to map to these resources. This is made possible |
|---|
| 13 |
by relying on a number of code- and protocol-based conventions that make it easy for Active Resource |
|---|
| 14 |
to infer complex relations and structures. These conventions are outlined in detail in the documentation |
|---|
| 15 |
for ActiveResource::Base. |
|---|
| 16 |
|
|---|
| 17 |
== Overview |
|---|
| 18 |
|
|---|
| 19 |
Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database |
|---|
| 20 |
tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result |
|---|
| 21 |
received and serialized into a usable Ruby object. |
|---|
| 22 |
|
|---|
| 23 |
=== Configuration and Usage |
|---|
| 24 |
|
|---|
| 25 |
Putting ActiveResource to use is very similar to ActiveRecord. It's as simple as creating a model class |
|---|
| 26 |
that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it: |
|---|
| 27 |
|
|---|
| 28 |
class Person < ActiveResource::Base |
|---|
| 29 |
self.site = "http://api.people.com:3000/" |
|---|
| 30 |
end |
|---|
| 31 |
|
|---|
| 32 |
Now the Person class is REST enabled and can invoke REST services very similarly to how ActiveRecord invokes |
|---|
| 33 |
lifecycle methods that operate against a persistent store. |
|---|
| 34 |
|
|---|
| 35 |
# Find a person with id = 1 |
|---|
| 36 |
ryan = Person.find(1) |
|---|
| 37 |
Person.exists?(1) #=> true |
|---|
| 38 |
|
|---|
| 39 |
As you can see, the methods are quite similar to Active Record's methods for dealing with database |
|---|
| 40 |
records. But rather than dealing with |
|---|
| 41 |
|
|---|
| 42 |
==== Protocol |
|---|
| 43 |
|
|---|
| 44 |
Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing |
|---|
| 45 |
built into ActionController but will also work with any other REST service that properly implements the protocol. |
|---|
| 46 |
REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification: |
|---|
| 47 |
|
|---|
| 48 |
* GET requests are used for finding and retrieving resources. |
|---|
| 49 |
* POST requests are used to create new resources. |
|---|
| 50 |
* PUT requests are used to update existing resources. |
|---|
| 51 |
* DELETE requests are used to delete resources. |
|---|
| 52 |
|
|---|
| 53 |
For more information on how this protocol works with Active Resource, see the ActiveResource::Base documentation; |
|---|
| 54 |
for more general information on REST web services, see the article here[http://en.wikipedia.org/wiki/Representational_State_Transfer]. |
|---|
| 55 |
|
|---|
| 56 |
==== Find |
|---|
| 57 |
|
|---|
| 58 |
GET Http requests expect the XML form of whatever resource/resources is/are being requested. So, |
|---|
| 59 |
for a request for a single element - the XML of that item is expected in response: |
|---|
| 60 |
|
|---|
| 61 |
# Expects a response of |
|---|
| 62 |
# |
|---|
| 63 |
# <person><id type="integer">1</id><attribute1>value1</attribute1><attribute2>..</attribute2></person> |
|---|
| 64 |
# |
|---|
| 65 |
# for GET http://api.people.com:3000/people/1.xml |
|---|
| 66 |
# |
|---|
| 67 |
ryan = Person.find(1) |
|---|
| 68 |
|
|---|
| 69 |
The XML document that is received is used to build a new object of type Person, with each |
|---|
| 70 |
XML element becoming an attribute on the object. |
|---|
| 71 |
|
|---|
| 72 |
ryan.is_a? Person #=> true |
|---|
| 73 |
ryan.attribute1 #=> 'value1' |
|---|
| 74 |
|
|---|
| 75 |
Any complex element (one that contains other elements) becomes its own object: |
|---|
| 76 |
|
|---|
| 77 |
# With this response: |
|---|
| 78 |
# |
|---|
| 79 |
# <person><id>1</id><attribute1>value1</attribute1><complex><attribute2>value2</attribute2></complex></person> |
|---|
| 80 |
# |
|---|
| 81 |
# for GET http://api.people.com:3000/people/1.xml |
|---|
| 82 |
# |
|---|
| 83 |
ryan = Person.find(1) |
|---|
| 84 |
ryan.complex #=> <Person::Complex::xxxxx> |
|---|
| 85 |
ryan.complex.attribute2 #=> 'value2' |
|---|
| 86 |
|
|---|
| 87 |
Collections can also be requested in a similar fashion |
|---|
| 88 |
|
|---|
| 89 |
# Expects a response of |
|---|
| 90 |
# |
|---|
| 91 |
# <people type="array"> |
|---|
| 92 |
# <person><id type="integer">1</id><first>Ryan</first></person> |
|---|
| 93 |
# <person><id type="integer">2</id><first>Jim</first></person> |
|---|
| 94 |
# </people> |
|---|
| 95 |
# |
|---|
| 96 |
# for GET http://api.people.com:3000/people.xml |
|---|
| 97 |
# |
|---|
| 98 |
people = Person.find(:all) |
|---|
| 99 |
people.first #=> <Person::xxx 'first' => 'Ryan' ...> |
|---|
| 100 |
people.last #=> <Person::xxx 'first' => 'Jim' ...> |
|---|
| 101 |
|
|---|
| 102 |
==== Create |
|---|
| 103 |
|
|---|
| 104 |
Creating a new resource submits the xml form of the resource as the body of the request and expects |
|---|
| 105 |
a 'Location' header in the response with the RESTful URL location of the newly created resource. The |
|---|
| 106 |
id of the newly created resource is parsed out of the Location response header and automatically set |
|---|
| 107 |
as the id of the ARes object. |
|---|
| 108 |
|
|---|
| 109 |
# <person><first>Ryan</first></person> |
|---|
| 110 |
# |
|---|
| 111 |
# is submitted as the body on |
|---|
| 112 |
# |
|---|
| 113 |
# POST http://api.people.com:3000/people.xml |
|---|
| 114 |
# |
|---|
| 115 |
# when save is called on a new Person object. An empty response is |
|---|
| 116 |
# is expected with a 'Location' header value: |
|---|
| 117 |
# |
|---|
| 118 |
# Response (201): Location: http://api.people.com:3000/people/2 |
|---|
| 119 |
# |
|---|
| 120 |
ryan = Person.new(:first => 'Ryan') |
|---|
| 121 |
ryan.new? #=> true |
|---|
| 122 |
ryan.save #=> true |
|---|
| 123 |
ryan.new? #=> false |
|---|
| 124 |
ryan.id #=> 2 |
|---|
| 125 |
|
|---|
| 126 |
==== Update |
|---|
| 127 |
|
|---|
| 128 |
'save' is also used to update an existing resource - and follows the same protocol as creating a resource |
|---|
| 129 |
with the exception that no response headers are needed - just an empty response when the update on the |
|---|
| 130 |
server side was successful. |
|---|
| 131 |
|
|---|
| 132 |
# <person><first>Ryan</first></person> |
|---|
| 133 |
# |
|---|
| 134 |
# is submitted as the body on |
|---|
| 135 |
# |
|---|
| 136 |
# PUT http://api.people.com:3000/people/1.xml |
|---|
| 137 |
# |
|---|
| 138 |
# when save is called on an existing Person object. An empty response is |
|---|
| 139 |
# is expected with code (204) |
|---|
| 140 |
# |
|---|
| 141 |
ryan = Person.find(1) |
|---|
| 142 |
ryan.first #=> 'Ryan' |
|---|
| 143 |
ryan.first = 'Rizzle' |
|---|
| 144 |
ryan.save #=> true |
|---|
| 145 |
|
|---|
| 146 |
==== Delete |
|---|
| 147 |
|
|---|
| 148 |
Destruction of a resource can be invoked as a class and instance method of the resource. |
|---|
| 149 |
|
|---|
| 150 |
# A request is made to |
|---|
| 151 |
# |
|---|
| 152 |
# DELETE http://api.people.com:3000/people/1.xml |
|---|
| 153 |
# |
|---|
| 154 |
# for both of these forms. An empty response with |
|---|
| 155 |
# is expected with response code (200) |
|---|
| 156 |
# |
|---|
| 157 |
ryan = Person.find(1) |
|---|
| 158 |
ryan.destroy #=> true |
|---|
| 159 |
ryan.exists? #=> false |
|---|
| 160 |
Person.delete(2) #=> true |
|---|
| 161 |
Person.exists?(2) #=> false |
|---|
| 162 |
|
|---|
| 163 |
|
|---|
| 164 |
You can find more usage information in the ActiveResource::Base documentation. |
|---|
| 165 |
|
|---|