diff options
author | francis <francis> | 2008-01-23 01:48:14 +0000 |
---|---|---|
committer | francis <francis> | 2008-01-23 01:48:14 +0000 |
commit | 60eaae4f7df1f1dae91defb87d3707451c359cf4 (patch) | |
tree | e74835c37779a2f094e810960cda07b99a75330e /vendor/rails-2.0.2/activeresource/lib | |
parent | 71d22c740302e1f83bbbd89b229734ea9c67493c (diff) |
Freeze in rails 2.0.2 (Am I going to regret having this beast in CVS?)
Diffstat (limited to 'vendor/rails-2.0.2/activeresource/lib')
11 files changed, 1712 insertions, 0 deletions
diff --git a/vendor/rails-2.0.2/activeresource/lib/active_resource.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource.rb new file mode 100644 index 000000000..96f2257a0 --- /dev/null +++ b/vendor/rails-2.0.2/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.0.2/activeresource/lib/active_resource/base.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/base.rb new file mode 100644 index 000000000..47c4d0c8f --- /dev/null +++ b/vendor/rails-2.0.2/activeresource/lib/active_resource/base.rb @@ -0,0 +1,872 @@ +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/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 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. + # + 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 + # ActiveResource's mapping to work. + def site + 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 ActiveResource's mapping to work. + def site=(site) + @connection = nil + @site = site.nil? ? nil : create_site_uri_from(site) + end + + # Sets the format that attributes are sent and received in from a mime type reference. Example: + # + # 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 :xml. + 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 + end + + # Returns the current format, default is ActiveResource::Formats::XmlFormat + def format # :nodoc: + read_inheritable_attribute("format") || ActiveResource::Formats[:xml] + 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 +false+). + def connection(refresh = false) + if defined?(@connection) || superclass == Object + @connection = Connection.new(site, format) if refresh || @connection.nil? + @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 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 + # + 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. + # + # +: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) || {} + + 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) + # # => 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 + + # 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 + 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 + + # 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 ActiveResource objects to be used as parameters in ActionPack 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 +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 + + # 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 + # # => POST /companies/ (create) + # + # my_company.new? + # # => false + # my_company.size = 10 + # my_company.save + # # => 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 +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 + + # 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 + + # 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 + + # 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 respond_to? 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 +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? + 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(id, 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 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 + + # FIXME: Make it generic enough to support any depth of module nesting + if (ancestors = self.class.name.split("::")).size > 1 + begin + ancestors.first.constantize.const_get(resource_name) + rescue NameError + self.class.const_get(resource_name) + end + 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.0.2/activeresource/lib/active_resource/connection.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/connection.rb new file mode 100644 index 000000000..2bf83b161 --- /dev/null +++ b/vendor/rails-2.0.2/activeresource/lib/active_resource/connection.rb @@ -0,0 +1,172 @@ +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 + + # 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 + 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 + self.site = site + self.format = format + end + + # Set URI for remote service. + def site=(site) + @site = site.is_a?(URI) ? site : URI.parse(site) + 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 + + + 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 : 0}b %.2fs)" % time if logger + handle_response(result) + 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 + 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; authentication information is pulled from credentials provided with site URI. + def authorization_header + (@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {}) + end + + def logger #:nodoc: + ActiveResource::Base.logger + end + end +end diff --git a/vendor/rails-2.0.2/activeresource/lib/active_resource/custom_methods.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/custom_methods.rb new file mode 100644 index 000000000..e08c66430 --- /dev/null +++ b/vendor/rails-2.0.2/activeresource/lib/active_resource/custom_methods.rb @@ -0,0 +1,105 @@ +# 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 }, +# :element => { :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 ActiveResource + module CustomMethods + def self.included(within) + within.class_eval do + extend ActiveResource::CustomMethods::ClassMethods + include ActiveResource::CustomMethods::InstanceMethods + + class << self + alias :orig_delete :delete + + def get(method_name, options = {}) + connection.get(custom_method_collection_url(method_name, options), headers) + end + + def post(method_name, options = {}, body = '') + connection.post(custom_method_collection_url(method_name, options), body, headers) + end + + def put(method_name, options = {}, body = '') + connection.put(custom_method_collection_url(method_name, options), body, headers) + end + + # Need to jump through some hoops to retain the original class 'delete' method + def delete(custom_method_name, options = {}) + 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}.xml#{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}.xml#{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}.xml#{self.class.send!(:query_string, options)}" + end + end + end +end diff --git a/vendor/rails-2.0.2/activeresource/lib/active_resource/formats.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/formats.rb new file mode 100644 index 000000000..28864cf58 --- /dev/null +++ b/vendor/rails-2.0.2/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.0.2/activeresource/lib/active_resource/formats/json_format.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/formats/json_format.rb new file mode 100644 index 000000000..df0d6ca37 --- /dev/null +++ b/vendor/rails-2.0.2/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.0.2/activeresource/lib/active_resource/formats/xml_format.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/formats/xml_format.rb new file mode 100644 index 000000000..01c28dcee --- /dev/null +++ b/vendor/rails-2.0.2/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 ActiveResource. + 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.0.2/activeresource/lib/active_resource/http_mock.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/http_mock.rb new file mode 100644 index 000000000..b54bf09c2 --- /dev/null +++ b/vendor/rails-2.0.2/activeresource/lib/active_resource/http_mock.rb @@ -0,0 +1,147 @@ +require 'active_resource/connection' + +module ActiveResource + class InvalidRequestError < StandardError; end #:nodoc: + + class HttpMock + class Responder + def initialize(responses) + @responses = responses + end + + for method in [ :post, :put, :get, :delete ] + module_eval <<-EOE + 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 + def requests + @@requests ||= [] + end + + def responses + @@responses ||= {} + end + + def respond_to(pairs = {}) + reset! + pairs.each do |(path, response)| + responses[path] = response + end + + if block_given? + yield Responder.new(responses) + else + Responder.new(responses) + end + end + + def reset! + requests.clear + responses.clear + end + end + + for method in [ :post, :put ] + module_eval <<-EOE + 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.inspect}")) + end + EOE + end + + for method in [ :get, :delete ] + module_eval <<-EOE + 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.inspect}")) + end + EOE + end + + def initialize(site) + @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.dup + @headers.update('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.0.2/activeresource/lib/active_resource/validations.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/validations.rb new file mode 100644 index 000000000..57d2ae559 --- /dev/null +++ b/vendor/rails-2.0.2/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 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/" + # 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.0.2/activeresource/lib/active_resource/version.rb b/vendor/rails-2.0.2/activeresource/lib/active_resource/version.rb new file mode 100644 index 000000000..4f7de5e1a --- /dev/null +++ b/vendor/rails-2.0.2/activeresource/lib/active_resource/version.rb @@ -0,0 +1,9 @@ +module ActiveResource + module VERSION #:nodoc: + MAJOR = 2 + MINOR = 0 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/rails-2.0.2/activeresource/lib/activeresource.rb b/vendor/rails-2.0.2/activeresource/lib/activeresource.rb new file mode 100644 index 000000000..e076455b1 --- /dev/null +++ b/vendor/rails-2.0.2/activeresource/lib/activeresource.rb @@ -0,0 +1 @@ +require 'active_resource' |