Ticket #6979: active_resource_custom_methods_path.diff
| File active_resource_custom_methods_path.diff, 13.0 kB (added by rwdaigle, 2 years ago) |
|---|
-
test/base_test.rb
old new 6 6 class BaseTest < Test::Unit::TestCase 7 7 def setup 8 8 @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') 9 @matz_deep = { :id => 1, :name => 'Matz', :other => 'other' }.to_xml(:root => 'person') 10 @ryan = { :name => 'Ryan' }.to_xml(:root => 'person') 9 11 @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') 10 12 @addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address') 13 @addy_deep = { :id => 1, :street => '12345 Street', :zip => "27519" }.to_xml(:root => 'address') 11 14 @default_request_headers = { 'Content-Type' => 'application/xml' } 12 15 13 16 ActiveResource::HttpMock.respond_to do |mock| 14 17 mock.get "/people/1.xml", {}, @matz 18 mock.get "/people/1.xml;shallow", {}, @matz 19 mock.get "/people/1.xml;deep", {}, @matz_deep 20 mock.get "/people.xml;retrieve?name=Matz", {}, "<people>#{@matz}</people>" 15 21 mock.get "/people/2.xml", {}, @david 16 22 mock.put "/people/1.xml", {}, nil, 204 23 mock.put "/people/1.xml;promote?position=Manager", {}, nil, 204 24 mock.put "/people.xml;promote?name=Matz", {}, nil, 204 25 mock.put "/people.xml;sort?by=name", {}, nil, 204 17 26 mock.delete "/people/1.xml", {}, nil, 200 18 27 mock.delete "/people/2.xml", {}, nil, 400 28 mock.delete "/people.xml;deactivate?name=Matz", {}, nil, 200 29 mock.delete "/people/1.xml;deactivate", {}, nil, 200 19 30 mock.post "/people.xml", {}, nil, 201, 'Location' => '/people/5.xml' 31 mock.post "/people.xml/new;register", {}, @ryan, 201, 'Location' => '/people/5.xml' 20 32 mock.get "/people/99.xml", {}, nil, 404 21 33 mock.get "/people.xml", {}, "<people>#{@matz}#{@david}</people>" 22 34 mock.get "/people/1/addresses.xml", {}, "<addresses>#{@addy}</addresses>" 23 35 mock.get "/people/1/addresses/1.xml", {}, @addy 36 mock.get "/people/1/addresses/1.xml;deep", {}, @addy_deep 24 37 mock.put "/people/1/addresses/1.xml", {}, nil, 204 38 mock.put "/people/1/addresses/1.xml;normalize_phone?locale=US", {}, nil, 204 39 mock.put "/people/1/addresses.xml;sort?by=name", {}, nil, 204 25 40 mock.delete "/people/1/addresses/1.xml", {}, nil, 200 26 41 mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5' 42 mock.post "/people/1/addresses.xml/new;link", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml' 27 43 mock.get "/people//addresses.xml", {}, nil, 404 28 44 mock.get "/people//addresses/1.xml", {}, nil, 404 29 45 mock.put "/people//addresses/1.xml", {}, nil, 404 … … 237 253 def test_delete 238 254 assert Person.delete(1) 239 255 end 256 257 def test_custom_collection_method 258 259 Person.collection_call do |call| 260 call.get :retrieve 261 call.put :promote, :sort 262 call.delete :deactivate 263 end 264 265 # Test GET against a collection URL 266 assert_equal [Person.new(:id => 1, :name => 'Matz')], Person.retrieve(:name => 'Matz') 267 268 # Test PUT against a collection URL 269 assert Person.promote(:name => 'Matz') 270 assert Person.sort(:by => 'name') 271 272 # Test DELETE against a collection URL 273 assert Person.deactivate(:name => 'Matz') 274 275 # With nested resources 276 StreetAddress.collection_call do |call| 277 call.put :sort 278 end 279 280 # Test PUT against a nested collection URL 281 assert StreetAddress.sort(:person_id => 1, :by => 'name') 282 end 283 284 def test_custom_element_method 285 286 Person.element_call do |call| 287 call.get :shallow, :deep 288 call.put :promote 289 call.delete :deactivate 290 end 291 292 # Test GET against an element URL 293 assert_equal Person.new(:id => 1, :name => 'Matz'), Person.find(1).shallow 294 assert_equal Person.new(:id => 1, :name => 'Matz', :other => 'other'), Person.find(1).deep 295 296 # Test PUT against an element URL 297 assert Person.find(1).promote(:position => 'Manager') 298 299 # Test DELETE against an element URL 300 assert Person.find(1).deactivate 301 302 # With nested resources 303 StreetAddress.element_call do |call| 304 call.get :deep 305 call.put :normalize_phone 306 end 307 308 # Test GET against a nested collection URL 309 addy = StreetAddress.find(1, :person_id => 1) 310 assert !addy.defined?(:zip) 311 deep_addy = addy.deep 312 assert_not_nil deep_addy 313 assert_equal "27519", deep_addy.zip 314 315 # Test PUT against a nested collection URL 316 assert addy.normalize_phone(:locale => 'US') 317 end 318 319 def test_custom_new_element_method 320 321 Person.new_element_call do |call| 322 call.post :register 323 end 324 325 # Test POST against a new element URL 326 ryan = Person.new(:name => 'Ryan') 327 assert ryan.register 328 assert_equal '5', ryan.id 329 330 # With nested resources 331 StreetAddress.new_element_call do |call| 332 call.post :link 333 end 334 335 # Test POST against a nested collection URL 336 addy = StreetAddress.new({:street => '123 Test Dr.'}, {:person_id => 1}) 337 assert addy.link 338 assert_equal "2", addy.id 339 end 240 340 end -
lib/active_resource/custom_methods.rb
old new 1 # Support custom REST methods, in synch with the Simply RESTful 2 # plugin. 3 # 4 # I.e. on the server routes config you would have: 5 # 6 # map.resources :people, :new => { :register => :post }, 7 # :element => { :promote => :put, :deactivate => :delete } 8 # :collection => { :active => :get } 9 # 10 # Which creates routes for the following http requests 11 # 12 # POST: /people/new;register #=> PeopleController.register 13 # PUT: /people/1;promote #=> PeopleController.promote(:id => 1) 14 # DELETE: /people/1;deactivate #=> PeopleController.deactivate(:id => 1) 15 # GET: /people;active #=> PeopleController.active 16 # 17 # This module provides the ability for ActiveResource to call these 18 # custom REST methods: 19 # 20 # class Person < ActiveResource::Base 21 # self.site = "http://37s.sunrise.i:3000" 22 # 23 # self.new_element_call do |call| 24 # call.post :register 25 # end 26 # 27 # self.element_call do |call| 28 # call.put :promote 29 # call.delete :deactivate 30 # end 31 # 32 # self.collection_call do |call| 33 # call.get :active 34 # end 35 # end 36 # 37 # # Defined methods are now available as class/instance methods 38 # 39 # ryan = Person.new(:name => 'Ryan) 40 # ryan.register #=> true 41 # ryan.id #=> 1 42 # 43 # ryan = Person.find(1) 44 # ryan.promote(:position => 'Manager') #=> true 45 # ryan.deactivate #=> true 46 # 47 # Person.active #=> [<Person::xxx>, <Person::xxx>] 48 # 49 module ActiveResource 50 module CustomMethods 51 52 def self.included(within) 53 within.extend(ClassMethods) 54 end 55 56 module ClassMethods 57 def collection_call 58 yield ActiveResource::CollectionCall.new(self) 59 end 60 def element_call 61 yield ActiveResource::ElementCall.new(self) 62 end 63 def new_element_call 64 yield ActiveResource::NewElementCall.new(self) 65 end 66 end 67 end 68 69 # These feel bloated - revisit for more DRY... 70 71 class CollectionCall 72 def initialize(klass) 73 @klass = klass 74 end 75 def get(*method_names) 76 method_names.each do |method| 77 @klass.class_eval <<-end_eval 78 def #{@klass}.#{method}(url_options = {}) 79 collection = connection.get("\#{prefix(url_options)}\#{collection_name}.xml;#{method}\#{query_string(url_options)}") || [] 80 collection.collect! { |element| new(element, url_options) } 81 end 82 end_eval 83 end 84 end 85 def put(*method_names) 86 method_names.each do |method| 87 @klass.class_eval <<-end_eval 88 def #{@klass}.#{method}(url_options = {}) 89 connection.put("\#{prefix(url_options)}\#{collection_name}.xml;#{method}\#{query_string(url_options)}") 90 end 91 end_eval 92 end 93 end 94 def delete(*method_names) 95 method_names.each do |method| 96 @klass.class_eval <<-end_eval 97 def #{@klass}.#{method}(url_options = {}) 98 connection.delete("\#{prefix(url_options)}\#{collection_name}.xml;#{method}\#{query_string(url_options)}") 99 end 100 end_eval 101 end 102 end 103 end 104 105 class ElementCall 106 def initialize(klass) 107 @klass = klass 108 end 109 def get(*method_names) 110 method_names.each do |method| 111 @klass.class_eval <<-end_eval 112 def #{method}(url_options = {}) 113 #{@klass}.new(connection.get("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}/\#{id}.xml;#{method}\#{self.class.query_string(url_options)}")) 114 end 115 end_eval 116 end 117 end 118 def put(*method_names) 119 method_names.each do |method| 120 @klass.class_eval <<-end_eval 121 def #{method}(url_options = {}) 122 connection.put("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}/\#{id}.xml;#{method}\#{self.class.query_string(url_options)}") 123 end 124 end_eval 125 end 126 end 127 def delete(*method_names) 128 method_names.each do |method| 129 @klass.class_eval <<-end_eval 130 def #{method}(url_options = {}) 131 connection.delete("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}/\#{id}.xml;#{method}\#{self.class.query_string(url_options)}") 132 end 133 end_eval 134 end 135 end 136 end 137 138 class NewElementCall 139 def initialize(klass) 140 @klass = klass 141 end 142 def post(*method_names) 143 method_names.each do |method| 144 @klass.class_eval <<-end_eval 145 def #{method}(url_options = {}) 146 returning(connection.post("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}.xml/new;#{method}", to_xml)) do |response| 147 self.id = id_from_response(response) 148 end 149 end 150 end_eval 151 end 152 end 153 end 154 end -
lib/active_resource/base.rb
old new 80 80 connection.delete(element_path(id)) 81 81 end 82 82 83 # This was private but was needed in CustomMethods, pretty sure 84 # there's a better way to access it other than to promote it to 85 # public. 86 def query_string(options) 87 # Omit parameters which appear in the URI path. 88 query_params = options.reject { |key, value| prefix_parameters.include?(key) } 89 90 # Accumulate a list of escaped key=value pairs for the given parameters. 91 pairs = [] 92 query_params.each do |key, value| 93 key = CGI.escape(key.to_s) 94 95 # a => b becomes a=b 96 # a => [b, c] becomes a[]=b&a[]=c 97 case value 98 when Array 99 value.each { |val| pairs << "#{key}[]=#{CGI.escape(val.to_s)}" } 100 else 101 pairs << "#{key}=#{CGI.escape(value.to_s)}" 102 end 103 end 104 105 "?#{pairs * '&'}" unless pairs.empty? 106 end 107 83 108 private 84 109 def find_every(options) 85 110 collection = connection.get(collection_path(options)) || [] … … 98 123 def prefix_parameters 99 124 @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set 100 125 end 101 102 def query_string(options)103 # Omit parameters which appear in the URI path.104 query_params = options.reject { |key, value| prefix_parameters.include?(key) }105 106 # Accumulate a list of escaped key=value pairs for the given parameters.107 pairs = []108 query_params.each do |key, value|109 key = CGI.escape(key.to_s)110 111 # a => b becomes a=b112 # a => [b, c] becomes a[]=b&a[]=c113 case value114 when Array115 value.each { |val| pairs << "#{key}[]=#{CGI.escape(val.to_s)}" }116 else117 pairs << "#{key}=#{CGI.escape(value.to_s)}"118 end119 end120 121 "?#{pairs * '&'}" unless pairs.empty?122 end123 126 end 124 127 125 128 attr_accessor :attributes -
lib/active_resource.rb
old new 37 37 require 'active_resource/base' 38 38 require 'active_resource/struct' 39 39 require 'active_resource/validations' 40 require 'active_resource/custom_methods' 40 41 41 42 module ActiveResource 42 43 Base.class_eval do 43 44 include Validations 45 include CustomMethods 44 46 end 45 47 end