mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Big documentation upgrade for ARes (closes #8694) [jeremymcanally]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7098 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
753cbf1cd4
commit
ae4838fff2
5 changed files with 674 additions and 189 deletions
|
@ -1,65 +1,57 @@
|
|||
= Active Resource -- Object-oriented REST services
|
||||
= Active Resource
|
||||
|
||||
Active Resource (ARes) connects business objects and REST web services. It is a library
|
||||
intended to provide transparent proxying capabilities between a client and a RESTful
|
||||
service (for which Rails provides the {Simply RESTful routing}[http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/resources.rb] implementation).
|
||||
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).
|
||||
|
||||
=== Configuration & Usage
|
||||
== Philosophy
|
||||
|
||||
Configuration is as simple as inheriting from ActiveResource::Base and providing a site
|
||||
class variable:
|
||||
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:
|
||||
|
||||
class Person < ActiveResource::Base
|
||||
self.site = "http://api.people.com:3000/"
|
||||
end
|
||||
|
||||
Person is now REST enable and can invoke REST services very similarly to how ActiveRecord invokes
|
||||
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
|
||||
# This will invoke the following Http call:
|
||||
# GET http://api.people.com:3000/people/1.xml
|
||||
# and will load up the XML response into a new
|
||||
# Person object
|
||||
#
|
||||
ryan = Person.find(1)
|
||||
Person.exists?(1) #=> true
|
||||
|
||||
# To create a new person - instantiate the object and call 'save',
|
||||
# which will invoke this Http call:
|
||||
# POST http://api.people.com:3000/people.xml
|
||||
# (and will submit the XML format of the person object in the request)
|
||||
#
|
||||
ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
|
||||
ryan.save #=> true
|
||||
ryan.id #=> 2
|
||||
Person.exists?(ryan.id) #=> true
|
||||
ryan.exists? #=> true
|
||||
As you can see, the methods are quite similar to Active Record's methods for dealing with database
|
||||
records. But rather than dealing with
|
||||
|
||||
# Resource creation can also use the convenience <tt>create</tt> method which
|
||||
# will request a resource save after instantiation.
|
||||
ryan = Person.create(:first => 'Ryan', :last => 'Daigle')
|
||||
ryan.exists? #=> true
|
||||
==== Protocol
|
||||
|
||||
# Updating is done with 'save' as well
|
||||
# PUT http://api.people.com:3000/people/1.xml
|
||||
#
|
||||
ryan = Person.find(1)
|
||||
ryan.first = 'Rizzle'
|
||||
ryan.save #=> true
|
||||
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:
|
||||
|
||||
# And destruction
|
||||
# DELETE http://api.people.com:3000/people/1.xml
|
||||
#
|
||||
ryan = Person.find(1)
|
||||
ryan.destroy #=> true # Or Person.delete(ryan.id)
|
||||
* 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.
|
||||
|
||||
|
||||
=== Protocol
|
||||
|
||||
ARes is built on a standard XML format for requesting and submitting resources. It mirrors the
|
||||
RESTful routing built into ActionController, though it's useful to discuss what ARes expects
|
||||
outside the context of ActionController as it is not dependent on a Rails-based RESTful implementation.
|
||||
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].
|
||||
|
||||
==== Find
|
||||
|
||||
|
@ -169,67 +161,5 @@ Destruction of a resource can be invoked as a class and instance method of the r
|
|||
Person.exists?(2) #=> false
|
||||
|
||||
|
||||
=== Errors & Validation
|
||||
You can find more usage information in the ActiveResource::Base documentation.
|
||||
|
||||
Error handling and validation is handled in much the same manner as you're used to seeing in
|
||||
ActiveRecord. Both the response code in the Http response and the body of the response are used to
|
||||
indicate that an error occurred.
|
||||
|
||||
==== Resource errors
|
||||
|
||||
When a get is requested for a resource that does not exist, the Http '404' (resource not found)
|
||||
response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
|
||||
exception.
|
||||
|
||||
# GET http://api.people.com:3000/people/1.xml
|
||||
# #=> Response (404)
|
||||
#
|
||||
ryan = Person.find(1) #=> Raises ActiveResource::ResourceNotFound
|
||||
|
||||
==== Validation errors
|
||||
|
||||
Creating and updating resources can lead to validation errors - i.e. 'First name cannot be empty' etc...
|
||||
These types of errors are denoted in the response by a response code of 422 and the xml representation
|
||||
of the validation errors. The save operation will then fail (with a 'false' return value) and the
|
||||
validation errors can be accessed on the resource in question.
|
||||
|
||||
# When
|
||||
#
|
||||
# PUT http://api.people.com:3000/people/1.xml
|
||||
#
|
||||
# is requested with invalid values, the expected response is:
|
||||
#
|
||||
# Response (422):
|
||||
# <errors><error>First cannot be empty</error></errors>
|
||||
#
|
||||
ryan = Person.find(1)
|
||||
ryan.first #=> ''
|
||||
ryan.save #=> false
|
||||
ryan.errors.invalid?(:first) #=> true
|
||||
ryan.errors.full_messages #=> ['First cannot be empty']
|
||||
|
||||
|
||||
==== Response errors
|
||||
|
||||
If the underlying Http request for an ARes operation results in an error response code, an
|
||||
exception will be raised. The following Http response codes will result in these exceptions:
|
||||
|
||||
200 - 399: Valid response, no exception
|
||||
404: ActiveResource::ResourceNotFound
|
||||
409: ActiveResource::ResourceConflict
|
||||
422: ActiveResource::ResourceInvalid (rescued by save as validation errors)
|
||||
401 - 499: ActiveResource::ClientError
|
||||
500 - 599: ActiveResource::ServerError
|
||||
|
||||
|
||||
=== Authentication
|
||||
|
||||
Many REST apis will require username/password authentication, usually in the form of
|
||||
Http authentication. This can easily be specified by putting the username and password
|
||||
in the Url of the ARes site:
|
||||
|
||||
class Person < ActiveResource::Base
|
||||
self.site = "http://ryan:password@api.people.com:3000/"
|
||||
end
|
||||
|
||||
For obvious reasons it is best if such services are available over https.
|
||||
|
|
|
@ -3,12 +3,155 @@ require 'cgi'
|
|||
require 'set'
|
||||
|
||||
module ActiveResource
|
||||
# ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
|
||||
#
|
||||
# For an outline of what Active Resource is capable of, see link:files/README.html.
|
||||
#
|
||||
# == Automated mapping
|
||||
#
|
||||
# Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
|
||||
# to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
|
||||
# Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
|
||||
# URI of the resources.
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://api.people.com:3000/"
|
||||
# end
|
||||
#
|
||||
# Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and
|
||||
# you can now use Active Resource's lifecycles methods to manipulate resources.
|
||||
#
|
||||
# == Lifecycle methods
|
||||
#
|
||||
# Active Resource exposes methods for creating, finding, updating, and deleting resources
|
||||
# from REST web services.
|
||||
#
|
||||
# ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
|
||||
# ryan.save #=> true
|
||||
# ryan.id #=> 2
|
||||
# Person.exists?(ryan.id) #=> true
|
||||
# ryan.exists? #=> true
|
||||
#
|
||||
# ryan = Person.find(1)
|
||||
# # => Resource holding our newly create Person object
|
||||
#
|
||||
# ryan.first = 'Rizzle'
|
||||
# ryan.save #=> true
|
||||
#
|
||||
# ryan.destroy #=> true
|
||||
#
|
||||
# As you can see, these are very similar to Active Record's lifecycle methods for database records.
|
||||
# You can read more about each of these methods in their respective documentation.
|
||||
#
|
||||
# === Custom REST methods
|
||||
#
|
||||
# Since simple CRUD/lifecycle methods can't accomplish every task, Active Resource also supports
|
||||
# defining your own custom REST methods.
|
||||
#
|
||||
# Person.new(:name => 'Ryan).post(:register)
|
||||
# # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
|
||||
#
|
||||
# Person.find(1).put(:promote, :position => 'Manager')
|
||||
# # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
|
||||
#
|
||||
# For more information on creating and using custom REST methods, see the
|
||||
# ActiveResource::CustomMethods documentation.
|
||||
#
|
||||
# == Validations
|
||||
#
|
||||
# You can validate resources client side by overriding validation methods in the base class.
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://api.people.com:3000/"
|
||||
# protected
|
||||
# def validate
|
||||
# errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# See the ActiveResource::Validations documentation for more information.
|
||||
#
|
||||
# == Authentication
|
||||
#
|
||||
# Many REST APIs will require authentication, usually in the form of basic
|
||||
# HTTP authentication. Authentication can be specified by putting the credentials
|
||||
# in the +site+ variable of the Active Resource class you need to authenticate.
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://ryan:password@api.people.com:3000/"
|
||||
# end
|
||||
#
|
||||
# For obvious security reasons, it is probably best if such services are available
|
||||
# over HTTPS.
|
||||
#
|
||||
# == Errors & Validation
|
||||
#
|
||||
# Error handling and validation is handled in much the same manner as you're used to seeing in
|
||||
# Active Record. Both the response code in the Http response and the body of the response are used to
|
||||
# indicate that an error occurred.
|
||||
#
|
||||
# === Resource errors
|
||||
#
|
||||
# When a get is requested for a resource that does not exist, the HTTP +404+ (Resource Not Found)
|
||||
# response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
|
||||
# exception.
|
||||
#
|
||||
# # GET http://api.people.com:3000/people/999.xml
|
||||
# ryan = Person.find(999) # => Raises ActiveResource::ResourceNotFound
|
||||
# # => Response = 404
|
||||
#
|
||||
# +404+ is just one of the HTTP error response codes that ActiveResource will handle with its own exception. The
|
||||
# following HTTP response codes will also result in these exceptions:
|
||||
#
|
||||
# 200 - 399:: Valid response, no exception
|
||||
# 404:: ActiveResource::ResourceNotFound
|
||||
# 409:: ActiveResource::ResourceConflict
|
||||
# 422:: ActiveResource::ResourceInvalid (rescued by save as validation errors)
|
||||
# 401 - 499:: ActiveResource::ClientError
|
||||
# 500 - 599:: ActiveResource::ServerError
|
||||
#
|
||||
# These custom exceptions allow you to deal with resource errors more naturally and with more precision
|
||||
# rather than returning a general HTTP error. For example:
|
||||
#
|
||||
# begin
|
||||
# ryan = Person.find(my_id)
|
||||
# rescue ActiveResource::ResourceNotFound
|
||||
# redirect_to :action => 'not_found'
|
||||
# rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid
|
||||
# redirect_to :action => 'new'
|
||||
# end
|
||||
#
|
||||
# === Validation errors
|
||||
#
|
||||
# Active Resource supports validations on resources and will return errors if any these validations fail
|
||||
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
|
||||
# a response code of +422+ and an XML representation of the validation errors. The save operation will
|
||||
# then fail (with a +false+ return value) and the validation errors can be accessed on the resource in question.
|
||||
#
|
||||
# ryan = Person.find(1)
|
||||
# ryan.first #=> ''
|
||||
# ryan.save #=> false
|
||||
#
|
||||
# # When
|
||||
# # PUT http://api.people.com:3000/people/1.xml
|
||||
# # is requested with invalid values, the response is:
|
||||
# #
|
||||
# # Response (422):
|
||||
# # <errors><error>First cannot be empty</error></errors>
|
||||
# #
|
||||
#
|
||||
# ryan.errors.invalid?(:first) #=> true
|
||||
# ryan.errors.full_messages #=> ['First cannot be empty']
|
||||
#
|
||||
# Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
|
||||
#
|
||||
class Base
|
||||
# The logger for diagnosing and tracing ARes calls.
|
||||
# The logger for diagnosing and tracing Active Resource calls.
|
||||
cattr_accessor :logger
|
||||
|
||||
class << self
|
||||
# Gets the URI of the resource's site
|
||||
# Gets the URI of the REST resources to map for this class. The site variable is required
|
||||
# ActiveResource's mapping to work.
|
||||
def site
|
||||
if defined?(@site)
|
||||
@site
|
||||
|
@ -17,13 +160,16 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
# Set the URI for the REST resources
|
||||
# Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
|
||||
# The site variable is required ActiveResource's mapping to work.
|
||||
def site=(site)
|
||||
@connection = nil
|
||||
@site = create_site_uri_from(site)
|
||||
end
|
||||
|
||||
# Base connection to remote service
|
||||
# An instance of ActiveResource::Connection that is the base connection to the remote service.
|
||||
# The +refresh+ parameter toggles whether or not the connection is refreshed at every request
|
||||
# or not (defaults to +false+).
|
||||
def connection(refresh = false)
|
||||
@connection = Connection.new(site) if refresh || @connection.nil?
|
||||
@connection
|
||||
|
@ -40,8 +186,8 @@ module ActiveResource
|
|||
attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
|
||||
attr_accessor_with_default(:primary_key, 'id') #:nodoc:
|
||||
|
||||
# Gets the resource prefix
|
||||
# prefix/collectionname/1.xml
|
||||
# Gets the prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
|
||||
# This method is regenerated at runtime based on what the prefix is set to.
|
||||
def prefix(options={})
|
||||
default = site.path
|
||||
default << '/' unless default[-1..-1] == '/'
|
||||
|
@ -50,13 +196,15 @@ module ActiveResource
|
|||
prefix(options)
|
||||
end
|
||||
|
||||
# An attribute reader for the source string for the resource path prefix. This
|
||||
# method is regenerated at runtime based on what the prefix is set to.
|
||||
def prefix_source
|
||||
prefix # generate #prefix and #prefix_source methods first
|
||||
prefix_source
|
||||
end
|
||||
|
||||
# Sets the resource prefix
|
||||
# prefix/collectionname/1.xml
|
||||
# Sets the prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>).
|
||||
# Default value is <tt>site.path</tt>.
|
||||
def prefix=(value = '/')
|
||||
# Replace :placeholders with '#{embedded options[:lookups]}'
|
||||
prefix_call = value.gsub(/:\w+/) { |key| "\#{options[#{key}]}" }
|
||||
|
@ -77,23 +225,53 @@ module ActiveResource
|
|||
alias_method :set_element_name, :element_name= #:nodoc:
|
||||
alias_method :set_collection_name, :collection_name= #:nodoc:
|
||||
|
||||
# Gets the element path for the given ID. If no query_options are given, they are split from the prefix options:
|
||||
# Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
|
||||
# will split from the prefix options.
|
||||
#
|
||||
# ==== Options
|
||||
# +prefix_options+:: A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt>
|
||||
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
|
||||
# +query_options+:: A hash to add items to the query string for the request.
|
||||
#
|
||||
# ==== Examples
|
||||
# Post.element_path(1)
|
||||
# # => /posts/1.xml
|
||||
#
|
||||
# Comment.element_path(1, :post_id => 5)
|
||||
# # => /posts/5/comments/1.xml
|
||||
#
|
||||
# Comment.element_path(1, :post_id => 5, :active => 1)
|
||||
# # => /posts/5/comments/1.xml?active=1
|
||||
#
|
||||
# Comment.element_path(1, {:post_id => 5}, {:active => 1})
|
||||
# # => /posts/5/comments/1.xml?active=1
|
||||
#
|
||||
# Post.element_path(1) # => /posts/1.xml
|
||||
# Comment.element_path(1, :post_id => 5) # => /posts/5/comments/1.xml
|
||||
# Comment.element_path(1, :post_id => 5, :active => 1) # => /posts/5/comments/1.xml?active=1
|
||||
# Comment.element_path(1, {:post_id => 5}, {:active => 1}) # => /posts/5/comments/1.xml?active=1
|
||||
def element_path(id, prefix_options = {}, query_options = nil)
|
||||
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
||||
"#{prefix(prefix_options)}#{collection_name}/#{id}.xml#{query_string(query_options)}"
|
||||
end
|
||||
|
||||
# Gets the collection path. If no query_options are given, they are split from the prefix options:
|
||||
# Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
|
||||
# will split from the +prefix_options+.
|
||||
#
|
||||
# ==== Options
|
||||
# +prefix_options+:: A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt>
|
||||
# would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
|
||||
# +query_options+:: A hash to add items to the query string for the request.
|
||||
#
|
||||
# ==== Examples
|
||||
# Post.collection_path
|
||||
# # => /posts.xml
|
||||
#
|
||||
# Comment.collection_path(:post_id => 5)
|
||||
# # => /posts/5/comments.xml
|
||||
#
|
||||
# Comment.collection_path(:post_id => 5, :active => 1)
|
||||
# # => /posts/5/comments.xml?active=1
|
||||
#
|
||||
# Comment.collection_path({:post_id => 5}, {:active => 1})
|
||||
# # => /posts/5/comments.xml?active=1
|
||||
#
|
||||
# Post.collection_path # => /posts.xml
|
||||
# Comment.collection_path(:post_id => 5) # => /posts/5/comments.xml
|
||||
# Comment.collection_path(:post_id => 5, :active => 1) # => /posts/5/comments.xml?active=1
|
||||
# Comment.collection_path({:post_id => 5}, {:active => 1}) # => /posts/5/comments.xml?active=1
|
||||
def collection_path(prefix_options = {}, query_options = nil)
|
||||
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
||||
"#{prefix(prefix_options)}#{collection_name}.xml#{query_string(query_options)}"
|
||||
|
@ -102,30 +280,77 @@ module ActiveResource
|
|||
alias_method :set_primary_key, :primary_key= #:nodoc:
|
||||
|
||||
# Create a new resource instance and request to the remote service
|
||||
# that it be saved. This is equivalent to the following simultaneous calls:
|
||||
# that it be saved, making it equivalent to the following simultaneous calls:
|
||||
#
|
||||
# ryan = Person.new(:first => 'ryan')
|
||||
# ryan.save
|
||||
#
|
||||
# The newly created resource is returned. If a failure has occurred an
|
||||
# exception will be raised (see save). If the resource is invalid and
|
||||
# has not been saved then <tt>resource.valid?</tt> will return <tt>false</tt>,
|
||||
# while <tt>resource.new?</tt> will still return <tt>true</tt>.
|
||||
#
|
||||
# has not been saved then valid? will return <tt>false</tt>,
|
||||
# while new? will still return <tt>true</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
# Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true)
|
||||
# my_person = Person.find(:first)
|
||||
# my_person.email
|
||||
# # => myname@nospam.com
|
||||
#
|
||||
# dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true)
|
||||
# dhh.valid?
|
||||
# # => true
|
||||
# dhh.new?
|
||||
# # => false
|
||||
#
|
||||
# # We'll assume that there's a validation that requires the name attribute
|
||||
# that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true)
|
||||
# that_guy.valid?
|
||||
# # => false
|
||||
# that_guy.new?
|
||||
# # => true
|
||||
#
|
||||
def create(attributes = {})
|
||||
returning(self.new(attributes)) { |res| res.save }
|
||||
end
|
||||
|
||||
# Core method for finding resources. Used similarly to Active Record's find method.
|
||||
#
|
||||
# Person.find(1) # => GET /people/1.xml
|
||||
# Person.find(:all) # => GET /people.xml
|
||||
# Person.find(:all, :params => { :title => "CEO" }) # => GET /people.xml?title=CEO
|
||||
# Person.find(:all, :from => :managers) # => GET /people/managers.xml
|
||||
# Person.find(:all, :from => "/companies/1/people.xml") # => GET /companies/1/people.xml
|
||||
# Person.find(:one, :from => :leader) # => GET /people/leader.xml
|
||||
# Person.find(:one, :from => "/companies/1/manager.xml") # => GET /companies/1/manager.xml
|
||||
# StreetAddress.find(1, :params => { :person_id => 1 }) # => GET /people/1/street_addresses/1.xml
|
||||
# ==== Arguments
|
||||
# The first argument is considered to be the scope of the query. That is, how many
|
||||
# resources are returned from the request. It can be one of the following.
|
||||
#
|
||||
# +:one+:: Returns a single resource.
|
||||
# +:first+:: Returns the first resource found.
|
||||
# +:all+:: Returns every resource that matches the request.
|
||||
#
|
||||
# ==== Options
|
||||
# +from+:: Sets the path or custom method that resources will be fetched from.
|
||||
# +params+:: Sets query and prefix (nested URL) parameters.
|
||||
#
|
||||
# ==== Examples
|
||||
# Person.find(1)
|
||||
# # => GET /people/1.xml
|
||||
#
|
||||
# Person.find(:all)
|
||||
# # => GET /people.xml
|
||||
#
|
||||
# Person.find(:all, :params => { :title => "CEO" })
|
||||
# # => GET /people.xml?title=CEO
|
||||
#
|
||||
# Person.find(:first, :from => :managers)
|
||||
# # => GET /people/managers.xml
|
||||
#
|
||||
# Person.find(:all, :from => "/companies/1/people.xml")
|
||||
# # => GET /companies/1/people.xml
|
||||
#
|
||||
# Person.find(:one, :from => :leader)
|
||||
# # => GET /people/leader.xml
|
||||
#
|
||||
# Person.find(:one, :from => "/companies/1/manager.xml")
|
||||
# # => GET /companies/1/manager.xml
|
||||
#
|
||||
# StreetAddress.find(1, :params => { :person_id => 1 })
|
||||
# # => GET /people/1/street_addresses/1.xml
|
||||
def find(*arguments)
|
||||
scope = arguments.slice!(0)
|
||||
options = arguments.slice!(0) || {}
|
||||
|
@ -138,11 +363,38 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
# Deletes the resources with the ID in the +id+ parameter.
|
||||
#
|
||||
# ==== Options
|
||||
# All options specify prefix and query parameters.
|
||||
#
|
||||
# ==== Examples
|
||||
# Event.delete(2)
|
||||
# # => DELETE /events/2
|
||||
#
|
||||
# Event.create(:name => 'Free Concert', :location => 'Community Center')
|
||||
# my_event = Event.find(:first)
|
||||
# # => Events (id: 7)
|
||||
# Event.delete(my_event.id)
|
||||
# # => DELETE /events/7
|
||||
#
|
||||
# # Let's assume a request to events/5/cancel.xml
|
||||
# Event.delete(params[:id])
|
||||
# # => DELETE /events/5
|
||||
#
|
||||
def delete(id, options = {})
|
||||
connection.delete(element_path(id, options))
|
||||
end
|
||||
|
||||
# Evalutes to <tt>true</tt> if the resource is found.
|
||||
# Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
|
||||
#
|
||||
# ==== Examples
|
||||
# Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
|
||||
# Note.exists?(1)
|
||||
# # => true
|
||||
#
|
||||
# Note.exists(1349)
|
||||
# # => false
|
||||
def exists?(id, options = {})
|
||||
id && !find_single(id, options).nil?
|
||||
rescue ActiveResource::ResourceNotFound
|
||||
|
@ -226,33 +478,79 @@ module ActiveResource
|
|||
attr_accessor :attributes #:nodoc:
|
||||
attr_accessor :prefix_options #:nodoc:
|
||||
|
||||
# Constructor method for new resources; the optional +attributes+ parameter takes a +Hash+
|
||||
# of attributes for the new resource.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_course = Course.new
|
||||
# my_course.name = "Western Civilization"
|
||||
# my_course.lecturer = "Don Trotter"
|
||||
# my_course.save
|
||||
#
|
||||
# my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
|
||||
# my_other_course.save
|
||||
def initialize(attributes = {})
|
||||
@attributes = {}
|
||||
@prefix_options = {}
|
||||
load(attributes)
|
||||
end
|
||||
|
||||
# Is the resource a new object?
|
||||
# A method to determine if the resource a new object (i.e., it has not been POSTed to the remote service yet).
|
||||
#
|
||||
# ==== Examples
|
||||
# not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
|
||||
# not_new.new?
|
||||
# # => false
|
||||
#
|
||||
# is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
|
||||
# is_new.new?
|
||||
# # => true
|
||||
#
|
||||
# is_new.save
|
||||
# is_new.new?
|
||||
# # => false
|
||||
#
|
||||
def new?
|
||||
id.nil?
|
||||
end
|
||||
|
||||
# Get the id of the object.
|
||||
# Get the +id+ attribute of the resource.
|
||||
def id
|
||||
attributes[self.class.primary_key]
|
||||
end
|
||||
|
||||
# Set the id of the object.
|
||||
# Set the +id+ attribute of the resource.
|
||||
def id=(id)
|
||||
attributes[self.class.primary_key] = id
|
||||
end
|
||||
|
||||
# True if and only if +other+ is the same object or is an instance of the same class, is not +new?+, and has the same +id+.
|
||||
# Test for equality. Resource are equal if and only if +other+ is the same object or
|
||||
# is an instance of the same class, is not +new?+, and has the same +id+.
|
||||
#
|
||||
# ==== Examples
|
||||
# ryan = Person.create(:name => 'Ryan')
|
||||
# jamie = Person.create(:name => 'Jamie')
|
||||
#
|
||||
# ryan == jamie
|
||||
# # => false (Different name attribute and id)
|
||||
#
|
||||
# ryan_again = Person.new(:name => 'Ryan')
|
||||
# ryan == ryan_again
|
||||
# # => false (ryan_again is new?)
|
||||
#
|
||||
# ryans_clone = Person.create(:name => 'Ryan')
|
||||
# ryan == ryans_clone
|
||||
# # => false (Different id attributes)
|
||||
#
|
||||
# ryans_twin = Person.find(ryan.id)
|
||||
# ryan == ryans_twin
|
||||
# # => true
|
||||
#
|
||||
def ==(other)
|
||||
other.equal?(self) || (other.instance_of?(self.class) && !other.new? && other.id == id)
|
||||
end
|
||||
|
||||
# Delegates to ==
|
||||
# Tests for equality (delegates to ==).
|
||||
def eql?(other)
|
||||
self == other
|
||||
end
|
||||
|
@ -263,6 +561,22 @@ module ActiveResource
|
|||
id.hash
|
||||
end
|
||||
|
||||
# Duplicate the current resource without saving it.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_invoice = Invoice.create(:customer => 'That Company')
|
||||
# next_invoice = my_invoice.dup
|
||||
# next_invoice.new?
|
||||
# # => true
|
||||
#
|
||||
# next_invoice.save
|
||||
# next_invoice == my_invoice
|
||||
# # => false (different id attributes)
|
||||
#
|
||||
# my_invoice.customer
|
||||
# # => That Company
|
||||
# next_invoice.customer
|
||||
# # => That Company
|
||||
def dup
|
||||
returning new do |resource|
|
||||
resource.attributes = @attributes
|
||||
|
@ -270,35 +584,137 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
# Delegates to +create+ if a new object, +update+ if its old. If the response to the save includes a body,
|
||||
# it will be assumed that this body is XML for the final object as it looked after the save (which would include
|
||||
# attributes like created_at that wasn't part of the original submit).
|
||||
# A method to save (+POST+) or update (+PUT+) a resource. It delegates to +create+ if a new object,
|
||||
# +update+ if it is existing. If the response to the save includes a body, it will be assumed that this body
|
||||
# is XML for the final object as it looked after the save (which would include attributes like +created_at+
|
||||
# that weren't part of the original submit).
|
||||
#
|
||||
# ==== Examples
|
||||
# my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2)
|
||||
# my_company.new?
|
||||
# # => true
|
||||
# my_company.save
|
||||
# # => POST /companies/ (create)
|
||||
#
|
||||
# my_company.new?
|
||||
# # => false
|
||||
# my_company.size = 10
|
||||
# my_company.save
|
||||
# # => PUT /companies/1 (update)
|
||||
def save
|
||||
new? ? create : update
|
||||
end
|
||||
|
||||
# Delete the resource.
|
||||
# Deletes the resource from the remote service.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_id = 3
|
||||
# my_person = Person.find(my_id)
|
||||
# my_person.destroy
|
||||
# Person.find(my_id)
|
||||
# # => 404 (Resource Not Found)
|
||||
#
|
||||
# new_person = Person.create(:name => 'James')
|
||||
# new_id = new_person.id
|
||||
# # => 7
|
||||
# new_person.destroy
|
||||
# Person.find(new_id)
|
||||
# # => 404 (Resource Not Found)
|
||||
def destroy
|
||||
connection.delete(element_path, self.class.headers)
|
||||
end
|
||||
|
||||
# Evaluates to <tt>true</tt> if this resource is found.
|
||||
# Evaluates to <tt>true</tt> if this resource is not +new?+ and is
|
||||
# found on the remote service. Using this method, you can check for
|
||||
# resources that may have been deleted between the object's instantiation
|
||||
# and actions on it.
|
||||
#
|
||||
# ==== Examples
|
||||
# Person.create(:name => 'Theodore Roosevelt')
|
||||
# that_guy = Person.find(:first)
|
||||
# that_guy.exists?
|
||||
# # => true
|
||||
#
|
||||
# that_lady = Person.new(:name => 'Paul Bean')
|
||||
# that_lady.exists?
|
||||
# # => false
|
||||
#
|
||||
# guys_id = that_guy.id
|
||||
# Person.delete(guys_id)
|
||||
# that_guy.exists?
|
||||
# # => false
|
||||
def exists?
|
||||
!new? && self.class.exists?(id, :params => prefix_options)
|
||||
end
|
||||
|
||||
# Convert the resource to an XML string
|
||||
# A method to convert the the resource to an XML string.
|
||||
#
|
||||
# ==== Options
|
||||
# The +options+ parameter is handed off to the +to_xml+ method on each
|
||||
# attribute, so it has the same options as the +to_xml+ methods in
|
||||
# ActiveSupport.
|
||||
#
|
||||
# indent:: Set the indent level for the XML output (default is +2+).
|
||||
# dasherize:: Boolean option to determine whether or not element names should
|
||||
# replace underscores with dashes (default is +false+).
|
||||
# skip_instruct:: Toggle skipping the +instruct!+ call on the XML builder
|
||||
# that generates the XML declaration (default is +false+).
|
||||
#
|
||||
# ==== Examples
|
||||
# my_group = SubsidiaryGroup.find(:first)
|
||||
# my_group.to_xml
|
||||
# # => <?xml version="1.0" encoding="UTF-8"?>
|
||||
# # <subsidiary_group> [...] </subsidiary_group>
|
||||
#
|
||||
# my_group.to_xml(:dasherize => true)
|
||||
# # => <?xml version="1.0" encoding="UTF-8"?>
|
||||
# # <subsidiary-group> [...] </subsidiary-group>
|
||||
#
|
||||
# my_group.to_xml(:skip_instruct => true)
|
||||
# # => <subsidiary_group> [...] </subsidiary_group>
|
||||
def to_xml(options={})
|
||||
attributes.to_xml({:root => self.class.element_name}.merge(options))
|
||||
end
|
||||
|
||||
# Reloads the attributes of this object from the remote web service.
|
||||
# A method to reload the attributes of this object from the remote web service.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_branch = Branch.find(:first)
|
||||
# my_branch.name
|
||||
# # => Wislon Raod
|
||||
#
|
||||
# # Another client fixes the typo...
|
||||
#
|
||||
# my_branch.name
|
||||
# # => Wislon Raod
|
||||
# my_branch.reload
|
||||
# my_branch.name
|
||||
# # => Wilson Road
|
||||
def reload
|
||||
self.load(self.class.find(id, :params => @prefix_options).attributes)
|
||||
end
|
||||
|
||||
# Manually load attributes from a hash. Recursively loads collections of
|
||||
# resources.
|
||||
# A method to manually load attributes from a hash. Recursively loads collections of
|
||||
# resources. This method is called in initialize and create when a +Hash+ of attributes
|
||||
# is provided.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
|
||||
#
|
||||
# the_supplier = Supplier.find(:first)
|
||||
# the_supplier.name
|
||||
# # => 'J&M Textiles'
|
||||
# the_supplier.load(my_attrs)
|
||||
# the_supplier.name('J&J Textiles')
|
||||
#
|
||||
# # These two calls are the same as Supplier.new(my_attrs)
|
||||
# my_supplier = Supplier.new
|
||||
# my_supplier.load(my_attrs)
|
||||
#
|
||||
# # These three calls are the same as Supplier.create(my_attrs)
|
||||
# your_supplier = Supplier.new
|
||||
# your_supplier.load(my_attrs)
|
||||
# your_supplier.save
|
||||
def load(attributes)
|
||||
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
||||
@prefix_options, attributes = split_options(attributes)
|
||||
|
@ -321,8 +737,9 @@ module ActiveResource
|
|||
# For checking respond_to? without searching the attributes (which is faster).
|
||||
alias_method :respond_to_without_attributes?, :respond_to?
|
||||
|
||||
# A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
|
||||
# person.respond_to?("name?") which will all return true.
|
||||
# A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a +Person+ object with a
|
||||
# +name+ attribute can answer +true+ to +my_person.respond_to?("name")+, +my_person.respond_to?("name=")+, and
|
||||
# +my_person.respond_to?("name?")+.
|
||||
def respond_to?(method, include_priv = false)
|
||||
method_name = method.to_s
|
||||
if attributes.nil?
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'uri'
|
|||
require 'benchmark'
|
||||
|
||||
module ActiveResource
|
||||
class ConnectionError < StandardError
|
||||
class ConnectionError < StandardError # :nodoc:
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, message = nil)
|
||||
|
@ -18,20 +18,28 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
class ClientError < ConnectionError; end # 4xx Client Error
|
||||
class ResourceNotFound < ClientError; end # 404 Not Found
|
||||
class ResourceConflict < ClientError; end # 409 Conflict
|
||||
# 4xx Client Error
|
||||
class ClientError < ConnectionError; end # :nodoc:
|
||||
|
||||
# 404 Not Found
|
||||
class ResourceNotFound < ClientError; end # :nodoc:
|
||||
|
||||
# 409 Conflict
|
||||
class ResourceConflict < ClientError; end # :nodoc:
|
||||
|
||||
class ServerError < ConnectionError; end # 5xx Server Error
|
||||
# 5xx Server Error
|
||||
class ServerError < ConnectionError; end # :nodoc:
|
||||
|
||||
# 405 Method Not Allowed
|
||||
class MethodNotAllowed < ClientError
|
||||
class MethodNotAllowed < ClientError # :nodoc:
|
||||
def allowed_methods
|
||||
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
||||
end
|
||||
end
|
||||
|
||||
# Class to handle connections to remote services.
|
||||
# Class to handle connections to remote web services.
|
||||
# This class is used by ActiveResource::Base to interface with REST
|
||||
# services.
|
||||
class Connection
|
||||
attr_reader :site
|
||||
|
||||
|
@ -46,6 +54,8 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
# The +site+ parameter is required and will set the +site+
|
||||
# attribute to the URI for the remote resource service.
|
||||
def initialize(site)
|
||||
raise ArgumentError, 'Missing site URI' unless site
|
||||
self.site = site
|
||||
|
@ -84,7 +94,6 @@ module ActiveResource
|
|||
from_xml_data(Hash.from_xml(response.body))
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Makes request to remote service.
|
||||
def request(method, path, *arguments)
|
||||
|
@ -152,6 +161,5 @@ module ActiveResource
|
|||
data
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,31 +1,34 @@
|
|||
# Support custom methods and sub-resources for REST.
|
||||
# A module to support custom REST methods and sub-resources, allowing you to break out
|
||||
# of the "default" REST methods with your own custom resource requests. For example,
|
||||
# say you use Rails to expose a REST service and configure your routes with:
|
||||
#
|
||||
# Say you on the server configure your routes with:
|
||||
# map.resources :people, :new => { :register => :post },
|
||||
# :element => { :promote => :put, :deactivate => :delete }
|
||||
# :collection => { :active => :get }
|
||||
#
|
||||
# map.resources :people, :new => { :register => :post },
|
||||
# :element => { :promote => :put, :deactivate => :delete }
|
||||
# :collection => { :active => :get }
|
||||
# This route set creates routes for the following http requests:
|
||||
#
|
||||
# Which creates routes for the following http requests:
|
||||
# POST /people/new/register.xml #=> PeopleController.register
|
||||
# PUT /people/1/promote.xml #=> PeopleController.promote with :id => 1
|
||||
# DELETE /people/1/deactivate.xml #=> PeopleController.deactivate with :id => 1
|
||||
# GET /people/active.xml #=> PeopleController.active
|
||||
#
|
||||
# POST /people/new/register.xml #=> PeopleController.register
|
||||
# PUT /people/1/promote.xml #=> PeopleController.promote with :id => 1
|
||||
# DELETE /people/1/deactivate.xml #=> PeopleController.deactivate with :id => 1
|
||||
# GET /people/active.xml #=> PeopleController.active
|
||||
#
|
||||
# This module provides the ability for Active Resource to call these
|
||||
# custom REST methods and get the response back.
|
||||
# Using this module, Active Resource can use these custom REST methods just like the
|
||||
# standard methods.
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://37s.sunrise.i:3000"
|
||||
# end
|
||||
#
|
||||
# Person.new(:name => 'Ryan).post(:register) #=> { :id => 1, :name => 'Ryan' }
|
||||
# Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.xml
|
||||
# # => { :id => 1, :name => 'Ryan' }
|
||||
#
|
||||
# Person.find(1).put(:promote, :position => 'Manager')
|
||||
# Person.find(1).delete(:deactivate)
|
||||
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml
|
||||
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml
|
||||
#
|
||||
# Person.get(:active) # GET /people/active.xml
|
||||
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
||||
#
|
||||
# Person.get(:active) #=> [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
||||
module ActiveResource
|
||||
module CustomMethods
|
||||
def self.included(within)
|
||||
|
|
|
@ -14,23 +14,76 @@ module ActiveResource
|
|||
@base, @errors = base, {}
|
||||
end
|
||||
|
||||
# Add an error to the base Active Resource object rather than an attribute.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_folder = Folder.find(1)
|
||||
# my_folder.errors.add_to_base("You can't edit an existing folder")
|
||||
# my_folder.errors.on_base
|
||||
# # => "You can't edit an existing folder"
|
||||
#
|
||||
# my_folder.errors.add_to_base("This folder has been tagged as frozen")
|
||||
# my_folder.valid?
|
||||
# # => false
|
||||
# my_folder.errors.on_base
|
||||
# # => ["You can't edit an existing folder", "This folder has been tagged as frozen"]
|
||||
#
|
||||
def add_to_base(msg)
|
||||
add(:base, msg)
|
||||
end
|
||||
|
||||
# Adds an error to an Active Resource object's attribute (named for the +attribute+ parameter)
|
||||
# with the error message in +msg+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_resource = Node.find(1)
|
||||
# my_resource.errors.add('name', 'can not be "base"') if my_resource.name == 'base'
|
||||
# my_resource.errors.on('name')
|
||||
# # => 'can not be "base"!'
|
||||
#
|
||||
# my_resource.errors.add('desc', 'can not be blank') if my_resource.desc == ''
|
||||
# my_resource.valid?
|
||||
# # => false
|
||||
# my_resource.errors.on('desc')
|
||||
# # => 'can not be blank!'
|
||||
#
|
||||
def add(attribute, msg)
|
||||
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
||||
@errors[attribute.to_s] << msg
|
||||
end
|
||||
|
||||
# Returns true if the specified +attribute+ has errors associated with it.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_resource = Disk.find(1)
|
||||
# my_resource.errors.add('location', 'must be Main') unless my_resource.location == 'Main'
|
||||
# my_resource.errors.on('location')
|
||||
# # => 'must be Main!'
|
||||
#
|
||||
# my_resource.errors.invalid?('location')
|
||||
# # => true
|
||||
# my_resource.errors.invalid?('name')
|
||||
# # => false
|
||||
def invalid?(attribute)
|
||||
!@errors[attribute.to_s].nil?
|
||||
end
|
||||
|
||||
# * Returns nil, if no errors are associated with the specified +attribute+.
|
||||
# * Returns the error message, if one error is associated with the specified +attribute+.
|
||||
# * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
|
||||
# A method to return the errors associated with +attribute+, which returns nil, if no errors are
|
||||
# associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+,
|
||||
# or an array of error messages if more than one error is associated with the specified +attribute+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
# my_person.errors.on('login')
|
||||
# # => nil
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.on('login')
|
||||
# # => 'can not be empty'
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be longer than 10 characters') if my_person.login.length > 10
|
||||
# my_person.errors.on('login')
|
||||
# # => ['can not be empty', 'can not be longer than 10 characters']
|
||||
def on(attribute)
|
||||
errors = @errors[attribute.to_s]
|
||||
return nil if errors.nil?
|
||||
|
@ -39,23 +92,72 @@ module ActiveResource
|
|||
|
||||
alias :[] :on
|
||||
|
||||
# Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
|
||||
# A method to return errors assigned to +base+ object through add_to_base, which returns nil, if no errors are
|
||||
# associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+,
|
||||
# or an array of error messages if more than one error is associated with the specified +attribute+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_account = Account.find(1)
|
||||
# my_account.errors.on_base
|
||||
# # => nil
|
||||
#
|
||||
# my_account.errors.add_to_base("This account is frozen")
|
||||
# my_account.errors.on_base
|
||||
# # => "This account is frozen"
|
||||
#
|
||||
# my_account.errors.add_to_base("This account has been closed")
|
||||
# my_account.errors.on_base
|
||||
# # => ["This account is frozen", "This account has been closed"]
|
||||
#
|
||||
def on_base
|
||||
on(:base)
|
||||
end
|
||||
|
||||
# Yields each attribute and associated message per error added.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# messages = ''
|
||||
# my_person.errors.each {|attr, msg| messages += attr.humanize + " " + msg + "<br />"}
|
||||
# messages
|
||||
# # => "Login can not be empty<br />Password can not be empty<br />"
|
||||
#
|
||||
def each
|
||||
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
||||
end
|
||||
|
||||
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
||||
# through iteration as "First name can't be empty".
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# messages = ''
|
||||
# my_person.errors.each_full {|msg| messages += msg + "<br/>"}
|
||||
# messages
|
||||
# # => "Login can not be empty<br />Password can not be empty<br />"
|
||||
#
|
||||
def each_full
|
||||
full_messages.each { |msg| yield msg }
|
||||
end
|
||||
|
||||
# Returns all the full error messages in an array.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# messages = ''
|
||||
# my_person.errors.full_messages.each {|msg| messages += msg + "<br/>"}
|
||||
# messages
|
||||
# # => "Login can not be empty<br />Password can not be empty<br />"
|
||||
#
|
||||
def full_messages
|
||||
full_messages = []
|
||||
|
||||
|
@ -79,6 +181,17 @@ module ActiveResource
|
|||
|
||||
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
||||
# with this as well.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
# my_person.errors.size
|
||||
# # => 0
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# my_person.error.size
|
||||
# # => 2
|
||||
#
|
||||
def size
|
||||
@errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
|
||||
end
|
||||
|
@ -86,6 +199,7 @@ module ActiveResource
|
|||
alias_method :count, :size
|
||||
alias_method :length, :size
|
||||
|
||||
# Grabs errors from the XML response.
|
||||
def from_xml(xml)
|
||||
clear
|
||||
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
|
||||
|
@ -102,9 +216,12 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
# Module to allow validation of ActiveResource objects, which are implemented by overriding +Base#validate+ or its variants.
|
||||
# Each of these methods can inspect the state of the object, which usually means ensuring that a number of
|
||||
# attributes have a certain value (such as not empty, within a given range, matching a certain regular expression). For example:
|
||||
# Module to allow validation of ActiveResource objects, which creates an Errors instance for every resource.
|
||||
# Methods are implemented by overriding +Base#validate+ or its variants Each of these methods can inspect
|
||||
# the state of the object, which usually means ensuring that a number of attributes have a certain value
|
||||
# (such as not empty, within a given range, matching a certain regular expression and so on).
|
||||
#
|
||||
# ==== Example
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://www.localhost.com:3000/"
|
||||
|
@ -133,7 +250,6 @@ module ActiveResource
|
|||
# person.attributes = { "last_name" => "Halpert", "phone_number" => "555-5555" }
|
||||
# person.save # => true (and person is now saved to the remote service)
|
||||
#
|
||||
# An Errors object is automatically created for every resource.
|
||||
module Validations
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
|
@ -141,6 +257,7 @@ module ActiveResource
|
|||
end
|
||||
end
|
||||
|
||||
# Validate a resource and save (POST) it to the remote web service.
|
||||
def save_with_validation
|
||||
save_without_validation
|
||||
true
|
||||
|
@ -149,6 +266,16 @@ module ActiveResource
|
|||
false
|
||||
end
|
||||
|
||||
# Checks for errors on an object (i.e., is resource.errors empty?).
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.create(params[:person])
|
||||
# my_person.valid?
|
||||
# # => true
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.valid?
|
||||
# # => false
|
||||
def valid?
|
||||
errors.empty?
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue