Chapter 12. Active Resource
Rails makes it really easy to provide and consume RESTful web APIs by way of a module called Active Resource.
Note
In Rails 4, Active Resource has been extracted out of the core framework and into a gem you can bundle into your application should you choose to include it.
Rails makes it easy to offer multiple different content types, or representations, of a particular resource through a single controller action. Being able to offer multiple representations of a resource through a single controller action makes adding support for non-browser-based HTML clients as simple as adding a few lines of code that returns the resource in the representation the client requested. The controller layout and logic of the application is preserved with the added benefit of interoperability with new clients.
Active Resource provides the tools to quickly and easily consume REST based web services conforming to the Rails RESTful URI structure and protocol conventions. Active Resource automatically maps the response from any conforming service to rich Ruby objects. Active Resource also provides all the lifecycle methods needed to easily find, create, update, and delete resources without having to write any code.
Following is a simple definition for an Active Resource model:
class Person < ActiveResource::Base self.site = "http://webservice.example.com" end
With only this simple subclass of ActiveResource::Base and a site
definition, which tells Active Resource the base endpoint URI of the
remote web service, it is possible to find, create, update, and delete resources in the
remote application:
# Build and save a new person person = Person.new(:name => "Cody", :title => "Developer") person.save # => true # Find an existing person person = Person.find(1) # Access a dynamically generated attribute person.title # => "Developer" # Update the attribute person.title = "Unemployed" # Save changes back to the remote service person.save # => true # Delete the person person.destroy # => true
As you can see, the functionality of an Active Resource based model is very similar to that of an Active Record based model. The only difference being that the data source is a remote web service rather than a SQL database. This chapter provides coverage of all the main functionality of Active Resource and shows you how Active Resource works under the hood so that you can implement your own RESTful Rails web services.
Background
Active Resource is only a small piece of a much larger puzzle. Active Resource is dependent on the remote web service conforming to the Rails RESTful URL structure and data serialization conventions. The web service does not have to be provided by a Rails application, but this is the most common case because Rails makes it so easy to create a RESTful, web service enabled application.
There are two benefits to understanding Active Resource. The first benefit is the ability to quickly and easily create an Active Resource client for someone else's web service. The second benefit is being able to easily expose your own application to Active Resource clients. We're going to take a look under the hood at the communication between an Active Resource client and a Rails application in order to build a complete understanding of web services in Rails.
Active Resource is able to easily consume a web service offered by Rails application conforming to a RESTful architecture because of the consistent URL layout and behavior Representational State Transfer (REST) imposes on the application. REST works with the concept of resources, which are sources of information that can be uniquely addressed by a URI. Clients of an application exchange representations of resources via HTTP. Multiple different representations of a single resource may be available from a single URI. In terms of Rails, multiple different formats of a resource are available from a single controller action, based on the format requested by the client. Active Resource works by specifically asking the Rails application for an XML or JSON representation of a resource, whereas a client's browser would request the HTML representation.
Another principle of REST is the use
of a constrained set of well defined operations. In Rails, the well defined set of
operations are based on the different HTTP verbs available. The HTTP verbs GET, POST, PUT, and DELETE map to the standard create, read, update, and
delete (CRUD) operations offered by a Rails controller. Instead of
specifying the operation to be performed through the request URI like
show in /customers/show/15, a RESTful application would send
an HTTP
GET request to the resource's member URI
/customers/15. A member URI, such
as /customers/15, uniquely addresses an individual
resource, whereas a collection URI, such as /customers, addresses the entire set of resources. Using the HTTP verb in conjunction with the resource URI to specify
the desired operation eliminates redundancy and utilizes the well defined underlying
HTTP protocol specifications.
Active Resource relies on the server providing the
appropriate HTTP status code with each response. This allows Active
Resource to accurately communicate any failure conditions that might occur. All status
codes between 200 and 399 are considered successful, except for 301 and 302, which are redirects. One
other exception is when the server returns the 422
Unprocessable Entity status code, which indicates that validation of the
resource failed. In this case, Active Resource rescues the exception and parses the
errors returned in the response body so that they are accessible to the client code.
We'll take a closer look at validation and errors later in the chapter. Table 12.1, “Active Resource Exceptions” shows the exceptions raised for each
different HTTP status code.
Table 12.1. Active Resource Exceptions
| HTTP Status Code | Exception |
|---|---|
| 301, 302 | ActiveResource::Redirection |
| 400 | ActiveResource::BadRequest |
| 401 | ActiveResource::UnauthorizedAccess |
| 403 | ActiveResource::ForbiddenAccess |
| 404 | ActiveResource::ResourceNotFound |
| 405 | ActiveResource::MethodNotAllowed |
| 409 | ActiveResource::ResourceConflict |
| 410 | ActiveResource::ResourceGone |
| 422 | ActiveResource::ResourceInvalid |
| 401 - 499 | ActiveResource::ClientError |
| 500 - 599 | ActiveResource::ServerError |
| Other | ActiveResource::ConnectionError |
In the following sections we'll take a look at the code for the client and server, as well as how they interoperate with one another. This will help to solidify your understanding of Active Resource and your knowledge of how Rails uses REST and HTTP protocol conventions to simplify web services.
Client and Server
Let's construct a client for a RESTful
Rails web service providing customer information. The web service application will be a
simple Rails application consisting of a CustomersController and
a Customer model. The client of the web service will be a simple
Active Resource client named Api::Customer. The overall
architecture of the client and server discussed in this section is shown in Figure 12.1, “High level overview of a RESTful Rails web service.”.
Web Service Client
Since the Active Resource client will most likely be
written for someone else's web service, it is unlikely that the client and server
will be contained within the same Rails project. You can place the code for
Api::Customer into app/models/api/customer.rb in a Rails application, or if you are
using Active Resource standalone, you can place the code anywhere you want. The
Api::Customer Active Resource model looks as follows:
Example 12.1. Simple Active Resource model
module Api
class Customer < ActiveResource::Base
self.site = 'http://localhost:3000'
end
endNote
By default, the enclosing element of a serialized
Active Resource model is the underscored class name of the model. If the name of
your class and the name of the element required by the remote web service do not
match up then you can change the element name with the
ActiveResource::Base.element_name class accessor:
class PersonResource < ActiveResource::Base self.element_name = "person" end
The module namespace Api has been added to the model in order to disambiguate it from the
Active Record model of the same name in the web service's application. This allows
the discussion of each model without confusion and allows for both models to exist
in the same project. Having two Customer classes in the same
project would not work because the two classes would generate a TypeError due to a superclass mismatch at runtime. Enclosing the Active
Resource model within a module does not have any effect on the way an Active
Resource model interacts with the remote web service.
Active Resource needs to be provided with the
URI location of the web service, which is called the site. You can set the site for all subclasses by setting ActiveResource::Base.site. You can also set or override the site for any particular subclass by setting the
site on the subclass. Active Resource uses
the configured site along with conventions based
on REST and the name of your Active Resource model's class name to
resolve the correct URIs for the remote resource. First we'll
configure logging in the client so that each request and response the client makes
is recorded in the configured log file. Then we'll take a look at the implementation
of the web service itself.
Logging Requests
Logging is a great way to record a history of
events that occur in an application. By default, a logger is not configured for
Active Resource, but it is easy to configure one. A logger is configured in
Active Resource by setting the ActiveResource::Base.logger class method with an instance of the
logger you want to use.
If you're using Active Resource from within a
Rails project to communicate with a remote API then you just
need to assign the Rails logger to also handle Active Resource logging. This is
best done in an initializer, such as config/initializers/active_resource.rb, and looks as follows:
ActiveResource::Base.logger = Rails.logger
Now with you'll get entries in your logs for each request made. The log entries will look something like:
GET http://localhost:3000/customers.xml --> 200 OK 301 (93.2ms) GET http://localhost:3000/customers/1.xml --> 200 OK 141 (51.1ms) POST http://localhost:3000/customers.xml --> 201 Created 143 (164.5ms) PUT http://localhost:3000/customers/4.xml --> 200 OK 1 (94.6ms)
Each log entry contains the HTTP verb and URI endpoint of the request on the first line and the HTTP status code, HTTP status phrase, content length on the second line. In brackets on the second line is also the entire elapsed time of the remote request in milliseconds.
Setting up a logger is slightly more involved if your Active Resource client is not part of a Rails application, but it still isn't too complex. The code would look something like the following:
require 'rubygems' require 'logger' require 'active_resource' require "active_resource/railties/log_subscriber" ActiveResource::Base.logger = Logger.new(STDOUT) ActiveResource::Base.logger.level = Logger::DEBUG Rails::LogSubscriber.add(:active_resource, ActiveResource::Railties::LogSubscriber.new)
First, all the files needed for the example are
required. Then a new instance of Ruby's Logger is created
and set to the Logger::DEBUG log level. Then the logger is
assigned to the ActiveResource::Base.logger class
accessor. Finally, a new instance of the ActiveResource::Railties::LogSubscriber class, which uses the
logger assigned to ActiveResource::Base.logger for
logging, is created and added as a subscriber to Active Resource log
events.
Defining a Schema
Since Active Resource is so dynamic, it can be
useful to define a schema for your resource. Defining a schema is completely
optional, but allows you to define the expected attributes you Active Resource
model has. This is useful when your Active Resource objects are quite rich in
behavior and are not just simple data objects. To fully utilize client-side
validations, which we'll take a look at in a later section, you'll also need to
define a schema in order to ensure that the attributes you're validating exist
at the time of validation. Without defining a schema your validations would
raise NoMethodError exceptions because the attributes
you're trying to validate don't exist.
You define a schema using the schema class method on your Active Resource model class. There
are three types of attribute you can define: string, integer, and float. You can define each
attribute separately:
class Customer < ActiveResource::Base
schema do
attribute 'name', 'string'
attribute 'age', 'integer'
end
endDefining the attributes means that the object
will respond_to? the attribute:
customer = Customer.new customer.respond_to?(:name) # => true customer.respond_to?(:age) # => true customer.respond_to?(:title) # => false
You can also define a list of attributes at once:
class Customer < ActiveResource::Base
schema do
string 'name', 'company'
integer 'age'
end
endAs you can see from the example, you just call
a method for the attribute's type, such as integer, instead
of the attribute method.
Web Service Server
The web service used for the examples in this chapter
is provided by a simple Rails controller called CustomersController that conforms to the Rails RESTful URI structure. For the sake of simplicity the
controller will only return XML representations of customer
resources.
In order for Rails to properly route requests for a
resource to the proper controller and action, the application needs a resource
definition in config/routes.rb:
Example::Application.routes.draw do |map|
resources :customers do
collection do
get :oldest, :adults
end
end
endThe resources
:customers call indicates that there is a CustomersController providing access to customer resources. The call
defines the standard Rails routes for the customer resource and also defines two
custom routes: oldest, and adults. The custom routes will be used for retrieving
resources based on custom URIs in the next section. Both custom
routes operate on the collection and only accept GET requests. The combination of HTTP verb and request
URI is shown in Table 12.2, “RESTful Rails HTTP to controller action mapping” and is also shown in the comment above each controller action in Example 12.2, “A simple controller providing a RESTful web service.”.
Table 12.2. RESTful Rails HTTP to controller action mapping
| HTTP Verb | Request URI | Controller Action |
|---|---|---|
The :id in URI
represents the ID of the resource in a
request. | ||
GET | /customers | index |
GET | /customers/:id | show |
POST | /customers | create |
PUT | /customers/:id | update |
DELETE | /customers/:id | destroy |
GET | /customers/oldest | oldest |
GET | /customers/adults | adults |
The implementation of the controller used throughout the chapter can be seen in Example 12.2, “A simple controller providing a RESTful web service.”.
Example 12.2. A simple controller providing a RESTful web service.
class CustomersController < ApplicationController
# GET /customers.xml
def index
@customers = Customer.find(:all)
respond_to do |format|
format.xml { render :xml => @customers }
end
end
# GET /customers/1.xml
def show
@customer = Customer.find(params[:id])
respond_to do |format|
format.xml { render :xml => @customer }
end
end
# POST /customers.xml
def create
@customer = Customer.new(params[:customer])
respond_to do |format|
if @customer.save
format.xml do
render :xml => @customer, :status => :created, :location => @customer
end
else
format.xml do
render :xml => @customer.errors, :status => :unprocessable_entity
end
end
end
end
# PUT /customers/1.xml
def update
@customer = Customer.find(params[:id])
respond_to do |format|
if @customer.update_attributes(params[:customer])
format.xml { head :ok }
else
format.xml do
render :xml => @customer.errors, :status => :unprocessable_entity
end
end
end
end
# DELETE /customers/1.xml
def destroy
@customer = Customer.find(params[:id])
@customer.destroy
respond_to do |format|
format.xml { head :ok }
end
end
# GET /customers/oldest.xml
def oldest
@customer = Customer.find(:first, :order => 'age DESC')
respond_to do |format|
format.xml { render :xml => @customer }
end
end
# GET /customers/adults.xml
def adults
@customers = Customer.find(:all, :conditions => 'age >= 18')
respond_to do |format|
format.xml { render :xml => @customers }
end
end
endAs mentioned before, the controller only returns responses in XML format. Most controllers would also support other representations of the resources, such as HTML, for non web service based clients. The details of the responses and codes returned by each action will be explained in more details in the following sections.
Example 12.3. Active Record Customer class on the server
class Customer < ActiveRecord::Base validates_presence_of :name validates_numericality_of :age, :greater_than => 0 end
The model used by the server in the examples is seen in Example 12.3, “Active Record Customer class on the server”. The model defines a couple of validations, which will be used to illustrate how validation and exceptions work when using Active Resource.
Finding Resources
Active Resource reads resources by sending GET requests to the collection or member URLs of a resource. Any call to find that
acts as a query will be sent to the collection URI of the resource
because the exact resource isn't yet known. Finding a resource by ID
sends the request to the member URI for the resource, since the exact
desired resource is known.
Finding all resources
Active Resource makes it easy to find:
customers = Api::Customer.find(:all)
Active Resource sends a GET request to the resource's collection URL:
GET /customers.xml HTTP/1.1
Active Resource appends .xml to the request path to indicate that the desired response format is
XML, which is the default. As you can see from Example 12.2, “A simple controller providing a RESTful web service.”, this request routes to the
index action of the CustomersController on the remote web service. The index action returns all customers in the database
serialized as XML with the HTTP status code
200 OK. The response body looks as follows:
<?xml version="1.0" encoding="UTF-8"?>
<customers type="array">
<customer>
<age type="integer">30</age>
<id type="integer">1</id>
<name>Cody</name>
</customer>
<customer>
<age type="integer">28</age>
<id type="integer">2</id>
<name>Tobi</name>
</customer>
</customers>The returned XML response is automatically mapped by Active Resource into objects complete with dynamic accessors for all of the attributes of the object. Inspecting the collection of customers would then look as follows:
[#<Api::Customer:0x105d2d9e8
@attributes={"name"=>"Cody", "id"=>1, "age"=>30},
@prefix_options={}>,
#<Api::Customer:0x105d2d920
@attributes={"name"=>"Tobi", "id"=>2, "age"=>28},
@prefix_options={}>]Each element in the array is an instance of the
Api::Customer class complete with dynamically generated
accessors for each attribute:
customer = customers.first customer.id # => 1 customer.name # => "Cody" customer.age # => 30 customer.new? # => false
Finding a single resource by ID
The code for finding a single resource by ID is similar to finding all resources:
customer = Api::Customer.find(1)
The GET request is
sent to the member URI of the customer resource with ID
1:
GET /customers/1.xml HTTP/1.1
Based on Table 12.2, “RESTful Rails HTTP to controller action mapping” you can see that the request routes to
show action of the controller. If the
customer exists, the server returns a successful response with a single customer
serialized as XML:
<?xml version="1.0" encoding="UTF-8"?> <customer> <age type="integer">30</age> <id type="integer">1</id> <name>Cody</name> </customer>
If the resource cannot be found, the server will
return an HTTP
404 Not Found status code. From Table 12.1, “Active Resource Exceptions” you can see that a 404 status code causes an ActiveResource::ResourceNotFound exception to be raised. An
exception is raised, instead of returning nil,
because the resource is being looked up by ID and is assumed to
exist. The resource not being found is therefore and exceptional state and an
exception is raised.
Finding resources from custom paths
Active Resource supports finding resources from
custom paths or actions that don't map precisely into the standard RESTful paradigm. A collection of resources can be found from a custom
path using the standard call to find(:all) along with a
custom :from option. Here we use the custom
collection path adults to retrieve all customers
over the age of 18.
customers = Api::Customer.find(:all, :from => :adults)
The :from option
causes the request to be sent to the custom /customers/adults.xml path of the web service instead of the standard
collection path /customers.xml. From Example 12.2, “A simple controller providing a RESTful web service.” you can see that the adults action is simply a custom query that returns a
collection of all customers over the age of 18.
Active Resource also supports finding a single
resource from a custom path or controller action. Here we select the oldest customer
from the database by using the custom oldest path
for the customer resource:
customer = Api::Customer.find(:one, :from => :oldest)
Passing :one as
the first argument to the find method tells the Active
Resource that the web service will only be returning a single resource. The :from option is always required when using find(:one). In this case Active Resource sends a
GET request to the /customers/oldest.xml, which in our case maps to the custom
oldest action of the web service, shown in
Example 12.2, “A simple controller providing a RESTful web service.”, that selects the oldest
customer in the system.
Creating Resources
Active Resource creates a resource by sending a POST request containing the resource's attribute data to
the collection URI of the resource. Creating a new resource looks
very similar to the creation of a new record using Active Record. Like with Active
Record, you can either build a new object with new and then
call save on it, or make a single call to create.
Creating a resource
A new resource can be created using either the
create class method, or by building a new object and
then calling save on it. Here we construct a new object and
then save it:
customer = Api::Customer.new(:name => "Daniel", :age => 29) customer.save
The call to save causes
Active Resource to serialize the dynamically generated attributes of the object and
send POST them to the collection URI of the resource:
POST /customers.xml HTTP/1.1
The request body contains the serialized customer data:
<?xml version="1.0" encoding="UTF-8"?> <customer> <name>Daniel</name> <age type="integer">29</age> </customer>
When the request is successful the server returns the
HTTP status code 201
Created, which indicates that the resource was successfully created on the
server. The server also returns an HTTP
Location header containing the URI location of the newly created resource:
Location: http://localhost/customers/3
Active Resource parses the object's id
from the Location header of the response. Knowing
the id of the resource allows the client to find, update, or delete the
resource in the future by sending requests to the resource's member URI.
When creating a resource the server has the choice of
whether or not return a response body. It is perfectly fine to return an empty
response body if there is no additional information generated at the time of save
that the client should know about. This would be accomplished by calling render in
the create controller action as follows:
render :nothing => true, :status => :created, :location => @customer
However, there are many instances where attributes
are set or updated at the time of save. An example is when the resource has created_at and updated_at timestamp attributes that are updated at the time of the save.
If the server doesn't return the updated representation of the resource in the
response body then the Active Resource client won't know about the new or changed
attributes until resource is fetched again using find or the object is reloaded with a call to reload.
In Example 12.2, “A simple controller providing a RESTful web service.” the controller does return the customer data in the response body when the
creation of the resource is successful because the call to render is passed the :xml =>
@customer option. The response body looks as follows:
<?xml version="1.0" encoding="UTF-8"?> <customer> <age type="integer">29</age> <id type="integer">3</id> <name>Daniel</name> </customer>
You can see that the customer resource now has an
id attribute that was not present in the data
sent by the Active Resource client.
Validation and Errors
Active Resource supports both client-side validations and server-side errors. Client-side validations are validations defined on the Active Resource class, just like you're used to defining in your Active Record models. Server-side errors are errors returned in the web service response of the server. Both client-side validations and server-side errors allow you to display the validation failures your objects encounter in a manner that is compatible with Active Record objects.
Client-Side Validations
Client side validations allow you check if your object is valid before making a request to the server. This is useful when you know what values are default in advance, as it saves you an entire remote HTTP request to the remote server, which will keep your application more responsive to your users. As mentioned earlier in the chapter, you need use client side validations in conjunction with a schema definition in order to ensure that the attributes you're validating are in existence at the time the validations are run.
You can utilize all of the validation methods that you're used to using with Active Record. See the section called “Validations” for more information on the types of validations that are possible. Let's take a look at a simple example:
class Customer < ActiveResource::Base
schema do
string 'name'
integer 'age'
end
validates_presence_of :name
validates_numericality_of :age, :greater_than_or_equal_to => 0
endHere we defined two validations with on the
name attribute and the other on the age attribute. If we then instantiate an instance of the Customer class in the console and call some methods on it we
get the following output:
customer = Customer.new customer.valid? # => false customer.errors.full_messages # => ["Name can't be blank", "Age is not a number"] customer.name = "Max Fightmaster" customer.age = 47 customer.valid? # => true
Notice how this is identical to how you'd work with an Active Record object. The ability to perform these types of client side validations and to utilize Active Resource objects in form fields is very powerful. It allows you to work with both local and remote objects in a similar fashion and to utilize the same helpers and logic for both types of objects.
Server-Side Errors
Active Resource has support for parsing any
server side error messages that are returned by the web service into an
errors collection on the model. The web service is
expected to return an HTTP
422 Unprocessable Entity status code along
with a collection of error elements when validation fails. Active Resource then
parses the errors out of the response and return false from the method making the request. The errors are then
accessible through an errors collection.
The Customer model on the
server, shown in Example 12.3, “Active Record Customer class on the server”, has validations
to ensure that the customer has a name and that the customer's age is greater
than 0. Let's attempt to create an invalid resource:
customer = Api::Customer.new(:name => "", :age => 0) customer.save
When the controller on the server tries to save
the model the validation will fail. As you can see from the create action in Example 12.2, “A simple controller providing a RESTful web service.”, the controller renders the
errors collection of the model with the
:unprocessable_entity
HTTP status code when customer cannot be saved. The HTTP
status code is used by Rails to indicate that validation
has failed. It is expected that the body of the response will contain the errors
that were generated by the request:422 Unprocessable
Entity
<?xml version="1.0" encoding="UTF-8"?> <errors> <error>Name can't be blank</error> <error>Age must be greater than 0</error> </errors>
Rails then parses each error message and tries to associate the message with the attributes of the Active Resource object at the time of the request. Any errors that cannot be associated with an attribute will appear on the base object. The errors can then be iterated through and interacted in similar way to Active Record errors:
customer.valid? # => false customer.errors.empty? # => false customer.errors.size # => 2 customer.errors.invalid?(:name) # => true customer.errors.on(:name) # => "can't be blank" customer.errors.invalid?(:age) # => true customer.errors.on(:age) # => "must be greater than 0" customer.errors.full_messages # => ["Name can't be blank", "Age must be greater than 0"]
Updating Resources
Active Resource updates a resource by sending a PUT request to the member URI of the
resource.
Unlike with an Active Record object, it is possible to
mass assign the ID of an Active Resource object. This
allows a client to update a resource that it knows the ID of without
having to first retrieve the resource from the remote server.
customer = Api::Customer.new(:id => 1, :age => 98) customer.save
However, you'll probably find a resource first and then update it:
customer = Api::Customer.find(1) customer.age = 98 customer.save
In the call to save, Active
Resource sends a PUT request with the updated
information for the resource to the resource's URI:
<?xml version="1.0" encoding="UTF-8"?> <customer> <name>Cody</name> <id type="integer">1</id> <age type="integer">98</age> </customer>
If the update is successful then the server will return
the HTTP status code 200 OK with an
empty response body.
If the update to the resource fails validation then the
server will return the errors causing the validation failure along with the HTTP status code 422 Unprocessable
Entity.
Deleting Resource
Active Resource deletes a resource by sending a DELETE request to the member URI of the
resource. Deleting a resource can be done by calling the delete class method of the Active Resource model with the ID
of the resource to be deleted:
Api::Customer.delete(1)
You can also call the destroy
method on an instance of an Active Resource model:
customer = Api::Customer.find(1) customer.destroy
Either way you choose to delete the resource, the same
HTTP request will be made, which is a DELETE request to the resource's member URI:
DELETE /customers/1.xml HTTP/1.1
The destroy action
shown in Example 12.2, “A simple controller providing a RESTful web service.” is the standard way of
deleting a record. The action returns a 200 Ok with
an empty response body.
Making Custom Requests
The case may be that the standard Active Resource
lifecycle methods are not sufficient for calling a custom operation on a resource.
Fortunately, Active Resource models are able to make custom GET, POST, PUT, and DELETE requests to both member
and collection URIs of a resource. ActiveResource::Base is extended with both class and instance methods for
each HTTP verb: get, post, put, and delete.
The custom URI is formed by appending
the name of the desired custom action to the collection or member URI. For a custom action on the resource's custom collection action oldest:
customer = Api::Customer.get(:oldest)
As we already saw in the section called “Finding resources from custom paths”, Active Resource sends an HTTP
GET request to the /customers/oldest.xml
URI. The same response is returned by the web service as before,
however, the response is not automatically mapped to an instance of the Api::Customer class. Normally the response from an Active Resource
lifecycle method is automatically mapped to an instance, or instances, of an Active
Resource model, but this is not the case for custom requests. A response to a custom
request is converted to a hash using Hash.from_xml.
In this case the response is a single hash:
{"name"=>"Cody", "id"=>1, "age"=>30}HTTP Authentication
Active Resource supports HTTP basic access authentication. HTTP basic authentication allows the web service to authenticate the Active Resource client based on a set of credentials with each request to the web service. See ??? for more detailed information on adding HTTP basic authentication to your application. Any use of HTTP basic authentication needs to be performed over a secure SSL connection in order to protect the credentials and data transmitted.
Setting up the server to perform HTTP basic authentication can be as simple as adding a before filter to the controller:
class CustomersController < ApplicationController
before_filter :authenticate
private
def authenticate
authenticate_or_request_with_http_basic do |user_name, password|
user_name == "cody" && password == "secret"
end
end
endThe controller will attempt to authenticate the user with
the user name cody and password secret or return an HTTP
401 Unauthorized status code.
To use Active Resource with a server that uses HTTP basic authentication you need to configure the credentials on the
Active Resource model. As with most other configuration settings, the credentials are
inherited to subclasses of ActiveResource::Base. This allows you
to set the credentials once on ActiveResource::Base or on a
particular subclass.
The credentials used by HTTP basic
authentication are a user name and a password. There are two ways to provide the user
name and password to be used. The first is to place the user name and password into the
site using the http://user:password@host:port format:
module Api
class Customer < ActiveResource::Base
self.site = 'http://cody:secret@localhost:3000'
end
endThe second way is to set the user and password class accessors. This method is
useful for code clarity, but also if the user name you're using is an email address. The
@ symbol in an email address will result in an
invalid URI.
module Api
class Customer < ActiveResource::Base
self.site = 'http://localhost:3000'
self.user = 'cody@example.com'
self.password = 'secret'
end
endOnce configured, Active Resource will provide the
credentials to the server on each request. As was shown above, the server returns an
HTTP
401 Unauthorized status code when the client cannot
be authenticated. The 401 Unauthorized status code
causes an ActiveResource::UnauthorizedAccess exception to be
raised in the Active Resource client.






Add a comment



Add a comment