aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/rails-2.1.0/activeresource/lib
diff options
context:
space:
mode:
authorfrancis <francis>2008-09-03 14:05:56 +0000
committerfrancis <francis>2008-09-03 14:05:56 +0000
commit3823e5cbc642d95a17fdb316736e7afe6334140c (patch)
treeaaa186d9fc8fead8d777c78b98de87c02fc18ca2 /vendor/rails-2.1.0/activeresource/lib
parentaf7a374ae63935168df2bbc02288f54f52c701a5 (diff)
Rails 2.1.0
Diffstat (limited to 'vendor/rails-2.1.0/activeresource/lib')
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource.rb47
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/base.rb1014
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/connection.rb207
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/custom_methods.rb119
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/formats.rb14
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/formats/json_format.rb23
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/formats/xml_format.rb34
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/http_mock.rb217
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/validations.rb288
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/active_resource/version.rb9
-rw-r--r--vendor/rails-2.1.0/activeresource/lib/activeresource.rb1
11 files changed, 1973 insertions, 0 deletions
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource.rb
new file mode 100644
index 000000000..18347457a
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource.rb
@@ -0,0 +1,47 @@
+#--
+# Copyright (c) 2006 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+$:.unshift(File.dirname(__FILE__)) unless
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+unless defined?(ActiveSupport)
+ begin
+ $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
+ require 'active_support'
+ rescue LoadError
+ require 'rubygems'
+ gem 'activesupport'
+ end
+end
+
+require 'active_resource/formats'
+require 'active_resource/base'
+require 'active_resource/validations'
+require 'active_resource/custom_methods'
+
+module ActiveResource
+ Base.class_eval do
+ include Validations
+ include CustomMethods
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/base.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/base.rb
new file mode 100644
index 000000000..463ee9f1e
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/base.rb
@@ -0,0 +1,1014 @@
+require 'active_resource/connection'
+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/vendor/rails/activeresource/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. In the case where you already have
+ # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
+ #
+ # class PersonResource < ActiveResource::Base
+ # self.site = "http://api.people.com:3000/"
+ # self.element_name = "person"
+ # end
+ #
+ #
+ # == 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 created 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. To invoke them, Active Resource provides the <tt>get</tt>,
+ # <tt>post</tt>, <tt>put</tt> and <tt>delete</tt> methods where you can specify a custom REST method
+ # name to invoke.
+ #
+ # # POST to the custom 'register' REST method, i.e. POST /people/new/register.xml.
+ # Person.new(:name => 'Ryan').post(:register)
+ # # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
+ #
+ # # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.xml?position=Manager.
+ # Person.find(1).put(:promote, :position => 'Manager')
+ # # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
+ #
+ # # GET all the positions available, i.e. GET /people/positions.xml.
+ # Person.get(:positions)
+ # # => [{:name => 'Manager'}, {:name => 'Clerk'}]
+ #
+ # # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.xml.
+ # Person.find(1).delete(:fire)
+ #
+ # For more information on 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 URL for the +site+ variable.
+ #
+ # class Person < ActiveResource::Base
+ # self.site = "http://ryan:password@api.people.com:3000/"
+ # end
+ #
+ # * defining +user+ and/or +password+ variables
+ #
+ # class Person < ActiveResource::Base
+ # self.site = "http://api.people.com:3000/"
+ # self.user = "ryan"
+ # self.password = "password"
+ # end
+ #
+ # For obvious security reasons, it is probably best if such services are available
+ # over HTTPS.
+ #
+ # Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
+ # as usernames. In those situations you should use the seperate user and password option.
+ # == 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 <tt>404</tt> (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) # 404, raises ActiveResource::ResourceNotFound
+ #
+ # <tt>404</tt> is just one of the HTTP error response codes that Active Resource 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 <tt>422</tt> and an XML representation of the validation errors. The save operation will
+ # then fail (with a <tt>false</tt> 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 type="array"><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.
+ #
+ # === Timeouts
+ #
+ # Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
+ # unresponsive servers. In such cases, your Active Resource method calls could timeout. You can control the
+ # amount of time before Active Resource times out with the +timeout+ variable.
+ #
+ # class Person < ActiveResource::Base
+ # self.site = "http://api.people.com:3000/"
+ # self.timeout = 5
+ # end
+ #
+ # This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API
+ # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
+ # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
+ # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
+ # server.
+ #
+ # When a timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
+ # ActiveResource::TimeoutError in your Active Resource method calls.
+ #
+ # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
+ # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
+ # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
+ class Base
+ # The logger for diagnosing and tracing Active Resource calls.
+ cattr_accessor :logger
+
+ class << self
+ # Gets the URI of the REST resources to map for this class. The site variable is required
+ # Active Resource's mapping to work.
+ def site
+ # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
+ #
+ # With superclass_delegating_reader
+ #
+ # Parent.site = 'http://anonymous@test.com'
+ # Subclass.site # => 'http://anonymous@test.com'
+ # Subclass.site.user = 'david'
+ # Parent.site # => 'http://david@test.com'
+ #
+ # Without superclass_delegating_reader (expected behaviour)
+ #
+ # Parent.site = 'http://anonymous@test.com'
+ # Subclass.site # => 'http://anonymous@test.com'
+ # Subclass.site.user = 'david' # => TypeError: can't modify frozen object
+ #
+ if defined?(@site)
+ @site
+ elsif superclass != Object && superclass.site
+ superclass.site.dup.freeze
+ end
+ end
+
+ # Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
+ # The site variable is required Active Resource's mapping to work.
+ def site=(site)
+ @connection = nil
+ if site.nil?
+ @site = nil
+ else
+ @site = create_site_uri_from(site)
+ @user = URI.decode(@site.user) if @site.user
+ @password = URI.decode(@site.password) if @site.password
+ end
+ end
+
+ # Gets the user for REST HTTP authentication.
+ def user
+ # Not using superclass_delegating_reader. See +site+ for explanation
+ if defined?(@user)
+ @user
+ elsif superclass != Object && superclass.user
+ superclass.user.dup.freeze
+ end
+ end
+
+ # Sets the user for REST HTTP authentication.
+ def user=(user)
+ @connection = nil
+ @user = user
+ end
+
+ # Gets the password for REST HTTP authentication.
+ def password
+ # Not using superclass_delegating_reader. See +site+ for explanation
+ if defined?(@password)
+ @password
+ elsif superclass != Object && superclass.password
+ superclass.password.dup.freeze
+ end
+ end
+
+ # Sets the password for REST HTTP authentication.
+ def password=(password)
+ @connection = nil
+ @password = password
+ end
+
+ # Sets the format that attributes are sent and received in from a mime type reference:
+ #
+ # Person.format = :json
+ # Person.find(1) # => GET /people/1.json
+ #
+ # Person.format = ActiveResource::Formats::XmlFormat
+ # Person.find(1) # => GET /people/1.xml
+ #
+ # Default format is <tt>:xml</tt>.
+ def format=(mime_type_reference_or_format)
+ format = mime_type_reference_or_format.is_a?(Symbol) ?
+ ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
+
+ write_inheritable_attribute("format", format)
+ connection.format = format if site
+ end
+
+ # Returns the current format, default is ActiveResource::Formats::XmlFormat.
+ def format # :nodoc:
+ read_inheritable_attribute("format") || ActiveResource::Formats[:xml]
+ end
+
+ # Sets the number of seconds after which requests to the REST API should time out.
+ def timeout=(timeout)
+ @connection = nil
+ @timeout = timeout
+ end
+
+ # Gets tthe number of seconds after which requests to the REST API should time out.
+ def timeout
+ if defined?(@timeout)
+ @timeout
+ elsif superclass != Object && superclass.timeout
+ superclass.timeout
+ end
+ end
+
+ # 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 <tt>false</tt>).
+ def connection(refresh = false)
+ if defined?(@connection) || superclass == Object
+ @connection = Connection.new(site, format) if refresh || @connection.nil?
+ @connection.user = user if user
+ @connection.password = password if password
+ @connection.timeout = timeout if timeout
+ @connection
+ else
+ superclass.connection
+ end
+ end
+
+ def headers
+ @headers ||= {}
+ end
+
+ # Do not include any modules in the default element name. This makes it easier to seclude ARes objects
+ # in a separate namespace without having to set element_name repeatedly.
+ attr_accessor_with_default(:element_name) { to_s.split("::").last.underscore } #:nodoc:
+
+ attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
+ attr_accessor_with_default(:primary_key, 'id') #:nodoc:
+
+ # 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] == '/'
+ # generate the actual method based on the current site path
+ self.prefix = default
+ 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 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}]}" }
+
+ # Redefine the new methods.
+ code = <<-end_code
+ def prefix_source() "#{value}" end
+ def prefix(options={}) "#{prefix_call}" end
+ end_code
+ silence_warnings { instance_eval code, __FILE__, __LINE__ }
+ rescue
+ logger.error "Couldn't set prefix: #{$!}\n #{code}"
+ raise
+ end
+
+ alias_method :set_prefix, :prefix= #:nodoc:
+
+ alias_method :set_element_name, :element_name= #:nodoc:
+ alias_method :set_collection_name, :collection_name= #:nodoc:
+
+ # 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 URLs (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
+ #
+ 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}.#{format.extension}#{query_string(query_options)}"
+ end
+
+ # 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
+ #
+ 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}.#{format.extension}#{query_string(query_options)}"
+ end
+
+ alias_method :set_primary_key, :primary_key= #:nodoc:
+
+ # Create a new resource instance and request to the remote service
+ # 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 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.
+ #
+ # ==== 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.
+ #
+ # * <tt>:one</tt> - Returns a single resource.
+ # * <tt>:first</tt> - Returns the first resource found.
+ # * <tt>:all</tt> - Returns every resource that matches the request.
+ #
+ # ==== Options
+ #
+ # * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
+ # * <tt>:params</tt> - 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(:all, :from => :developers, :params => { :language => 'ruby' })
+ # # => GET /people/developers.xml?language=ruby
+ #
+ # 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) || {}
+
+ case scope
+ when :all then find_every(options)
+ when :first then find_every(options).first
+ when :one then find_one(options)
+ else find_single(scope, options)
+ end
+ end
+
+ # Deletes the resources with the ID in the +id+ parameter.
+ #
+ # ==== Options
+ # All options specify prefix and query parameters.
+ #
+ # ==== Examples
+ # Event.delete(2) # sends DELETE /events/2
+ #
+ # Event.create(:name => 'Free Concert', :location => 'Community Center')
+ # my_event = Event.find(:first) # let's assume this is event with ID 7
+ # Event.delete(my_event.id) # sends DELETE /events/7
+ #
+ # # Let's assume a request to events/5/cancel.xml
+ # Event.delete(params[:id]) # sends DELETE /events/5
+ def delete(id, options = {})
+ connection.delete(element_path(id, options))
+ end
+
+ # 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 = {})
+ if id
+ prefix_options, query_options = split_options(options[:params])
+ path = element_path(id, prefix_options, query_options)
+ response = connection.head(path, headers)
+ response.code == 200
+ end
+ # id && !find_single(id, options).nil?
+ rescue ActiveResource::ResourceNotFound
+ false
+ end
+
+ private
+ # Find every resource
+ def find_every(options)
+ case from = options[:from]
+ when Symbol
+ instantiate_collection(get(from, options[:params]))
+ when String
+ path = "#{from}#{query_string(options[:params])}"
+ instantiate_collection(connection.get(path, headers) || [])
+ else
+ prefix_options, query_options = split_options(options[:params])
+ path = collection_path(prefix_options, query_options)
+ instantiate_collection( (connection.get(path, headers) || []), prefix_options )
+ end
+ end
+
+ # Find a single resource from a one-off URL
+ def find_one(options)
+ case from = options[:from]
+ when Symbol
+ instantiate_record(get(from, options[:params]))
+ when String
+ path = "#{from}#{query_string(options[:params])}"
+ instantiate_record(connection.get(path, headers))
+ end
+ end
+
+ # Find a single resource from the default URL
+ def find_single(scope, options)
+ prefix_options, query_options = split_options(options[:params])
+ path = element_path(scope, prefix_options, query_options)
+ instantiate_record(connection.get(path, headers), prefix_options)
+ end
+
+ def instantiate_collection(collection, prefix_options = {})
+ collection.collect! { |record| instantiate_record(record, prefix_options) }
+ end
+
+ def instantiate_record(record, prefix_options = {})
+ returning new(record) do |resource|
+ resource.prefix_options = prefix_options
+ end
+ end
+
+
+ # Accepts a URI and creates the site URI from that.
+ def create_site_uri_from(site)
+ site.is_a?(URI) ? site.dup : URI.parse(site)
+ end
+
+ # contains a set of the current prefix parameters.
+ def prefix_parameters
+ @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
+ end
+
+ # Builds the query string for the request.
+ def query_string(options)
+ "?#{options.to_query}" unless options.nil? || options.empty?
+ end
+
+ # split an option hash into two hashes, one containing the prefix options,
+ # and the other containing the leftovers.
+ def split_options(options = {})
+ prefix_options, query_options = {}, {}
+
+ (options || {}).each do |key, value|
+ next if key.blank?
+ (prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
+ end
+
+ [ prefix_options, query_options ]
+ end
+ end
+
+ 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
+
+ # Returns a clone of the resource that hasn't been assigned an +id+ yet and
+ # is treated as a new resource.
+ #
+ # ryan = Person.find(1)
+ # not_ryan = ryan.clone
+ # not_ryan.new? # => true
+ #
+ # Any active resource member attributes will NOT be cloned, though all other
+ # attributes are. This is to prevent the conflict between any +prefix_options+
+ # that refer to the original parent resource and the newly cloned parent
+ # resource that does not exist.
+ #
+ # ryan = Person.find(1)
+ # ryan.address = StreetAddress.find(1, :person_id => ryan.id)
+ # ryan.hash = {:not => "an ARes instance"}
+ #
+ # not_ryan = ryan.clone
+ # not_ryan.new? # => true
+ # not_ryan.address # => NoMethodError
+ # not_ryan.hash # => {:not => "an ARes instance"}
+ def clone
+ # Clone all attributes except the pk and any nested ARes
+ cloned = attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.inject({}) do |attrs, (k, v)|
+ attrs[k] = v.clone
+ attrs
+ end
+ # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
+ # attempts to convert hashes into member objects and arrays into collections of objects. We want
+ # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
+ resource = self.class.new({})
+ resource.prefix_options = self.prefix_options
+ resource.send :instance_variable_set, '@attributes', cloned
+ resource
+ end
+
+
+ # 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+ attribute of the resource.
+ def id
+ attributes[self.class.primary_key]
+ end
+
+ # Set the +id+ attribute of the resource.
+ def id=(id)
+ attributes[self.class.primary_key] = id
+ end
+
+ # Allows Active Resource objects to be used as parameters in Action Pack URL generation.
+ def to_param
+ id && id.to_s
+ end
+
+ # 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 <tt>new?</tt>, 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
+
+ # Tests for equality (delegates to ==).
+ def eql?(other)
+ self == other
+ end
+
+ # Delegates to id in order to allow two resources of the same type and id to work with something like:
+ # [Person.find(1), Person.find(2)] & [Person.find(1), Person.find(4)] # => [Person.find(1)]
+ def hash
+ 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 self.class.new do |resource|
+ resource.attributes = @attributes
+ resource.prefix_options = @prefix_options
+ end
+ end
+
+ # 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 # sends POST /companies/ (create)
+ #
+ # my_company.new? # => false
+ # my_company.size = 10
+ # my_company.save # sends PUT /companies/1 (update)
+ def save
+ new? ? create : update
+ end
+
+ # 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 not <tt>new?</tt> 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?(to_param, :params => prefix_options)
+ end
+
+ # 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
+ # Active Support.
+ #
+ # * <tt>:indent</tt> - Set the indent level for the XML output (default is +2+).
+ # * <tt>:dasherize</tt> - Boolean option to determine whether or not element names should
+ # replace underscores with dashes (default is <tt>false</tt>).
+ # * <tt>:skip_instruct</tt> - Toggle skipping the +instruct!+ call on the XML builder
+ # that generates the XML declaration (default is <tt>false</tt>).
+ #
+ # ==== 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
+
+ # 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(to_param, :params => @prefix_options).attributes)
+ end
+
+ # 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)
+ attributes.each do |key, value|
+ @attributes[key.to_s] =
+ case value
+ when Array
+ resource = find_or_create_resource_for_collection(key)
+ value.map { |attrs| resource.new(attrs) }
+ when Hash
+ resource = find_or_create_resource_for(key)
+ resource.new(value)
+ else
+ value.dup rescue value
+ end
+ end
+ self
+ end
+
+ # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
+ alias_method :respond_to_without_attributes?, :respond_to?
+
+ # 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 <tt>true</tt> to <tt>my_person.respond_to?("name")</tt>, <tt>my_person.respond_to?("name=")</tt>, and
+ # <tt>my_person.respond_to?("name?")</tt>.
+ def respond_to?(method, include_priv = false)
+ method_name = method.to_s
+ if attributes.nil?
+ return super
+ elsif attributes.has_key?(method_name)
+ return true
+ elsif ['?','='].include?(method_name.last) && attributes.has_key?(method_name.first(-1))
+ return true
+ end
+ # super must be called at the end of the method, because the inherited respond_to?
+ # would return true for generated readers, even if the attribute wasn't present
+ super
+ end
+
+
+ protected
+ def connection(refresh = false)
+ self.class.connection(refresh)
+ end
+
+ # Update the resource on the remote service.
+ def update
+ returning connection.put(element_path(prefix_options), to_xml, self.class.headers) do |response|
+ load_attributes_from_response(response)
+ end
+ end
+
+ # Create (i.e., save to the remote service) the new resource.
+ def create
+ returning connection.post(collection_path, to_xml, self.class.headers) do |response|
+ self.id = id_from_response(response)
+ load_attributes_from_response(response)
+ end
+ end
+
+ def load_attributes_from_response(response)
+ if response['Content-Length'] != "0" && response.body.strip.size > 0
+ load(self.class.format.decode(response.body))
+ end
+ end
+
+ # Takes a response from a typical create post and pulls the ID out
+ def id_from_response(response)
+ response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1]
+ end
+
+ def element_path(options = nil)
+ self.class.element_path(to_param, options || prefix_options)
+ end
+
+ def collection_path(options = nil)
+ self.class.collection_path(options || prefix_options)
+ end
+
+ private
+ # Tries to find a resource for a given collection name; if it fails, then the resource is created
+ def find_or_create_resource_for_collection(name)
+ find_or_create_resource_for(name.to_s.singularize)
+ end
+
+ # Tries to find a resource in a non empty list of nested modules
+ # Raises a NameError if it was not found in any of the given nested modules
+ def find_resource_in_modules(resource_name, module_names)
+ receiver = Object
+ namespaces = module_names[0, module_names.size-1].map do |module_name|
+ receiver = receiver.const_get(module_name)
+ end
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(resource_name) }
+ return namespace.const_get(resource_name)
+ else
+ raise NameError
+ end
+ end
+
+ # Tries to find a resource for a given name; if it fails, then the resource is created
+ def find_or_create_resource_for(name)
+ resource_name = name.to_s.camelize
+ ancestors = self.class.name.split("::")
+ if ancestors.size > 1
+ find_resource_in_modules(resource_name, ancestors)
+ else
+ self.class.const_get(resource_name)
+ end
+ rescue NameError
+ resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
+ resource.prefix = self.class.prefix
+ resource.site = self.class.site
+ resource
+ end
+
+ def split_options(options = {})
+ self.class.send!(:split_options, options)
+ end
+
+ def method_missing(method_symbol, *arguments) #:nodoc:
+ method_name = method_symbol.to_s
+
+ case method_name.last
+ when "="
+ attributes[method_name.first(-1)] = arguments.first
+ when "?"
+ attributes[method_name.first(-1)]
+ else
+ attributes.has_key?(method_name) ? attributes[method_name] : super
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/connection.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/connection.rb
new file mode 100644
index 000000000..0c4ea432d
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/connection.rb
@@ -0,0 +1,207 @@
+require 'net/https'
+require 'date'
+require 'time'
+require 'uri'
+require 'benchmark'
+
+module ActiveResource
+ class ConnectionError < StandardError # :nodoc:
+ attr_reader :response
+
+ def initialize(response, message = nil)
+ @response = response
+ @message = message
+ end
+
+ def to_s
+ "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
+ end
+ end
+
+ # Raised when a Timeout::Error occurs.
+ class TimeoutError < ConnectionError
+ def initialize(message)
+ @message = message
+ end
+ def to_s; @message ;end
+ end
+
+ # 3xx Redirection
+ class Redirection < ConnectionError # :nodoc:
+ def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
+ end
+
+ # 4xx Client Error
+ class ClientError < ConnectionError; end # :nodoc:
+
+ # 400 Bad Request
+ class BadRequest < ClientError; end # :nodoc
+
+ # 401 Unauthorized
+ class UnauthorizedAccess < ClientError; end # :nodoc
+
+ # 403 Forbidden
+ class ForbiddenAccess < ClientError; end # :nodoc
+
+ # 404 Not Found
+ class ResourceNotFound < ClientError; end # :nodoc:
+
+ # 409 Conflict
+ class ResourceConflict < ClientError; end # :nodoc:
+
+ # 5xx Server Error
+ class ServerError < ConnectionError; end # :nodoc:
+
+ # 405 Method Not Allowed
+ 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 web services.
+ # This class is used by ActiveResource::Base to interface with REST
+ # services.
+ class Connection
+ attr_reader :site, :user, :password, :timeout
+ attr_accessor :format
+
+ class << self
+ def requests
+ @@requests ||= []
+ end
+ end
+
+ # The +site+ parameter is required and will set the +site+
+ # attribute to the URI for the remote resource service.
+ def initialize(site, format = ActiveResource::Formats[:xml])
+ raise ArgumentError, 'Missing site URI' unless site
+ @user = @password = nil
+ self.site = site
+ self.format = format
+ end
+
+ # Set URI for remote service.
+ def site=(site)
+ @site = site.is_a?(URI) ? site : URI.parse(site)
+ @user = URI.decode(@site.user) if @site.user
+ @password = URI.decode(@site.password) if @site.password
+ end
+
+ # Set user for remote service.
+ def user=(user)
+ @user = user
+ end
+
+ # Set password for remote service.
+ def password=(password)
+ @password = password
+ end
+
+ # Set the number of seconds after which HTTP requests to the remote service should time out.
+ def timeout=(timeout)
+ @timeout = timeout
+ end
+
+ # Execute a GET request.
+ # Used to get (find) resources.
+ def get(path, headers = {})
+ format.decode(request(:get, path, build_request_headers(headers)).body)
+ end
+
+ # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
+ # Used to delete resources.
+ def delete(path, headers = {})
+ request(:delete, path, build_request_headers(headers))
+ end
+
+ # Execute a PUT request (see HTTP protocol documentation if unfamiliar).
+ # Used to update resources.
+ def put(path, body = '', headers = {})
+ request(:put, path, body.to_s, build_request_headers(headers))
+ end
+
+ # Execute a POST request.
+ # Used to create new resources.
+ def post(path, body = '', headers = {})
+ request(:post, path, body.to_s, build_request_headers(headers))
+ end
+
+ # Execute a HEAD request.
+ # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
+ def head(path, headers = {})
+ request(:head, path, build_request_headers(headers))
+ end
+
+
+ private
+ # Makes request to remote service.
+ def request(method, path, *arguments)
+ logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
+ result = nil
+ time = Benchmark.realtime { result = http.send(method, path, *arguments) }
+ logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body.length : 0}b %.2fs)" % time if logger
+ handle_response(result)
+ rescue Timeout::Error => e
+ raise TimeoutError.new(e.message)
+ end
+
+ # Handles response and error codes from remote service.
+ def handle_response(response)
+ case response.code.to_i
+ when 301,302
+ raise(Redirection.new(response))
+ when 200...400
+ response
+ when 400
+ raise(BadRequest.new(response))
+ when 401
+ raise(UnauthorizedAccess.new(response))
+ when 403
+ raise(ForbiddenAccess.new(response))
+ when 404
+ raise(ResourceNotFound.new(response))
+ when 405
+ raise(MethodNotAllowed.new(response))
+ when 409
+ raise(ResourceConflict.new(response))
+ when 422
+ raise(ResourceInvalid.new(response))
+ when 401...500
+ raise(ClientError.new(response))
+ when 500...600
+ raise(ServerError.new(response))
+ else
+ raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
+ end
+ end
+
+ # Creates new Net::HTTP instance for communication with
+ # remote service and resources.
+ def http
+ http = Net::HTTP.new(@site.host, @site.port)
+ http.use_ssl = @site.is_a?(URI::HTTPS)
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
+ http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
+ http
+ end
+
+ def default_header
+ @default_header ||= { 'Content-Type' => format.mime_type }
+ end
+
+ # Builds headers for request to remote service.
+ def build_request_headers(headers)
+ authorization_header.update(default_header).update(headers)
+ end
+
+ # Sets authorization header
+ def authorization_header
+ (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
+ end
+
+ def logger #:nodoc:
+ ActiveResource::Base.logger
+ end
+ end
+end
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/custom_methods.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/custom_methods.rb
new file mode 100644
index 000000000..4c8699288
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/custom_methods.rb
@@ -0,0 +1,119 @@
+module ActiveResource
+ # 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:
+ #
+ # map.resources :people, :new => { :register => :post },
+ # :member => { :promote => :put, :deactivate => :delete }
+ # :collection => { :active => :get }
+ #
+ # This route set 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
+ #
+ # 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) # POST /people/new/register.xml
+ # # => { :id => 1, :name => 'Ryan' }
+ #
+ # 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'}]
+ #
+ module CustomMethods
+ def self.included(base)
+ base.class_eval do
+ extend ActiveResource::CustomMethods::ClassMethods
+ include ActiveResource::CustomMethods::InstanceMethods
+
+ class << self
+ alias :orig_delete :delete
+
+ # Invokes a GET to a given custom REST method. For example:
+ #
+ # Person.get(:active) # GET /people/active.xml
+ # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
+ #
+ # Person.get(:active, :awesome => true) # GET /people/active.xml?awesome=true
+ # # => [{:id => 1, :name => 'Ryan'}]
+ #
+ # Note: the objects returned from this method are not automatically converted
+ # into Active Resource instances - they are ordinary Hashes. If you are expecting
+ # Active Resource instances, use the <tt>find</tt> class method with the
+ # <tt>:from</tt> option. For example:
+ #
+ # Person.find(:all, :from => :active)
+ def get(custom_method_name, options = {})
+ connection.get(custom_method_collection_url(custom_method_name, options), headers)
+ end
+
+ def post(custom_method_name, options = {}, body = '')
+ connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
+ end
+
+ def put(custom_method_name, options = {}, body = '')
+ connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
+ end
+
+ def delete(custom_method_name, options = {})
+ # Need to jump through some hoops to retain the original class 'delete' method
+ if custom_method_name.is_a?(Symbol)
+ connection.delete(custom_method_collection_url(custom_method_name, options), headers)
+ else
+ orig_delete(custom_method_name, options)
+ end
+ end
+ end
+ end
+ end
+
+ module ClassMethods
+ def custom_method_collection_url(method_name, options = {})
+ prefix_options, query_options = split_options(options)
+ "#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}"
+ end
+ end
+
+ module InstanceMethods
+ def get(method_name, options = {})
+ connection.get(custom_method_element_url(method_name, options), self.class.headers)
+ end
+
+ def post(method_name, options = {}, body = '')
+ if new?
+ connection.post(custom_method_new_element_url(method_name, options), (body.nil? ? to_xml : body), self.class.headers)
+ else
+ connection.post(custom_method_element_url(method_name, options), body, self.class.headers)
+ end
+ end
+
+ def put(method_name, options = {}, body = '')
+ connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
+ end
+
+ def delete(method_name, options = {})
+ connection.delete(custom_method_element_url(method_name, options), self.class.headers)
+ end
+
+
+ private
+ def custom_method_element_url(method_name, options = {})
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.send!(:query_string, options)}"
+ end
+
+ def custom_method_new_element_url(method_name, options = {})
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.send!(:query_string, options)}"
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/formats.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/formats.rb
new file mode 100644
index 000000000..28864cf58
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/formats.rb
@@ -0,0 +1,14 @@
+module ActiveResource
+ module Formats
+ # Lookup the format class from a mime type reference symbol. Example:
+ #
+ # ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
+ # ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
+ def self.[](mime_type_reference)
+ ActiveResource::Formats.const_get(mime_type_reference.to_s.camelize + "Format")
+ end
+ end
+end
+
+require 'active_resource/formats/xml_format'
+require 'active_resource/formats/json_format' \ No newline at end of file
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/formats/json_format.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/formats/json_format.rb
new file mode 100644
index 000000000..df0d6ca37
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/formats/json_format.rb
@@ -0,0 +1,23 @@
+module ActiveResource
+ module Formats
+ module JsonFormat
+ extend self
+
+ def extension
+ "json"
+ end
+
+ def mime_type
+ "application/json"
+ end
+
+ def encode(hash)
+ hash.to_json
+ end
+
+ def decode(json)
+ ActiveSupport::JSON.decode(json)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/formats/xml_format.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/formats/xml_format.rb
new file mode 100644
index 000000000..5e97ffa77
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/formats/xml_format.rb
@@ -0,0 +1,34 @@
+module ActiveResource
+ module Formats
+ module XmlFormat
+ extend self
+
+ def extension
+ "xml"
+ end
+
+ def mime_type
+ "application/xml"
+ end
+
+ def encode(hash)
+ hash.to_xml
+ end
+
+ def decode(xml)
+ from_xml_data(Hash.from_xml(xml))
+ end
+
+ private
+ # Manipulate from_xml Hash, because xml_simple is not exactly what we
+ # want for Active Resource.
+ def from_xml_data(data)
+ if data.is_a?(Hash) && data.keys.size == 1
+ data.values.first
+ else
+ data
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/http_mock.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/http_mock.rb
new file mode 100644
index 000000000..22f83ae91
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/http_mock.rb
@@ -0,0 +1,217 @@
+require 'active_resource/connection'
+
+module ActiveResource
+ class InvalidRequestError < StandardError; end #:nodoc:
+
+ # One thing that has always been a pain with remote web services is testing. The HttpMock
+ # class makes it easy to test your Active Resource models by creating a set of mock responses to specific
+ # requests.
+ #
+ # To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
+ # method with an attached block. The block declares a set of URIs with expected input, and the output
+ # each request should return. The passed in block has any number of entries in the following generalized
+ # format:
+ #
+ # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
+ #
+ # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
+ # +head+.
+ # * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
+ # called.
+ # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
+ # hash format, such as <tt>{ "Content-Type" => "application/xml" }</tt>. This mock will only trigger
+ # if your tests sends a request with identical headers.
+ # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
+ # such as XML.
+ # * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
+ # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
+ # <tt>request_headers</tt> listed above.
+ #
+ # In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
+ # +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
+ # will be raised letting you know you need to create a new mock for that request.
+ #
+ # ==== Example
+ # def setup
+ # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
+ # ActiveResource::HttpMock.respond_to do |mock|
+ # mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
+ # mock.get "/people/1.xml", {}, @matz
+ # mock.put "/people/1.xml", {}, nil, 204
+ # mock.delete "/people/1.xml", {}, nil, 200
+ # end
+ # end
+ #
+ # def test_get_matz
+ # person = Person.find(1)
+ # assert_equal "Matz", person.name
+ # end
+ #
+ class HttpMock
+ class Responder #:nodoc:
+ def initialize(responses)
+ @responses = responses
+ end
+
+ for method in [ :post, :put, :get, :delete, :head ]
+ module_eval <<-EOE, __FILE__, __LINE__
+ def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
+ @responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
+ end
+ EOE
+ end
+ end
+
+ class << self
+
+ # Returns an array of all request objects that have been sent to the mock. You can use this to check
+ # wether or not your model actually sent an HTTP request.
+ #
+ # ==== Example
+ # def setup
+ # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
+ # ActiveResource::HttpMock.respond_to do |mock|
+ # mock.get "/people/1.xml", {}, @matz
+ # end
+ # end
+ #
+ # def test_should_request_remote_service
+ # person = Person.find(1) # Call the remote service
+ #
+ # # This request object has the same HTTP method and path as declared by the mock
+ # expected_request = ActiveResource::Request.new(:get, "/people/1.xml")
+ #
+ # # Assert that the mock received, and responded to, the expected request from the model
+ # assert ActiveResource::HttpMock.requests.include?(expected_request)
+ # end
+ def requests
+ @@requests ||= []
+ end
+
+ # Returns a hash of <tt>request => response</tt> pairs for all all responses this mock has delivered, where +request+
+ # is an instance of ActiveResource::Request and the response is, naturally, an instance of
+ # ActiveResource::Response.
+ def responses
+ @@responses ||= {}
+ end
+
+ # Accepts a block which declares a set of requests and responses for the HttpMock to respond to. See the main
+ # ActiveResource::HttpMock description for a more detailed explanation.
+ def respond_to(pairs = {}) #:yields: mock
+ reset!
+ pairs.each do |(path, response)|
+ responses[path] = response
+ end
+
+ if block_given?
+ yield Responder.new(responses)
+ else
+ Responder.new(responses)
+ end
+ end
+
+ # Deletes all logged requests and responses.
+ def reset!
+ requests.clear
+ responses.clear
+ end
+ end
+
+ for method in [ :post, :put ]
+ module_eval <<-EOE, __FILE__, __LINE__
+ def #{method}(path, body, headers)
+ request = ActiveResource::Request.new(:#{method}, path, body, headers)
+ self.class.requests << request
+ self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for \#{request}"))
+ end
+ EOE
+ end
+
+ for method in [ :get, :delete, :head ]
+ module_eval <<-EOE, __FILE__, __LINE__
+ def #{method}(path, headers)
+ request = ActiveResource::Request.new(:#{method}, path, nil, headers)
+ self.class.requests << request
+ self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for \#{request}"))
+ end
+ EOE
+ end
+
+ def initialize(site) #:nodoc:
+ @site = site
+ end
+ end
+
+ class Request
+ attr_accessor :path, :method, :body, :headers
+
+ def initialize(method, path, body = nil, headers = {})
+ @method, @path, @body, @headers = method, path, body, headers.reverse_merge('Content-Type' => 'application/xml')
+ end
+
+ def ==(other_request)
+ other_request.hash == hash
+ end
+
+ def eql?(other_request)
+ self == other_request
+ end
+
+ def to_s
+ "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
+ end
+
+ def hash
+ "#{path}#{method}#{headers}".hash
+ end
+ end
+
+ class Response
+ attr_accessor :body, :message, :code, :headers
+
+ def initialize(body, message = 200, headers = {})
+ @body, @message, @headers = body, message.to_s, headers
+ @code = @message[0,3].to_i
+
+ resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
+ if resp_cls && !resp_cls.body_permitted?
+ @body = nil
+ end
+
+ if @body.nil?
+ self['Content-Length'] = "0"
+ else
+ self['Content-Length'] = body.size.to_s
+ end
+ end
+
+ def success?
+ (200..299).include?(code)
+ end
+
+ def [](key)
+ headers[key]
+ end
+
+ def []=(key, value)
+ headers[key] = value
+ end
+
+ def ==(other)
+ if (other.is_a?(Response))
+ other.body == body && other.message == message && other.headers == headers
+ else
+ false
+ end
+ end
+ end
+
+ class Connection
+ private
+ silence_warnings do
+ def http
+ @http ||= HttpMock.new(@site)
+ end
+ end
+ end
+end
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/validations.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/validations.rb
new file mode 100644
index 000000000..a7c624f30
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/validations.rb
@@ -0,0 +1,288 @@
+module ActiveResource
+ class ResourceInvalid < ClientError #:nodoc:
+ end
+
+ # Active Resource validation is reported to and from this object, which is used by Base#save
+ # to determine whether the object in a valid state to be saved. See usage example in Validations.
+ class Errors
+ include Enumerable
+ attr_reader :errors
+
+ delegate :empty?, :to => :errors
+
+ def initialize(base) # :nodoc:
+ @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
+
+ # 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?
+ errors.size == 1 ? errors.first : errors
+ end
+
+ alias :[] :on
+
+ # 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 = []
+
+ @errors.each_key do |attr|
+ @errors[attr].each do |msg|
+ next if msg.nil?
+
+ if attr == "base"
+ full_messages << msg
+ else
+ full_messages << [attr.humanize, msg].join(' ')
+ end
+ end
+ end
+ full_messages
+ end
+
+ def clear
+ @errors = {}
+ end
+
+ # 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
+
+ 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) }
+ messages = Hash.from_xml(xml)['errors']['error'] rescue []
+ messages.each do |message|
+ attr_message = humanized_attributes.keys.detect do |attr_name|
+ if message[0, attr_name.size + 1] == "#{attr_name} "
+ add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
+ end
+ end
+
+ add_to_base message if attr_message.nil?
+ end
+ end
+ end
+
+ # Module to allow validation of Active Resource 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/"
+ # protected
+ # def validate
+ # errors.add_on_empty %w( first_name last_name )
+ # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
+ # end
+ #
+ # def validate_on_create # is only run the first time a new object is saved
+ # unless valid_member?(self)
+ # errors.add("membership_discount", "has expired")
+ # end
+ # end
+ #
+ # def validate_on_update
+ # errors.add_to_base("No changes have occurred") if unchanged_attributes?
+ # end
+ # end
+ #
+ # person = Person.new("first_name" => "Jim", "phone_number" => "I will not tell you.")
+ # person.save # => false (and doesn't do the save)
+ # person.errors.empty? # => false
+ # person.errors.count # => 2
+ # person.errors.on "last_name" # => "can't be empty"
+ # person.attributes = { "last_name" => "Halpert", "phone_number" => "555-5555" }
+ # person.save # => true (and person is now saved to the remote service)
+ #
+ module Validations
+ def self.included(base) # :nodoc:
+ base.class_eval do
+ alias_method_chain :save, :validation
+ end
+ end
+
+ # Validate a resource and save (POST) it to the remote web service.
+ def save_with_validation
+ save_without_validation
+ true
+ rescue ResourceInvalid => error
+ errors.from_xml(error.response.body)
+ 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
+
+ # Returns the Errors object that holds all information about attribute error messages.
+ def errors
+ @errors ||= Errors.new(self)
+ end
+ end
+end
diff --git a/vendor/rails-2.1.0/activeresource/lib/active_resource/version.rb b/vendor/rails-2.1.0/activeresource/lib/active_resource/version.rb
new file mode 100644
index 000000000..88798ea1c
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/active_resource/version.rb
@@ -0,0 +1,9 @@
+module ActiveResource
+ module VERSION #:nodoc:
+ MAJOR = 2
+ MINOR = 1
+ TINY = 0
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+end
diff --git a/vendor/rails-2.1.0/activeresource/lib/activeresource.rb b/vendor/rails-2.1.0/activeresource/lib/activeresource.rb
new file mode 100644
index 000000000..e076455b1
--- /dev/null
+++ b/vendor/rails-2.1.0/activeresource/lib/activeresource.rb
@@ -0,0 +1 @@
+require 'active_resource'