Changeset 4890
- Timestamp:
- 09/01/06 01:15:10 (2 years ago)
- Files:
-
- trunk/activeresource/CHANGELOG (modified) (1 diff)
- trunk/activeresource/lib/active_resource/base.rb (modified) (5 diffs)
- trunk/activeresource/lib/active_resource/connection.rb (modified) (2 diffs)
- trunk/activeresource/test/base_test.rb (modified) (4 diffs)
- trunk/activeresource/test/fixtures/person.rb (modified) (1 diff)
- trunk/activeresource/test/fixtures/street_address.rb (added)
- trunk/activeresource/test/http_mock.rb (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activeresource/CHANGELOG
r4887 r4890 1 1 *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 2 29 3 30 * 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 4 4 class Base 5 5 class << self 6 attr_reader :site 7 6 8 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) 12 10 end 13 11 … … 24 22 element_name.pluralize 25 23 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) 29 30 end 30 31 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 33 36 end 34 37 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 35 67 def find(*arguments) 36 scope = arguments.slice!(0) 68 scope = arguments.slice!(0) 69 options = arguments.slice!(0) || {} 37 70 38 71 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) 47 75 end 48 76 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 49 88 end 50 89 51 90 attr_accessor :attributes 91 attr_accessor :prefix_options 52 92 53 def initialize(attributes = {}) 54 @attributes = attributes 93 def initialize(attributes = {}, prefix_options = {}) 94 @attributes = attributes 95 @prefix_options = prefix_options 55 96 end 56 97 98 def new_resource? 99 id.nil? 100 end 101 57 102 def id 58 attributes[ "id"]103 attributes[self.class.primary_key] 59 104 end 60 105 61 106 def id=(id) 62 attributes[ "id"] = id107 attributes[self.class.primary_key] = id 63 108 end 64 109 65 110 def save 66 update111 new_resource? ? create : update 67 112 end 68 113 69 114 def destroy 70 connection.delete(self.class.element_path(id ))115 connection.delete(self.class.element_path(id, prefix_options)[0..-5]) 71 116 end 72 117 … … 74 119 attributes.to_xml(:root => self.class.element_name) 75 120 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 77 128 protected 78 129 def connection(refresh = false) … … 81 132 82 133 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) 84 135 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 86 143 def method_missing(method_symbol, *arguments) 87 144 method_name = method_symbol.to_s … … 91 148 attributes[method_name.first(-1)] = arguments.first 92 149 when "?" 93 # TODO150 attributes[method_name.first(-1)] == true 94 151 else 95 attributes [method_name] ||super152 attributes.has_key?(method_name) ? attributes[method_name] : super 96 153 end 97 154 end trunk/activeresource/lib/active_resource/connection.rb
r4887 r4890 34 34 @@requests ||= [] 35 35 end 36 37 def default_header 38 class << self ; attr_reader :default_header end 39 @default_header = { 'Content-Type' => 'application/xml' } 40 end 36 41 end 37 42 … … 45 50 46 51 def delete(path) 47 request(:delete, path )52 request(:delete, path, self.class.default_header) 48 53 end 49 54 50 55 def put(path, body = '') 51 request(:put, path, body )56 request(:put, path, body, self.class.default_header) 52 57 end 53 58 54 59 def post(path, body = '') 55 request(:post, path, body )60 request(:post, path, body, self.class.default_header) 56 61 end 57 62 trunk/activeresource/test/base_test.rb
r4886 r4890 1 1 require "#{File.dirname(__FILE__)}/abstract_unit" 2 2 require "fixtures/person" 3 require "fixtures/street_address" 3 4 4 5 class BaseTest < Test::Unit::TestCase 5 6 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 18 30 end 19 31 … … 34 46 end 35 47 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 36 77 def test_find_by_id 37 78 matz = Person.find(1) 38 79 assert_kind_of Person, matz 39 80 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 40 87 end 41 88 … … 56 103 def test_find_by_id_not_found 57 104 assert_raises(ActiveResource::ResourceNotFound) { Person.find(99) } 105 assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1) } 58 106 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 60 120 def test_update 61 121 matz = Person.find(:first) … … 65 125 matz.save 66 126 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 68 136 def test_destroy 69 137 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 } 71 150 end 72 151 end trunk/activeresource/test/fixtures/person.rb
r4492 r4890 1 1 class Person < ActiveResource::Base 2 self.site = "http://37s.sunrise.i:3000 /"2 self.site = "http://37s.sunrise.i:3000" 3 3 end trunk/activeresource/test/http_mock.rb
r4492 r4890 3 3 module ActiveResource 4 4 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 5 19 class << self 6 20 def requests … … 12 26 end 13 27 14 def respond_to(pairs )28 def respond_to(pairs = {}) 15 29 reset! 16 30 pairs.each do |(path, response)| 17 31 responses[path] = response 18 32 end 33 yield Responder.new(responses) if block_given? 19 34 end 20 35 … … 43 58 attr_accessor :path, :method, :body 44 59 45 def initialize(method, path, body = nil )60 def initialize(method, path, body = nil, headers = nil) 46 61 @method, @path, @body = method, path, body 47 62 end … … 65 80 66 81 class Response 67 attr_accessor :body, :code 82 attr_accessor :body, :code, :headers 68 83 69 def initialize(body, code = 200 )70 @body, @code = body, code84 def initialize(body, code = 200, headers = nil) 85 @body, @code, @headers = body, code, headers 71 86 end 72 87 … … 74 89 (200..299).include?(code) 75 90 end 91 92 def [](key) 93 headers[key] 94 end 95 96 def []=(key, value) 97 headers[key] = value 98 end 99 76 100 end 77 101