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

Changeset 4890

Show
Ignore:
Timestamp:
09/01/06 01:15:10 (2 years ago)
Author:
rick
Message:

Major updates to ActiveResource, please see changelog and unit tests [Rick Olson]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activeresource/CHANGELOG

    r4887 r4890  
    11*SVN* 
     2 
     3* Major updates [Rick Olson] 
     4 
     5  * Add full support for find/create/update/destroy 
     6  * Add support for specifying prefixes. 
     7  * Allow overriding of element_name, collection_name, and primary key 
     8  * Provide simpler HTTP mock interface for testing 
     9   
     10    # rails routing code 
     11    map.resources :posts do |post| 
     12      post.resources :comments 
     13    end 
     14     
     15    # ActiveResources 
     16    class Post < ActiveResource::Base 
     17      self.site = "http://37s.sunrise.i:3000/" 
     18    end 
     19 
     20    class Comment < ActiveResource::Base 
     21      self.site = "http://37s.sunrise.i:3000/posts/:post_id/" 
     22    end 
     23     
     24    @post     = Post.find 5 
     25    @comments = Comment.find :all, :post_id => @post.id 
     26 
     27    @comment  = Comment.new({:body => 'hello world'}, {:post_id => @post.id}) 
     28    @comment.save 
    229 
    330* Base.site= accepts URIs. 200...400 are valid response codes. PUT and POST request bodies default to ''. [Jeremy Kemper] 
  • trunk/activeresource/lib/active_resource/base.rb

    r4886 r4890  
    44  class Base 
    55    class << self 
     6      attr_reader :site 
     7 
    68      def site=(site) 
    7         @@site = site.is_a?(URI) ? site : URI.parse(site) 
    8       end 
    9  
    10       def site 
    11         @@site 
     9        @site = site.is_a?(URI) ? site : URI.parse(site) 
    1210      end 
    1311 
     
    2422        element_name.pluralize 
    2523      end 
    26        
    27       def element_path(id) 
    28         "/#{collection_name}/#{id}.xml" 
     24 
     25      def prefix(options={}) 
     26        default = site.path 
     27        default << '/' unless default[-1..-1] == '/' 
     28        set_prefix default 
     29        prefix(options) 
    2930      end 
    3031       
    31       def collection_path 
    32         "/#{collection_name}.xml" 
     32      def set_prefix(value = '/') 
     33        prefix_call = value.gsub(/:\w+/) { |s| "\#{options[#{s}]}" } 
     34        method_decl = %(def self.prefix(options={}) "#{prefix_call}" end) 
     35        eval method_decl 
    3336      end 
    3437       
     38      def set_element_name(value) 
     39        class << self ; attr_reader :element_name ; end 
     40        @element_name = value 
     41      end 
     42       
     43      def set_collection_name(value) 
     44        class << self ; attr_reader :collection_name ; end 
     45        @collection_name = value 
     46      end 
     47 
     48      def element_path(id, options = {}) 
     49        "#{prefix(options)}#{collection_name}/#{id}.xml" 
     50      end 
     51       
     52      def collection_path(options = {}) 
     53        "#{prefix(options)}#{collection_name}.xml" 
     54      end 
     55       
     56      def primary_key 
     57        set_primary_key 'id' 
     58      end 
     59       
     60      def set_primary_key(value) 
     61        class << self ; attr_reader :primary_key ; end 
     62        @primary_key = value 
     63      end 
     64       
     65      # Person.find(1) # => GET /people/1.xml 
     66      # StreetAddress.find(1, :person_id => 1) # => GET /people/1/street_addresses/1.xml 
    3567      def find(*arguments) 
    36         scope = arguments.slice!(0) 
     68        scope   = arguments.slice!(0) 
     69        options = arguments.slice!(0) || {} 
    3770 
    3871        case scope 
    39           when Fixnum 
    40             # { :person => person1 } 
    41             new(connection.get(element_path(scope)).values.first) 
    42           when :all 
    43             # { :people => { :person => [ person1, person2 ] } } 
    44             connection.get(collection_path).values.first.values.first.collect { |element| new(element) } 
    45           when :first 
    46             find(:all, *arguments).first 
     72          when :all   then find_every(options) 
     73          when :first then find_every(options).first 
     74          else             find_single(scope, options) 
    4775        end 
    4876      end 
     77 
     78      private 
     79        # { :people => { :person => [ person1, person2 ] } } 
     80        def find_every(options) 
     81          connection.get(collection_path(options)).values.first.values.first.collect { |element| new(element, options) } 
     82        end 
     83         
     84        # { :person => person1 } 
     85        def find_single(scope, options) 
     86          new(connection.get(element_path(scope, options)).values.first, options) 
     87        end 
    4988    end 
    5089 
    5190    attr_accessor :attributes 
     91    attr_accessor :prefix_options 
    5292     
    53     def initialize(attributes = {}) 
    54       @attributes = attributes 
     93    def initialize(attributes = {}, prefix_options = {}) 
     94      @attributes     = attributes 
     95      @prefix_options = prefix_options 
    5596    end 
    56      
     97 
     98    def new_resource? 
     99      id.nil? 
     100    end 
     101 
    57102    def id 
    58       attributes["id"
     103      attributes[self.class.primary_key
    59104    end 
    60105     
    61106    def id=(id) 
    62       attributes["id"] = id 
     107      attributes[self.class.primary_key] = id 
    63108    end 
    64109     
    65110    def save 
    66       update 
     111      new_resource? ? create : update 
    67112    end 
    68113 
    69114    def destroy 
    70       connection.delete(self.class.element_path(id)
     115      connection.delete(self.class.element_path(id, prefix_options)[0..-5]
    71116    end 
    72117     
     
    74119      attributes.to_xml(:root => self.class.element_name) 
    75120    end 
    76      
     121 
     122    # Reloads the attributes of this object from the remote web service. 
     123    def reload 
     124      @attributes.update(self.class.find(self.id, @prefix_options).instance_variable_get(:@attributes)) 
     125      self 
     126    end 
     127 
    77128    protected 
    78129      def connection(refresh = false) 
     
    81132     
    82133      def update 
    83         connection.put(self.class.element_path(id), to_xml) 
     134        connection.put(self.class.element_path(id, prefix_options)[0..-5], to_xml) 
    84135      end 
    85      
     136 
     137      def create 
     138        returning connection.post(self.class.collection_path(prefix_options)[0..-5], to_xml) do |resp| 
     139          self.id = resp['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] 
     140        end 
     141      end 
     142 
    86143      def method_missing(method_symbol, *arguments) 
    87144        method_name = method_symbol.to_s 
     
    91148            attributes[method_name.first(-1)] = arguments.first 
    92149          when "?" 
    93             # TODO 
     150            attributes[method_name.first(-1)] == true 
    94151          else 
    95             attributes[method_name] || super 
     152            attributes.has_key?(method_name) ? attributes[method_name] : super 
    96153        end 
    97154      end 
  • trunk/activeresource/lib/active_resource/connection.rb

    r4887 r4890  
    3434        @@requests ||= [] 
    3535      end 
     36       
     37      def default_header 
     38        class << self ; attr_reader :default_header end 
     39        @default_header = { 'Content-Type' => 'application/xml' } 
     40      end 
    3641    end 
    3742 
     
    4550 
    4651    def delete(path) 
    47       request(:delete, path
     52      request(:delete, path, self.class.default_header
    4853    end 
    4954 
    5055    def put(path, body = '') 
    51       request(:put, path, body
     56      request(:put, path, body, self.class.default_header
    5257    end 
    5358 
    5459    def post(path, body = '') 
    55       request(:post, path, body
     60      request(:post, path, body, self.class.default_header
    5661    end 
    5762 
  • trunk/activeresource/test/base_test.rb

    r4886 r4890  
    11require "#{File.dirname(__FILE__)}/abstract_unit" 
    22require "fixtures/person" 
     3require "fixtures/street_address" 
    34 
    45class BaseTest < Test::Unit::TestCase 
    56  def setup 
    6     ActiveResource::HttpMock.respond_to( 
    7       ActiveResource::Request.new(:get,    "/people/1.xml") => ActiveResource::Response.new("<person><name>Matz</name><id type='integer'>1</id></person>"), 
    8       ActiveResource::Request.new(:get,    "/people/2.xml") => ActiveResource::Response.new("<person><name>David</name><id type='integer'>2</id></person>"), 
    9       ActiveResource::Request.new(:put,    "/people/1.xml") => ActiveResource::Response.new({}, 200), 
    10       ActiveResource::Request.new(:delete, "/people/1.xml") => ActiveResource::Response.new({}, 200), 
    11       ActiveResource::Request.new(:delete, "/people/2.xml") => ActiveResource::Response.new({}, 400), 
    12       ActiveResource::Request.new(:post,   "/people.xml")     => ActiveResource::Response.new({}, 200), 
    13       ActiveResource::Request.new(:get,    "/people/99.xml")  => ActiveResource::Response.new({}, 404), 
    14       ActiveResource::Request.new(:get,    "/people.xml")     => ActiveResource::Response.new( 
    15         "<people><person><name>Matz</name><id type='integer'>1</id></person><person><name>David</name><id type='integer'>2</id></person></people>" 
    16       ) 
    17     ) 
     7    @matz  = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') 
     8    @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') 
     9    @addy  = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address') 
     10    ActiveResource::HttpMock.respond_to do |mock| 
     11      mock.get    "/people/1.xml",             @matz 
     12      mock.get    "/people/2.xml",             @david 
     13      mock.put    "/people/1",                 nil, 204 
     14      mock.delete "/people/1",                 nil, 200 
     15      mock.delete "/people/2",                 nil, 400 
     16      mock.post   "/people",                   nil, 201, 'Location' => '/people/5.xml' 
     17      mock.get    "/people/99.xml",            nil, 404 
     18      mock.get    "/people.xml",               "<people>#{@matz}#{@david}</people>" 
     19      mock.get    "/people/1/addresses.xml",   "<addresses>#{@addy}</addresses>" 
     20      mock.get    "/people/1/addresses/1.xml", @addy 
     21      mock.put    "/people/1/addresses/1",     nil, 204 
     22      mock.delete "/people/1/addresses/1",     nil, 200 
     23      mock.post   "/people/1/addresses",       nil, 201, 'Location' => '/people/1/addresses/5' 
     24      mock.get    "/people//addresses.xml",    nil, 404 
     25      mock.get    "/people//addresses/1.xml",  nil, 404 
     26      mock.put    "/people//addresses/1",      nil, 404 
     27      mock.delete "/people//addresses/1",      nil, 404 
     28      mock.post   "/people//addresses",        nil, 404 
     29    end 
    1830  end 
    1931 
     
    3446  end 
    3547 
     48  def test_collection_path 
     49    assert_equal '/people.xml', Person.collection_path 
     50  end 
     51 
     52  def test_custom_element_path 
     53    assert_equal '/people/1/addresses/1.xml', StreetAddress.element_path(1, :person_id => 1) 
     54  end 
     55 
     56  def test_custom_collection_path 
     57    assert_equal '/people/1/addresses.xml', StreetAddress.collection_path(:person_id => 1) 
     58  end 
     59 
     60  def test_custom_element_name 
     61    assert_equal 'address', StreetAddress.element_name 
     62  end 
     63 
     64  def test_custom_collection_name 
     65    assert_equal 'addresses', StreetAddress.collection_name 
     66  end 
     67 
     68  def test_prefix 
     69    assert_equal "/", Person.prefix 
     70  end 
     71 
     72  def test_custom_prefix 
     73    assert_equal '/people//', StreetAddress.prefix 
     74    assert_equal '/people/1/', StreetAddress.prefix(:person_id => 1) 
     75  end 
     76 
    3677  def test_find_by_id 
    3778    matz = Person.find(1) 
    3879    assert_kind_of Person, matz 
    3980    assert_equal "Matz", matz.name 
     81  end 
     82 
     83  def test_find_by_id_with_custom_prefix 
     84    addy = StreetAddress.find(1, :person_id => 1) 
     85    assert_kind_of StreetAddress, addy 
     86    assert_equal '12345 Street', addy.street 
    4087  end 
    4188 
     
    56103  def test_find_by_id_not_found 
    57104    assert_raises(ActiveResource::ResourceNotFound) { Person.find(99) } 
     105    assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1) } 
    58106  end 
    59    
     107 
     108  def test_create 
     109    rick = Person.new 
     110    rick.save 
     111    assert_equal '5', rick.id 
     112  end 
     113 
     114  def test_create_with_custom_prefix 
     115    matzs_house = StreetAddress.new({}, {:person_id => 1}) 
     116    matzs_house.save 
     117    assert_equal '5', matzs_house.id 
     118  end 
     119 
    60120  def test_update 
    61121    matz = Person.find(:first) 
     
    65125    matz.save 
    66126  end 
    67    
     127 
     128  def test_update_with_custom_prefix 
     129    addy = StreetAddress.find(1, :person_id => 1) 
     130    addy.street = "54321 Street" 
     131    assert_kind_of StreetAddress, addy 
     132    assert_equal "54321 Street", addy.street 
     133    addy.save 
     134  end 
     135 
    68136  def test_destroy 
    69137    assert Person.find(1).destroy 
    70     assert_raises(ActiveResource::ClientError) { Person.find(2).destroy } 
     138    ActiveResource::HttpMock.respond_to do |mock| 
     139      mock.get "/people/1.xml", nil, 404 
     140    end 
     141    assert_raises(ActiveResource::ResourceNotFound) { Person.find(1).destroy } 
     142  end 
     143 
     144  def test_destroy_with_custom_prefix 
     145    assert StreetAddress.find(1, :person_id => 1).destroy 
     146    ActiveResource::HttpMock.respond_to do |mock| 
     147      mock.get "/people/1/addresses/1.xml", nil, 404 
     148    end 
     149    assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :person_id => 1).destroy } 
    71150  end 
    72151end 
  • trunk/activeresource/test/fixtures/person.rb

    r4492 r4890  
    11class Person < ActiveResource::Base 
    2   self.site = "http://37s.sunrise.i:3000/
     2  self.site = "http://37s.sunrise.i:3000
    33end 
  • trunk/activeresource/test/http_mock.rb

    r4492 r4890  
    33module ActiveResource 
    44  class HttpMock 
     5    class Responder 
     6      def initialize(responses) 
     7        @responses = responses 
     8      end 
     9       
     10      for method in [ :post, :put, :get, :delete ] 
     11        module_eval <<-EOE 
     12          def #{method}(path, body = nil, status = 200, headers = {}) 
     13            @responses[Request.new(:#{method}, path, nil)] = Response.new(body || {}, status, headers) 
     14          end 
     15        EOE 
     16      end 
     17    end 
     18 
    519    class << self 
    620      def requests 
     
    1226      end 
    1327 
    14       def respond_to(pairs
     28      def respond_to(pairs = {}
    1529        reset! 
    1630        pairs.each do |(path, response)| 
    1731          responses[path] = response 
    1832        end 
     33        yield Responder.new(responses) if block_given? 
    1934      end 
    2035 
     
    4358    attr_accessor :path, :method, :body 
    4459     
    45     def initialize(method, path, body = nil
     60    def initialize(method, path, body = nil, headers = nil
    4661      @method, @path, @body = method, path, body 
    4762    end 
     
    6580   
    6681  class Response 
    67     attr_accessor :body, :code 
     82    attr_accessor :body, :code, :headers 
    6883     
    69     def initialize(body, code = 200
    70       @body, @code = body, code 
     84    def initialize(body, code = 200, headers = nil
     85      @body, @code, @headers = body, code, headers 
    7186    end 
    7287     
     
    7489      (200..299).include?(code) 
    7590    end 
     91 
     92    def [](key) 
     93      headers[key] 
     94    end 
     95     
     96    def []=(key, value) 
     97      headers[key] = value 
     98    end 
     99 
    76100  end 
    77101