Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 7098

Show
Ignore:
Timestamp:
06/23/07 17:29:54 (2 years ago)
Author:
david
Message:

Big documentation upgrade for ARes (closes #8694) [jeremymcanally]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activeresource/lib/active_resource/base.rb

    r7082 r7098  
    44 
    55module ActiveResource 
     6  # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application. 
     7  # 
     8  # For an outline of what Active Resource is capable of, see link:files/README.html. 
     9  # 
     10  # == Automated mapping 
     11  # 
     12  # Active Resource objects represent your RESTful resources as manipulatable Ruby objects.  To map resources 
     13  # to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class 
     14  # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the 
     15  # URI of the resources. 
     16  #  
     17  #             class Person < ActiveResource::Base 
     18  #               self.site = "http://api.people.com:3000/" 
     19  #             end 
     20  #  
     21  # Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and 
     22  # you can now use Active Resource's lifecycles methods to manipulate resources.   
     23  #  
     24  # == Lifecycle methods 
     25  # 
     26  # Active Resource exposes methods for creating, finding, updating, and deleting resources 
     27  # from REST web services. 
     28  #  
     29  #     ryan = Person.new(:first => 'Ryan', :last => 'Daigle') 
     30  #     ryan.save  #=> true 
     31  #     ryan.id  #=> 2 
     32  #     Person.exists?(ryan.id)  #=> true 
     33  #     ryan.exists?  #=> true 
     34  #  
     35  #     ryan = Person.find(1) 
     36  #     # => Resource holding our newly create Person object 
     37  #  
     38  #     ryan.first = 'Rizzle' 
     39  #     ryan.save  #=> true 
     40  #  
     41  #     ryan.destroy  #=> true 
     42  # 
     43  # As you can see, these are very similar to Active Record's lifecycle methods for database records. 
     44  # You can read more about each of these methods in their respective documentation. 
     45  #  
     46  # === Custom REST methods 
     47  # 
     48  # Since simple CRUD/lifecycle methods can't accomplish every task, Active Resource also supports 
     49  # defining your own custom REST methods. 
     50  #  
     51  #   Person.new(:name => 'Ryan).post(:register)                                                 
     52  #   # => { :id => 1, :name => 'Ryan', :position => 'Clerk' } 
     53  # 
     54  #   Person.find(1).put(:promote, :position => 'Manager')               
     55  #   # => { :id => 1, :name => 'Ryan', :position => 'Manager' } 
     56  #  
     57  # For more information on creating and using custom REST methods, see the  
     58  # ActiveResource::CustomMethods documentation. 
     59  # 
     60  # == Validations 
     61  # 
     62  # You can validate resources client side by overriding validation methods in the base class. 
     63  #  
     64  #             class Person < ActiveResource::Base 
     65  #                self.site = "http://api.people.com:3000/" 
     66  #                protected 
     67  #                  def validate 
     68  #                    errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/ 
     69  #                  end 
     70  #             end 
     71  #  
     72  # See the ActiveResource::Validations documentation for more information. 
     73  # 
     74  # == Authentication 
     75  #  
     76  # Many REST APIs will require authentication, usually in the form of basic 
     77  # HTTP authentication.  Authentication can be specified by putting the credentials 
     78  # in the +site+ variable of the Active Resource class you need to authenticate. 
     79  #  
     80  #   class Person < ActiveResource::Base 
     81  #     self.site = "http://ryan:password@api.people.com:3000/" 
     82  #   end 
     83  #  
     84  # For obvious security reasons, it is probably best if such services are available  
     85  # over HTTPS. 
     86  #  
     87  # == Errors & Validation 
     88  # 
     89  # Error handling and validation is handled in much the same manner as you're used to seeing in 
     90  # Active Record.  Both the response code in the Http response and the body of the response are used to 
     91  # indicate that an error occurred. 
     92  #  
     93  # === Resource errors 
     94  #  
     95  # When a get is requested for a resource that does not exist, the HTTP +404+ (Resource Not Found) 
     96  # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound 
     97  # exception. 
     98  #  
     99  #   # GET http://api.people.com:3000/people/999.xml 
     100  #   ryan = Person.find(999) # => Raises ActiveResource::ResourceNotFound 
     101  #   # => Response = 404 
     102  #  
     103  # +404+ is just one of the HTTP error response codes that ActiveResource will handle with its own exception. The  
     104  # following HTTP response codes will also result in these exceptions: 
     105  #  
     106  # 200 - 399:: Valid response, no exception 
     107  # 404:: ActiveResource::ResourceNotFound 
     108  # 409:: ActiveResource::ResourceConflict 
     109  # 422:: ActiveResource::ResourceInvalid (rescued by save as validation errors) 
     110  # 401 - 499:: ActiveResource::ClientError 
     111  # 500 - 599:: ActiveResource::ServerError 
     112  # 
     113  # These custom exceptions allow you to deal with resource errors more naturally and with more precision 
     114  # rather than returning a general HTTP error.  For example: 
     115  # 
     116  #   begin 
     117  #     ryan = Person.find(my_id) 
     118  #   rescue ActiveResource::ResourceNotFound 
     119  #     redirect_to :action => 'not_found' 
     120  #   rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid 
     121  #     redirect_to :action => 'new' 
     122  #   end 
     123  # 
     124  # === Validation errors 
     125  #  
     126  # Active Resource supports validations on resources and will return errors if any these validations fail 
     127  # (e.g., "First name can not be blank" and so on).  These types of errors are denoted in the response by  
     128  # a response code of +422+ and an XML representation of the validation errors.  The save operation will  
     129  # then fail (with a +false+ return value) and the validation errors can be accessed on the resource in question. 
     130  #  
     131  #   ryan = Person.find(1) 
     132  #   ryan.first #=> '' 
     133  #   ryan.save  #=> false 
     134  # 
     135  #   # When  
     136  #   # PUT http://api.people.com:3000/people/1.xml 
     137  #   # is requested with invalid values, the response is: 
     138  #   # 
     139  #   # Response (422): 
     140  #   # <errors><error>First cannot be empty</error></errors> 
     141  #   # 
     142  # 
     143  #   ryan.errors.invalid?(:first)  #=> true 
     144  #   ryan.errors.full_messages  #=> ['First cannot be empty'] 
     145  #  
     146  # Learn more about Active Resource's validation features in the ActiveResource::Validations documentation. 
     147  # 
    6148  class Base 
    7     # The logger for diagnosing and tracing ARes calls. 
     149    # The logger for diagnosing and tracing Active Resource calls. 
    8150    cattr_accessor :logger 
    9151 
    10152    class << self 
    11       # Gets the URI of the resource's site 
     153      # Gets the URI of the REST resources to map for this class.  The site variable is required  
     154      # ActiveResource's mapping to work. 
    12155      def site 
    13156        if defined?(@site) 
     
    18161      end 
    19162 
    20       # Set the URI for the REST resources 
     163      # Sets the URI of the REST resources to map for this class to the value in the +site+ argument. 
     164      # The site variable is required ActiveResource's mapping to work. 
    21165      def site=(site) 
    22166        @connection = nil 
     
    24168      end 
    25169 
    26       # Base connection to remote service 
     170      # An instance of ActiveResource::Connection that is the base connection to the remote service. 
     171      # The +refresh+ parameter toggles whether or not the connection is refreshed at every request 
     172      # or not (defaults to +false+). 
    27173      def connection(refresh = false) 
    28174        @connection = Connection.new(site) if refresh || @connection.nil? 
     
    41187      attr_accessor_with_default(:primary_key, 'id') #:nodoc: 
    42188       
    43       # Gets the resource prefix 
    44       # prefix/collectionname/1.xml 
     189      # Gets the prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>) 
     190      # This method is regenerated at runtime based on what the prefix is set to. 
    45191      def prefix(options={}) 
    46192        default = site.path 
     
    51197      end 
    52198 
     199      # An attribute reader for the source string for the resource path prefix.  This 
     200      # method is regenerated at runtime based on what the prefix is set to. 
    53201      def prefix_source 
    54202        prefix # generate #prefix and #prefix_source methods first 
     
    56204      end 
    57205 
    58       # Sets the resource prefix 
    59       # prefix/collectionname/1.xml 
     206      # Sets the prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>). 
     207      # Default value is <tt>site.path</tt>. 
    60208      def prefix=(value = '/') 
    61209        # Replace :placeholders with '#{embedded options[:lookups]}' 
     
    78226      alias_method :set_collection_name, :collection_name=  #:nodoc: 
    79227 
    80       # Gets the element path for the given ID.  If no query_options are given, they are split from the prefix options: 
    81       # 
    82       # Post.element_path(1) # => /posts/1.xml 
    83       # Comment.element_path(1, :post_id => 5) # => /posts/5/comments/1.xml 
    84       # Comment.element_path(1, :post_id => 5, :active => 1) # => /posts/5/comments/1.xml?active=1 
    85       # Comment.element_path(1, {:post_id => 5}, {:active => 1}) # => /posts/5/comments/1.xml?active=1 
     228      # Gets the element path for the given ID in +id+.  If the +query_options+ parameter is omitted, Rails 
     229      # will split from the prefix options. 
     230      # 
     231      # ==== Options 
     232      # +prefix_options+:: A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt> 
     233      #                    would yield a URL like <tt>/accounts/19/purchases.xml</tt>). 
     234      # +query_options+:: A hash to add items to the query string for the request. 
     235      # 
     236      # ==== Examples 
     237      #   Post.element_path(1)  
     238      #   # => /posts/1.xml 
     239      # 
     240      #   Comment.element_path(1, :post_id => 5)  
     241      #   # => /posts/5/comments/1.xml 
     242      # 
     243      #   Comment.element_path(1, :post_id => 5, :active => 1)  
     244      #   # => /posts/5/comments/1.xml?active=1 
     245      # 
     246      #   Comment.element_path(1, {:post_id => 5}, {:active => 1})  
     247      #   # => /posts/5/comments/1.xml?active=1 
     248      # 
    86249      def element_path(id, prefix_options = {}, query_options = nil) 
    87250        prefix_options, query_options = split_options(prefix_options) if query_options.nil? 
     
    89252      end 
    90253 
    91       # Gets the collection path.  If no query_options are given, they are split from the prefix options: 
    92       # 
    93       # Post.collection_path # => /posts.xml 
    94       # Comment.collection_path(:post_id => 5) # => /posts/5/comments.xml 
    95       # Comment.collection_path(:post_id => 5, :active => 1) # => /posts/5/comments.xml?active=1 
    96       # Comment.collection_path({:post_id => 5}, {:active => 1}) # => /posts/5/comments.xml?active=1 
     254      # Gets the collection path for the REST resources.  If the +query_options+ parameter is omitted, Rails 
     255      # will split from the +prefix_options+. 
     256      # 
     257      # ==== Options 
     258      # +prefix_options+:: A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt> 
     259      #                    would yield a URL like <tt>/accounts/19/purchases.xml</tt>). 
     260      # +query_options+:: A hash to add items to the query string for the request. 
     261      # 
     262      # ==== Examples 
     263      #   Post.collection_path 
     264      #   # => /posts.xml 
     265      # 
     266      #   Comment.collection_path(:post_id => 5)  
     267      #   # => /posts/5/comments.xml 
     268      # 
     269      #   Comment.collection_path(:post_id => 5, :active => 1)  
     270      #   # => /posts/5/comments.xml?active=1 
     271      # 
     272      #   Comment.collection_path({:post_id => 5}, {:active => 1})  
     273      #   # => /posts/5/comments.xml?active=1 
     274      # 
    97275      def collection_path(prefix_options = {}, query_options = nil) 
    98276        prefix_options, query_options = split_options(prefix_options) if query_options.nil? 
     
    103281 
    104282      # Create a new resource instance and request to the remote service 
    105       # that it be saved.  This is equivalent to the following simultaneous calls: 
     283      # that it be saved, making it equivalent to the following simultaneous calls: 
    106284      # 
    107285      #   ryan = Person.new(:first => 'ryan') 
     
    110288      # The newly created resource is returned.  If a failure has occurred an 
    111289      # exception will be raised (see save).  If the resource is invalid and 
    112       # has not been saved then <tt>resource.valid?</tt> will return <tt>false</tt>, 
    113       # while <tt>resource.new?</tt> will still return <tt>true</tt>. 
    114       #       
     290      # has not been saved then valid? will return <tt>false</tt>, 
     291      # while new? will still return <tt>true</tt>. 
     292      # 
     293      # ==== Examples 
     294      #   Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true) 
     295      #   my_person = Person.find(:first) 
     296      #   my_person.email 
     297      #   # => myname@nospam.com 
     298      # 
     299      #   dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true) 
     300      #   dhh.valid? 
     301      #   # => true 
     302      #   dhh.new? 
     303      #   # => false 
     304      # 
     305      #   # We'll assume that there's a validation that requires the name attribute 
     306      #   that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true) 
     307      #   that_guy.valid? 
     308      #   # => false 
     309      #   that_guy.new? 
     310      #   # => true 
     311      # 
    115312      def create(attributes = {}) 
    116313        returning(self.new(attributes)) { |res| res.save }         
     
    119316      # Core method for finding resources.  Used similarly to Active Record's find method. 
    120317      # 
    121       #   Person.find(1)                                         # => GET /people/1.xml 
    122       #   Person.find(:all)                                      # => GET /people.xml 
    123       #   Person.find(:all, :params => { :title => "CEO" })      # => GET /people.xml?title=CEO 
    124       #   Person.find(:all, :from => :managers)                  # => GET /people/managers.xml 
    125       #   Person.find(:all, :from => "/companies/1/people.xml")  # => GET /companies/1/people.xml 
    126       #   Person.find(:one, :from => :leader)                    # => GET /people/leader.xml 
    127       #   Person.find(:one, :from => "/companies/1/manager.xml") # => GET /companies/1/manager.xml 
    128       #   StreetAddress.find(1, :params => { :person_id => 1 })  # => GET /people/1/street_addresses/1.xml 
     318      # ==== Arguments 
     319      # The first argument is considered to be the scope of the query.  That is, how many  
     320      # resources are returned from the request.  It can be one of the following. 
     321      # 
     322      # +:one+:: Returns a single resource. 
     323      # +:first+:: Returns the first resource found. 
     324      # +:all+:: Returns every resource that matches the request. 
     325      #  
     326      # ==== Options 
     327      # +from+:: Sets the path or custom method that resources will be fetched from. 
     328      # +params+:: Sets query and prefix (nested URL) parameters. 
     329      # 
     330      # ==== Examples 
     331      #   Person.find(1)                                          
     332      #   # => GET /people/1.xml 
     333      # 
     334      #   Person.find(:all)                                       
     335      #   # => GET /people.xml 
     336      # 
     337      #   Person.find(:all, :params => { :title => "CEO" })       
     338      #   # => GET /people.xml?title=CEO 
     339      # 
     340      #   Person.find(:first, :from => :managers)                   
     341      #   # => GET /people/managers.xml 
     342      # 
     343      #   Person.find(:all, :from => "/companies/1/people.xml")   
     344      #   # => GET /companies/1/people.xml 
     345      # 
     346      #   Person.find(:one, :from => :leader)                     
     347      #   # => GET /people/leader.xml 
     348      # 
     349      #   Person.find(:one, :from => "/companies/1/manager.xml")  
     350      #   # => GET /companies/1/manager.xml 
     351      # 
     352      #   StreetAddress.find(1, :params => { :person_id => 1 })   
     353      #   # => GET /people/1/street_addresses/1.xml 
    129354      def find(*arguments) 
    130355        scope   = arguments.slice!(0) 
     
    139364      end 
    140365 
     366      # Deletes the resources with the ID in the +id+ parameter. 
     367      # 
     368      # ==== Options 
     369      # All options specify prefix and query parameters. 
     370      # 
     371      # ==== Examples 
     372      #   Event.delete(2) 
     373      #   # => DELETE /events/2 
     374      # 
     375      #   Event.create(:name => 'Free Concert', :location => 'Community Center') 
     376      #   my_event = Event.find(:first) 
     377      #   # => Events (id: 7) 
     378      #   Event.delete(my_event.id) 
     379      #   # => DELETE /events/7 
     380      # 
     381      #   # Let's assume a request to events/5/cancel.xml 
     382      #   Event.delete(params[:id]) 
     383      #   # => DELETE /events/5 
     384      # 
    141385      def delete(id, options = {}) 
    142386        connection.delete(element_path(id, options)) 
    143387      end 
    144388 
    145       # Evalutes to <tt>true</tt> if the resource is found. 
     389      # Asserts the existence of a resource, returning <tt>true</tt> if the resource is found. 
     390      # 
     391      # ==== Examples 
     392      #   Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...') 
     393      #   Note.exists?(1) 
     394      #   # => true 
     395      # 
     396      #   Note.exists(1349) 
     397      #   # => false 
    146398      def exists?(id, options = {}) 
    147399        id && !find_single(id, options).nil? 
     
    227479    attr_accessor :prefix_options #:nodoc: 
    228480 
     481    # Constructor method for new resources; the optional +attributes+ parameter takes a +Hash+ 
     482    # of attributes for the new resource. 
     483    # 
     484    # ==== Examples 
     485    #   my_course = Course.new 
     486    #   my_course.name = "Western Civilization" 
     487    #   my_course.lecturer = "Don Trotter" 
     488    #   my_course.save 
     489    # 
     490    #   my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling") 
     491    #   my_other_course.save 
    229492    def initialize(attributes = {}) 
    230493      @attributes     = {} 
     
    233496    end 
    234497 
    235     # Is the resource a new object? 
     498    # A method to determine if the resource a new object (i.e., it has not been POSTed to the remote service yet). 
     499    # 
     500    # ==== Examples 
     501    #   not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall') 
     502    #   not_new.new? 
     503    #   # => false 
     504    # 
     505    #   is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM') 
     506    #   is_new.new? 
     507    #   # => true 
     508    # 
     509    #   is_new.save 
     510    #   is_new.new? 
     511    #   # => false 
     512    # 
    236513    def new? 
    237514      id.nil? 
    238515    end 
    239516 
    240     # Get the id of the object
     517    # Get the +id+ attribute of the resource
    241518    def id 
    242519      attributes[self.class.primary_key] 
    243520    end 
    244521 
    245     # Set the id of the object
     522    # Set the +id+ attribute of the resource
    246523    def id=(id) 
    247524      attributes[self.class.primary_key] = id 
    248525    end 
    249526 
    250     # True if and only if +other+ is the same object or is an instance of the same class, is not +new?+, and has the same +id+. 
     527    # Test for equality.  Resource are equal if and only if +other+ is the same object or  
     528    # is an instance of the same class, is not +new?+, and has the same +id+. 
     529    # 
     530    # ==== Examples 
     531    #   ryan = Person.create(:name => 'Ryan') 
     532    #   jamie = Person.create(:name => 'Jamie') 
     533    # 
     534    #   ryan == jamie 
     535    #   # => false (Different name attribute and id) 
     536    # 
     537    #   ryan_again = Person.new(:name => 'Ryan') 
     538    #   ryan == ryan_again 
     539    #   # => false (ryan_again is new?) 
     540    # 
     541    #   ryans_clone = Person.create(:name => 'Ryan') 
     542    #   ryan == ryans_clone 
     543    #   # => false (Different id attributes) 
     544    # 
     545    #   ryans_twin = Person.find(ryan.id) 
     546    #   ryan == ryans_twin 
     547    #   # => true 
     548    # 
    251549    def ==(other) 
    252550      other.equal?(self) || (other.instance_of?(self.class) && !other.new? && other.id == id) 
    253551    end 
    254552 
    255     # Delegates to == 
     553    # Tests for equality (delegates to ==). 
    256554    def eql?(other) 
    257555      self == other 
     
    264562    end 
    265563     
     564    # Duplicate the current resource without saving it. 
     565    # 
     566    # ==== Examples 
     567    #   my_invoice = Invoice.create(:customer => 'That Company') 
     568    #   next_invoice = my_invoice.dup 
     569    #   next_invoice.new? 
     570    #   # => true 
     571    # 
     572    #   next_invoice.save 
     573    #   next_invoice == my_invoice 
     574    #   # => false (different id attributes) 
     575    # 
     576    #   my_invoice.customer 
     577    #   # => That Company 
     578    #   next_invoice.customer 
     579    #   # => That Company 
    266580    def dup 
    267581      returning new do |resource| 
     
    271585    end 
    272586 
    273     # Delegates to +create+ if a new object, +update+ if its old. If the response to the save includes a body, 
    274     # it will be assumed that this body is XML for the final object as it looked after the save (which would include 
    275     # attributes like created_at that wasn't part of the original submit). 
     587    # A method to save (+POST+) or update (+PUT+) a resource.  It delegates to +create+ if a new object,  
     588    # +update+ if it is existing. If the response to the save includes a body, it will be assumed that this body 
     589    # is XML for the final object as it looked after the save (which would include attributes like +created_at+ 
     590    # that weren't part of the original submit). 
     591    # 
     592    # ==== Examples 
     593    #   my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2) 
     594    #   my_company.new? 
     595    #   # => true 
     596    #   my_company.save 
     597    #   # => POST /companies/ (create) 
     598    # 
     599    #   my_company.new? 
     600    #   # => false 
     601    #   my_company.size = 10 
     602    #   my_company.save 
     603    #   # => PUT /companies/1 (update) 
    276604    def save 
    277605      new? ? create : update 
    278606    end 
    279607 
    280     # Delete the resource. 
     608    # Deletes the resource from the remote service. 
     609    # 
     610    # ==== Examples 
     611    #   my_id = 3 
     612    #   my_person = Person.find(my_id) 
     613    #   my_person.destroy 
     614    #   Person.find(my_id) 
     615    #   # => 404 (Resource Not Found) 
     616    #    
     617    #   new_person = Person.create(:name => 'James') 
     618    #   new_id = new_person.id  
     619    #   # => 7 
     620    #   new_person.destroy 
     621    #   Person.find(new_id) 
     622    #   # => 404 (Resource Not Found) 
    281623    def destroy 
    282624      connection.delete(element_path, self.class.headers) 
    283625    end 
    284626 
    285     # Evaluates to <tt>true</tt> if this resource is found. 
     627    # Evaluates to <tt>true</tt> if this resource is not +new?+ and is 
     628    # found on the remote service.  Using this method, you can check for 
     629    # resources that may have been deleted between the object's instantiation 
     630    # and actions on it. 
     631    # 
     632    # ==== Examples 
     633    #   Person.create(:name => 'Theodore Roosevelt') 
     634    #   that_guy = Person.find(:first) 
     635    #   that_guy.exists? 
     636    #   # => true 
     637    # 
     638    #   that_lady = Person.new(:name => 'Paul Bean') 
     639    #   that_lady.exists? 
     640    #   # => false 
     641    # 
     642    #   guys_id = that_guy.id 
     643    #   Person.delete(guys_id) 
     644    #   that_guy.exists? 
     645    #   # => false 
    286646    def exists? 
    287647      !new? && self.class.exists?(id, :params => prefix_options) 
    288648    end 
    289649 
    290     # Convert the resource to an XML string 
     650    # A method to convert the the resource to an XML string. 
     651    # 
     652    # ==== Options 
     653    # The +options+ parameter is handed off to the +to_xml+ method on each 
     654    # attribute, so it has the same options as the +to_xml+ methods in 
     655    # ActiveSupport. 
     656    # 
     657    # indent:: Set the indent level for the XML output (default is +2+). 
     658    # dasherize:: Boolean option to determine whether or not element names should 
     659    #             replace underscores with dashes (default is +false+). 
     660    # skip_instruct::  Toggle skipping the +instruct!+ call on the XML builder  
     661    #                  that generates the XML declaration (default is +false+). 
     662    # 
     663    # ==== Examples 
     664    #   my_group = SubsidiaryGroup.find(:first) 
     665    #   my_group.to_xml 
     666    #   # => <?xml version="1.0" encoding="UTF-8"?> 
     667    #   #    <subsidiary_group> [...] </subsidiary_group> 
     668    # 
     669    #   my_group.to_xml(:dasherize => true) 
     670    #   # => <?xml version="1.0" encoding="UTF-8"?> 
     671    #   #    <subsidiary-group> [...] </subsidiary-group> 
     672    # 
     673    #   my_group.to_xml(:skip_instruct => true) 
     674    #   # => <subsidiary_group> [...] </subsidiary_group> 
    291675    def to_xml(options={}) 
    292676      attributes.to_xml({:root => self.class.element_name}.merge(options)) 
    293677    end 
    294678 
    295     # Reloads the attributes of this object from the remote web service. 
     679    # A method to reload the attributes of this object from the remote web service. 
     680    # 
     681    # ==== Examples 
     682    #   my_branch = Branch.find(:first) 
     683    #   my_branch.name 
     684    #   # => Wislon Raod 
     685    #    
     686    #   # Another client fixes the typo... 
     687    # 
     688    #   my_branch.name 
     689    #   # => Wislon Raod 
     690    #   my_branch.reload 
     691    #   my_branch.name 
     692    #   # => Wilson Road 
    296693    def reload 
    297694      self.load(self.class.find(id, :params => @prefix_options).attributes) 
    298695    end 
    299696 
    300     # Manually load attributes from a hash. Recursively loads collections of 
    301     # resources. 
     697    # A method to manually load attributes from a hash. Recursively loads collections of 
     698    # resources.  This method is called in initialize and create when a +Hash+ of attributes 
     699    # is provided. 
     700    # 
     701    # ==== Examples 
     702    #   my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'} 
     703    # 
     704    #   the_supplier = Supplier.find(:first) 
     705    #   the_supplier.name 
     706    #   # => 'J&M Textiles' 
     707    #   the_supplier.load(my_attrs) 
     708    #   the_supplier.name('J&J Textiles') 
     709    # 
     710    #   # These two calls are the same as Supplier.new(my_attrs) 
     711    #   my_supplier = Supplier.new 
     712    #   my_supplier.load(my_attrs) 
     713    # 
     714    #   # These three calls are the same as Supplier.create(my_attrs) 
     715    #   your_supplier = Supplier.new 
     716    #   your_supplier.load(my_attrs) 
     717    #   your_supplier.save 
    302718    def load(attributes) 
    303719      raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash) 
     
    322738    alias_method :respond_to_without_attributes?, :respond_to? 
    323739 
    324     # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and 
    325     # person.respond_to?("name?") which will all return true. 
     740    # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a +Person+ object with a 
     741    # +name+ attribute can answer +true+ to +my_person.respond_to?("name")+, +my_person.respond_to?("name=")+, and 
     742    # +my_person.respond_to?("name?")+. 
    326743    def respond_to?(method, include_priv = false) 
    327744      method_name = method.to_s 
  • trunk/activeresource/lib/active_resource/connection.rb

    r7074 r7098  
    66 
    77module ActiveResource 
    8   class ConnectionError < StandardError 
     8  class ConnectionError < StandardError # :nodoc: 
    99    attr_reader :response 
    1010 
     
    1919  end 
    2020 
    21   class ClientError < ConnectionError;  end  # 4xx Client Error 
    22   class ResourceNotFound < ClientError; end  # 404 Not Found 
    23   class ResourceConflict < ClientError; end  # 409 Conflict 
     21  # 4xx Client Error 
     22  class ClientError < ConnectionError; end # :nodoc: 
     23   
     24  # 404 Not Found 
     25  class ResourceNotFound < ClientError; end # :nodoc: 
     26   
     27  # 409 Conflict 
     28  class ResourceConflict < ClientError; end # :nodoc: 
    2429 
    25   class ServerError < ConnectionError;  end  # 5xx Server Error 
     30  # 5xx Server Error 
     31  class ServerError < ConnectionError; end # :nodoc: 
    2632 
    2733  # 405 Method Not Allowed 
    28   class MethodNotAllowed < ClientError 
     34  class MethodNotAllowed < ClientError # :nodoc: 
    2935    def allowed_methods 
    3036      @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym } 
     
    3238  end 
    3339 
    34   # Class to handle connections to remote services. 
     40  # Class to handle connections to remote web services. 
     41  # This class is used by ActiveResource::Base to interface with REST 
     42  # services. 
    3543  class Connection 
    3644    attr_reader :site 
     
    4755    end 
    4856 
     57    # The +site+ parameter is required and will set the +site+ 
     58    # attribute to the URI for the remote resource service. 
    4959    def initialize(site) 
    5060      raise ArgumentError, 'Missing site URI' unless site 
     
    8494      from_xml_data(Hash.from_xml(response.body)) 
    8595    end 
    86  
    8796 
    8897    private 
     
    153162        end 
    154163      end 
    155        
    156164  end 
    157165end 
  • trunk/activeresource/lib/active_resource/custom_methods.rb

    r7064 r7098  
    1 # Support custom methods and sub-resources for REST. 
     1# A module to support custom REST methods and sub-resources, allowing you to break out 
     2# of the "default" REST methods with your own custom resource requests.  For example,  
     3# say you use Rails to expose a REST service and configure your routes with: 
    24# 
    3 # Say you on the server configure your routes with: 
     5#    map.resources :people, :new => { :register => :post }, 
     6#                           :element => { :promote => :put, :deactivate => :delete } 
     7#                           :collection => { :active => :get } 
    48# 
    5 #   map.resources :people, :new => { :register => :post }, 
    6 #                          :element => { :promote => :put, :deactivate => :delete } 
    7 #                          :collection => { :active => :get } 
     9#  This route set creates routes for the following http requests: 
    810# 
    9 #  Which creates routes for the following http requests: 
     11#    POST    /people/new/register.xml #=> PeopleController.register 
     12#    PUT     /people/1/promote.xml    #=> PeopleController.promote with :id => 1 
     13#    DELETE  /people/1/deactivate.xml #=> PeopleController.deactivate with :id => 1 
     14#    GET     /people/active.xml       #=> PeopleController.active 
    1015# 
    11 #  POST    /people/new/register.xml #=> PeopleController.register 
    12 #  PUT     /people/1/promote.xml    #=> PeopleController.promote with :id => 1 
    13 #  DELETE  /people/1/deactivate.xml #=> PeopleController.deactivate with :id => 1 
    14 #  GET     /people/active.xml       #=> PeopleController.active 
    15 
    16 # This module provides the ability for Active Resource to call these 
    17 # custom REST methods and get the response back. 
     16# Using this module, Active Resource can use these custom REST methods just like the 
     17# standard methods. 
    1818# 
    1919#   class Person < ActiveResource::Base 
     
    2121#   end 
    2222# 
    23 #   Person.new(:name => 'Ryan).post(:register) #=> { :id => 1, :name => 'Ryan' } 
     23#   Person.new(:name => 'Ryan).post(:register)  # POST /people/new/register.xml 
     24#   # => { :id => 1, :name => 'Ryan' } 
    2425# 
    25 #   Person.find(1).put(:promote, :position => 'Manager') 
    26 #   Person.find(1).delete(:deactivate) 
     26#   Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml 
     27#   Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml 
    2728# 
    28 #   Person.get(:active) #=> [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}] 
     29#   Person.get(:active)  # GET /people/active.xml  
     30#   # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}] 
     31
    2932module ActiveResource 
    3033  module CustomMethods  
  • trunk/activeresource/lib/active_resource/validations.rb

    r5962 r7098  
    1515    end 
    1616 
     17    # Add an error to the base Active Resource object rather than an attribute. 
     18    # 
     19    # ==== Examples 
     20    #   my_folder = Folder.find(1) 
     21    #   my_folder.errors.add_to_base("You can't edit an existing folder") 
     22    #   my_folder.errors.on_base 
     23    #   # => "You can't edit an existing folder" 
     24    # 
     25    #   my_folder.errors.add_to_base("This folder has been tagged as frozen") 
     26    #   my_folder.valid? 
     27    #   # => false 
     28    #   my_folder.errors.on_base 
     29    #   # => ["You can't edit an existing folder", "This folder has been tagged as frozen"] 
     30    # 
    1731    def add_to_base(msg) 
    1832      add(:base, msg) 
    1933    end 
    2034 
     35    # Adds an error to an Active Resource object's attribute (named for the +attribute+ parameter) 
     36    # with the error message in +msg+. 
     37    # 
     38    # ==== Examples 
     39    #   my_resource = Node.find(1) 
     40    #   my_resource.errors.add('name', 'can not be "base"') if my_resource.name == 'base' 
     41    #   my_resource.errors.on('name') 
     42    #   # => 'can not be "base"!' 
     43    # 
     44    #   my_resource.errors.add('desc', 'can not be blank') if my_resource.desc == '' 
     45    #   my_resource.valid? 
     46    #   # => false 
     47    #   my_resource.errors.on('desc') 
     48    #   # => 'can not be blank!' 
     49    # 
    2150    def add(attribute, msg) 
    2251      @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil? 
     
    2554 
    2655    # Returns true if the specified +attribute+ has errors associated with it. 
     56    # 
     57    # ==== Examples 
     58    #   my_resource = Disk.find(1) 
     59    #   my_resource.errors.add('location', 'must be Main') unless my_resource.location == 'Main' 
     60    #   my_resource.errors.on('location') 
     61    #   # => 'must be Main!' 
     62    # 
     63    #   my_resource.errors.invalid?('location') 
     64    #   # => true 
     65    #   my_resource.errors.invalid?('name') 
     66    #   # => false 
    2767    def invalid?(attribute) 
    2868      !@errors[attribute.to_s].nil? 
    2969    end 
    3070 
    31     # * Returns nil, if no errors are associated with the specified +attribute+. 
    32     # * Returns the error message, if one error is associated with the specified +attribute+. 
    33     # * Returns an array of error messages, if more than one error is associated with the specified +attribute+. 
     71    # A method to return the errors associated with +attribute+, which returns nil, if no errors are  
     72    # associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+, 
     73    # or an array of error messages if more than one error is associated with the specified +attribute+. 
     74    # 
     75    # ==== Examples 
     76    #   my_person = Person.new(params[:person]) 
     77    #   my_person.errors.on('login') 
     78    #   # => nil 
     79    # 
     80    #   my_person.errors.add('login', 'can not be empty') if my_person.login == '' 
     81    #   my_person.errors.on('login') 
     82    #   # => 'can not be empty' 
     83    # 
     84    #   my_person.errors.add('login', 'can not be longer than 10 characters') if my_person.login.length > 10 
     85    #   my_person.errors.on('login') 
     86    #   # => ['can not be empty', 'can not be longer than 10 characters'] 
    3487    def on(attribute) 
    3588      errors = @errors[attribute.to_s] 
     
    4093    alias :[] :on 
    4194 
    42     # Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute). 
     95    # A method to return errors assigned to +base+ object through add_to_base, which returns nil, if no errors are  
     96    # associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+, 
     97    # or an array of error messages if more than one error is associated with the specified +attribute+. 
     98    # 
     99    # ==== Examples 
     100    #   my_account = Account.find(1) 
     101    #   my_account.errors.on_base 
     102    #   # => nil 
     103    # 
     104    #   my_account.errors.add_to_base("This account is frozen") 
     105    #   my_account.errors.on_base 
     106    #   # => "This account is frozen" 
     107    # 
     108    #   my_account.errors.add_to_base("This account has been closed") 
     109    #   my_account.errors.on_base 
     110    #   # => ["This account is frozen", "This account has been closed"] 
     111    # 
    43112    def on_base 
    44113      on(:base) 
     
    46115 
    47116    # Yields each attribute and associated message per error added. 
     117    # 
     118    # ==== Examples 
     119    #   my_person = Person.new(params[:person]) 
     120    # 
     121    #   my_person.errors.add('login', 'can not be empty') if my_person.login == '' 
     122    #   my_person.errors.add('password', 'can not be empty') if my_person.password == '' 
     123    #   messages = '' 
     124    #   my_person.errors.each {|attr, msg| messages += attr.humanize + " " + msg + "<br />"} 
     125    #   messages 
     126    #   # => "Login can not be empty<br />Password can not be empty<br />" 
     127    # 
    48128    def each 
    49129      @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } } 
     
    52132    # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned 
    53133    # through iteration as "First name can't be empty". 
     134    # 
     135    # ==== Examples 
     136    #   my_person = Person.new(params[:person]) 
     137    # 
     138    #   my_person.errors.add('login', 'can not be empty') if my_person.login == '' 
     139    #   my_person.errors.add('password', 'can not be empty') if my_person.password == '' 
     140    #   messages = '' 
     141    #   my_person.errors.each_full {|msg| messages += msg + "<br/>"} 
     142    #   messages 
     143    #   # => "Login can not be empty<br />Password can not be empty<br />" 
     144    # 
    54145    def each_full 
    55146      full_messages.each { |msg| yield msg } 
     
    57148 
    58149    # Returns all the full error messages in an array. 
     150    # 
     151    # ==== Examples 
     152    #   my_person = Person.new(params[:person]) 
     153    # 
     154    #   my_person.errors.add('login', 'can not be empty') if my_person.login == '' 
     155    #   my_person.errors.add('password', 'can not be empty') if my_person.password == '' 
     156    #   messages = '' 
     157    #   my_person.errors.full_messages.each {|msg| messages += msg + "<br/>"} 
     158    #   messages 
     159    #   # => "Login can not be empty<br />Password can not be empty<br />" 
     160    # 
    59161    def full_messages 
    60162      full_messages = [] 
     
    80182    # Returns the total number of errors added. Two errors added to the same attribute will be counted as such 
    81183    # with this as well. 
     184    # 
     185    # ==== Examples 
     186    #   my_person = Person.new(params[:person]) 
     187    #   my_person.errors.size 
     188    #   # => 0 
     189    # 
     190    #   my_person.errors.add('login', 'can not be empty') if my_person.login == '' 
     191    #   my_person.errors.add('password', 'can not be empty') if my_person.password == '' 
     192    #   my_person.error.size 
     193    #   # => 2 
     194    # 
    82195    def size 
    83196      @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size } 
     
    87200    alias_method :length, :size 
    88201     
     202    # Grabs errors from the XML response. 
    89203    def from_xml(xml) 
    90204      clear 
     
    103217  end 
    104218   
    105   # Module to allow validation of ActiveResource objects, which are implemented by overriding +Base#validate+ or its variants.  
    106   # Each of these methods can inspect the state of the object, which usually means ensuring that a number of  
    107   # attributes have a certain value (such as not empty, within a given range, matching a certain regular expression). For example: 
     219  # Module to allow validation of ActiveResource objects, which creates an Errors instance for every resource. 
     220  # Methods are implemented by overriding +Base#validate+ or its variants   Each of these methods can inspect  
     221  # the state of the object, which usually means  ensuring that a number of attributes have a certain value  
     222  # (such as not empty, within a given range, matching a certain regular expression and so on). 
     223  # 
     224  # ==== Example 
    108225  # 
    109226  #   class Person < ActiveResource::Base 
     
    134251  #   person.save                         # => true (and person is now saved to the remote service) 
    135252  # 
    136   # An Errors object is automatically created for every resource. 
    137253  module Validations 
    138254    def self.included(base) # :nodoc: 
     
    142258    end 
    143259 
     260    # Validate a resource and save (POST) it to the remote web service. 
    144261    def save_with_validation 
    145262      save_without_validation 
     
    150267    end 
    151268 
     269    # Checks for errors on an object (i.e., is resource.errors empty?). 
     270    #  
     271    # ==== Examples 
     272    #   my_person = Person.create(params[:person]) 
     273    #   my_person.valid? 
     274    #   # => true 
     275    # 
     276    #   my_person.errors.add('login', 'can not be empty') if my_person.login == '' 
     277    #   my_person.valid? 
     278    #   # => false 
    152279    def valid? 
    153280      errors.empty? 
  • trunk/activeresource/README

    r6025 r7098  
    1 = Active Resource -- Object-oriented REST services 
     1= Active Resource 
    22 
    3 Active Resource (ARes) connects business objects and REST web services.  It is a library 
    4 intended to provide transparent proxying capabilities between a client and a RESTful 
    5 service (for which Rails provides the {Simply RESTful routing}[http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/resources.rb] implementation). 
     3Active Resource (ARes) connects business objects and Representational State Transfer (REST) 
     4web services.  It implements object-relational mapping for REST webservices to provide transparent  
     5proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing 
     6in ActionController::Resources). 
    67 
    7 === Configuration & Usage 
     8== Philosophy 
    89 
    9 Configuration is as simple as inheriting from ActiveResource::Base and providing a site 
    10 class variable: 
     10Active Resource attempts to provide a coherent wrapper object-relational mapping for REST 
     11web services. It follows the same philosophy as Active Record, in that one of its prime aims 
     12is to reduce the amount of code needed to map to these resources.  This is made possible  
     13by relying on a number of code- and protocol-based conventions that make it easy for Active Resource 
     14to infer complex relations and structures.  These conventions are outlined in detail in the documentation 
     15for ActiveResource::Base. 
     16 
     17== Overview 
     18 
     19Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database 
     20tables.  When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result 
     21received and serialized into a usable Ruby object. 
     22 
     23=== Configuration and Usage 
     24 
     25Putting ActiveResource to use is very similar to ActiveRecord.  It's as simple as creating a model class 
     26that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it: 
    1127 
    1228   class Person < ActiveResource::Base 
     
    1430   end 
    1531 
    16 Person is now REST enable and can invoke REST services very similarly to how ActiveRecord invokes 
     32Now the Person class is REST enabled and can invoke REST services very similarly to how ActiveRecord invokes 
    1733lifecycle methods that operate against a persistent store. 
    1834 
    1935   # Find a person with id = 1 
    20    # This will invoke the following Http call: 
    21    # GET http://api.people.com:3000/people/1.xml 
    22    # and will load up the XML response into a new 
    23    # Person object 
    24    # 
    2536   ryan = Person.find(1) 
    2637   Person.exists?(1)  #=> true 
    2738 
    28    # To create a new person - instantiate the object and call 'save', 
    29    # which will invoke this Http call: 
    30    # POST http://api.people.com:3000/people.xml 
    31    # (and will submit the XML format of the person object in the request) 
    32    # 
    33    ryan = Person.new(:first => 'Ryan', :last => 'Daigle') 
    34    ryan.save  #=> true 
    35    ryan.id  #=> 2 
    36    Person.exists?(ryan.id)  #=> true 
    37    ryan.exists?  #=> true 
     39As you can see, the methods are quite similar to Active Record's methods for dealing with database 
     40records.  But rather than dealing with  
    3841 
    39    # Resource creation can also use the convenience <tt>create</tt> method which 
    40    # will request a resource save after instantiation. 
    41    ryan = Person.create(:first => 'Ryan', :last => 'Daigle') 
    42    ryan.exists?  #=> true 
     42==== Protocol 
    4343 
    44    # Updating is done with 'save' as well 
    45    # PUT http://api.people.com:3000/people/1.xml 
    46    # 
    47    ryan = Person.find(1) 
    48    ryan.first = 'Rizzle' 
    49    ryan.save  #=> true 
     44Active Resource is built on a standard XML format for requesting and submitting resources over HTTP.  It mirrors the RESTful routing  
     45built into ActionController but will also work with any other REST service that properly implements the protocol.  
     46REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification: 
    5047 
    51    # And destruction 
    52    # DELETE http://api.people.com:3000/people/1.xml 
    53    # 
    54    ryan = Person.find(1) 
    55    ryan.destroy  #=> true  # Or Person.delete(ryan.id) 
     48* GET requests are used for finding and retrieving resources. 
     49* POST requests are used to create new resources. 
     50* PUT requests are used to update existing resources. 
     51* DELETE requests are used to delete resources.  
    5652 
    57  
    58 === Protocol 
    59  
    60 ARes is built on a standard XML format for requesting and submitting resources.  It mirrors the 
    61 RESTful routing built into ActionController, though it's useful to discuss what ARes expects 
    62 outside the context of ActionController as it is not dependent on a Rails-based RESTful implementation. 
     53For more information on how this protocol works with Active Resource, see the ActiveResource::Base documentation; 
     54for more general information on REST web services, see the article here[http://en.wikipedia.org/wiki/Representational_State_Transfer]. 
    6355 
    6456==== Find 
     
    170162 
    171163 
    172 === Errors & Validation 
     164You can find more usage information in the ActiveResource::Base documentation. 
    173165 
    174 Error handling and validation is handled in much the same manner as you're used to seeing in 
    175 ActiveRecord.  Both the response code in the Http response and the body of the response are used to 
    176 indicate that an error occurred. 
    177  
    178 ==== Resource errors 
    179  
    180 When a get is requested for a resource that does not exist, the Http '404' (resource not found) 
    181 response code will be returned from the server which will raise an ActiveResource::ResourceNotFound 
    182 exception. 
    183  
    184   # GET http://api.people.com:3000/people/1.xml 
    185   # #=> Response (404) 
    186   # 
    187   ryan = Person.find(1) #=> Raises ActiveResource::ResourceNotFound 
    188  
    189 ==== Validation errors 
    190  
    191 Creating and updating resources can lead to validation errors - i.e. 'First name cannot be empty' etc... 
    192 These types of errors are denoted in the response by a response code of 422 and the xml representation 
    193 of the validation errors.  The save operation will then fail (with a 'false' return value) and the 
    194 validation errors can be accessed on the resource in question. 
    195  
    196   # When  
    197   # 
    198   # PUT http://api.people.com:3000/people/1.xml 
    199   # 
    200   # is requested with invalid values, the expected response is: 
    201   # 
    202   # Response (422): 
    203   # <errors><error>First cannot be empty</error></errors> 
    204   # 
    205   ryan = Person.find(1) 
    206   ryan.first #=> '' 
    207   ryan.save  #=> false 
    208   ryan.errors.invalid?(:first)  #=> true 
    209   ryan.errors.full_messages  #=> ['First cannot be empty'] 
    210  
    211  
    212 ==== Response errors 
    213  
    214 If the underlying Http request for an ARes operation results in an error response code, an 
    215 exception will be raised.  The following Http response codes will result in these exceptions: 
    216  
    217    200 - 399: Valid response, no exception 
    218    404: ActiveResource::ResourceNotFound 
    219    409: ActiveResource::ResourceConflict 
    220    422: ActiveResource::ResourceInvalid (rescued by save as validation errors) 
    221    401 - 499: ActiveResource::ClientError 
    222    500 - 599: ActiveResource::ServerError 
    223  
    224  
    225 === Authentication 
    226  
    227 Many REST apis will require username/password authentication, usually in the form of 
    228 Http authentication.  This can easily be specified by putting the username and password 
    229 in the Url of the ARes site: 
    230  
    231    class Person < ActiveResource::Base 
    232      self.site = "http://ryan:password@api.people.com:3000/" 
    233    end 
    234  
    235 For obvious reasons it is best if such services are available over https.