Ticket #6979: active_resource_custom_methods_path_consolidated.diff
| File active_resource_custom_methods_path_consolidated.diff, 13.1 kB (added by rwdaigle, 2 years ago) |
|---|
-
test/base/custom_methods_test.rb
old new 1 require "#{File.dirname(__FILE__)}/../abstract_unit" 2 require "#{File.dirname(__FILE__)}/../fixtures/person" 3 require "#{File.dirname(__FILE__)}/../fixtures/street_address" 4 5 class BaseTest < Test::Unit::TestCase 6 def setup 7 @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') 8 @matz_deep = { :id => 1, :name => 'Matz', :other => 'other' }.to_xml(:root => 'person') 9 @ryan = { :name => 'Ryan' }.to_xml(:root => 'person') 10 @addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address') 11 @addy_deep = { :id => 1, :street => '12345 Street', :zip => "27519" }.to_xml(:root => 'address') 12 @default_request_headers = { 'Content-Type' => 'application/xml' } 13 14 ActiveResource::HttpMock.respond_to do |mock| 15 mock.get "/people/1.xml", {}, @matz 16 mock.get "/people/1.xml;shallow", {}, @matz 17 mock.get "/people/1.xml;deep", {}, @matz_deep 18 mock.get "/people.xml;retrieve?name=Matz", {}, "<people>#{@matz}</people>" 19 mock.put "/people/1.xml;promote?position=Manager", {}, nil, 204 20 mock.put "/people.xml;promote?name=Matz", {}, nil, 204 21 mock.put "/people.xml;sort?by=name", {}, nil, 204 22 mock.delete "/people.xml;deactivate?name=Matz", {}, nil, 200 23 mock.delete "/people/1.xml;deactivate", {}, nil, 200 24 mock.post "/people.xml/new;register", {}, @ryan, 201, 'Location' => '/people/5.xml' 25 mock.get "/people/1/addresses/1.xml", {}, @addy 26 mock.get "/people/1/addresses/1.xml;deep", {}, @addy_deep 27 mock.put "/people/1/addresses/1.xml;normalize_phone?locale=US", {}, nil, 204 28 mock.put "/people/1/addresses.xml;sort?by=name", {}, nil, 204 29 mock.post "/people/1/addresses.xml/new;link", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml' 30 end 31 end 32 33 def test_custom_collection_method 34 35 Person.invokes_custom_method do |person| 36 person.invokes :retrieve, :on => :collection, :using => :get 37 person.invokes [:promote, :sort], :on => :collection, :using => :put 38 person.invokes :deactivate, :on => :collection, :using => :delete 39 end 40 41 # Test GET against a collection URL 42 assert_equal [Person.new(:id => 1, :name => 'Matz')], Person.retrieve(:name => 'Matz') 43 44 # Test PUT against a collection URL 45 assert Person.promote(:name => 'Matz') 46 assert Person.sort(:by => 'name') 47 48 # Test DELETE against a collection URL 49 assert Person.deactivate(:name => 'Matz') 50 51 # With nested resources 52 StreetAddress.invokes_custom_method do |address| 53 address.invokes :sort, :on => :collection, :using => :put 54 end 55 56 # Test PUT against a nested collection URL 57 assert StreetAddress.sort(:person_id => 1, :by => 'name') 58 end 59 60 def test_custom_element_method 61 62 Person.invokes_custom_method do |person| 63 person.invokes [:shallow, :deep] 64 person.invokes :promote, :using => :put 65 person.invokes :deactivate, :using => :delete 66 end 67 68 # Test GET against an element URL 69 assert_equal Person.new(:id => 1, :name => 'Matz'), Person.find(1).shallow 70 assert_equal Person.new(:id => 1, :name => 'Matz', :other => 'other'), Person.find(1).deep 71 72 # Test PUT against an element URL 73 assert Person.find(1).promote(:position => 'Manager') 74 75 # Test DELETE against an element URL 76 assert Person.find(1).deactivate 77 78 # With nested resources 79 StreetAddress.invokes_custom_method do |address| 80 address.invokes :deep 81 address.invokes :normalize_phone, :using => :put 82 end 83 84 # Test GET against a nested collection URL 85 addy = StreetAddress.find(1, :person_id => 1) 86 assert !addy.defined?(:zip) 87 deep_addy = addy.deep 88 assert_not_nil deep_addy 89 assert_equal "27519", deep_addy.zip 90 91 # Test PUT against a nested collection URL 92 assert addy.normalize_phone(:locale => 'US') 93 end 94 95 def test_custom_new_element_method 96 97 Person.invokes_custom_method do |person| 98 person.invokes :register, :on => :new, :using => :post 99 end 100 101 # Test POST against a new element URL 102 ryan = Person.new(:name => 'Ryan') 103 assert ryan.register 104 assert_equal '5', ryan.id 105 106 # With nested resources 107 StreetAddress.invokes_custom_method do |address| 108 address.invokes :link, :on => :new, :using => :post 109 end 110 111 # Test POST against a nested collection URL 112 addy = StreetAddress.new({:street => '123 Test Dr.'}, {:person_id => 1}) 113 assert addy.link 114 assert_equal "2", addy.id 115 end 116 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.invokes_custom_method do |person| 24 # person.invokes :register, :on => :new, :using => :post 25 # person.invokes :promote, :using => :put 26 # person.invokes :deactivate, :using => :delete 27 # person.invokes :active, :on => :collection 28 # end 29 # end 30 # 31 # # Defined methods are now available as class/instance methods 32 # 33 # ryan = Person.new(:name => 'Ryan) 34 # ryan.register #=> true 35 # ryan.id #=> 1 36 # 37 # ryan = Person.find(1) 38 # ryan.promote(:position => 'Manager') #=> true 39 # ryan.deactivate #=> true 40 # 41 # Person.active #=> [<Person::xxx>, <Person::xxx>] 42 # 43 module ActiveResource 44 module CustomMethods 45 46 def self.included(within) 47 within.extend(ClassMethods) 48 end 49 50 module ClassMethods 51 def invokes_custom_method 52 yield ActiveResource::CustomMethodProxy.new(self) 53 end 54 end 55 end 56 57 class CustomMethodProxy 58 59 DEFAULT_OPTIONS = {:on => :element, :using => :get} 60 61 def initialize(klass) 62 @klass = klass 63 end 64 65 def invokes(custom_methods, options = {}) 66 67 method = options[:using] || DEFAULT_OPTIONS[:using] 68 type = options[:on] || DEFAULT_OPTIONS[:on] 69 70 case type 71 when :collection then 72 CollectionCall.new(@klass).send(method, custom_methods) 73 when :element then 74 ElementCall.new(@klass).send(method, custom_methods) 75 when :new then 76 NewElementCall.new(@klass).send(method, custom_methods) 77 end 78 end 79 end 80 81 # These feel bloated - revisit for more DRY... 82 83 class CollectionCall 84 def initialize(klass) 85 @klass = klass 86 end 87 def get(*method_names) 88 method_names.flatten.each do |method| 89 @klass.class_eval <<-end_eval 90 def #{@klass}.#{method}(url_options = {}) 91 collection = connection.get("\#{prefix(url_options)}\#{collection_name}.xml;#{method}\#{query_string(url_options)}") || [] 92 collection.collect! { |element| new(element, url_options) } 93 end 94 end_eval 95 end 96 end 97 def put(*method_names) 98 method_names.flatten.each do |method| 99 @klass.class_eval <<-end_eval 100 def #{@klass}.#{method}(url_options = {}) 101 connection.put("\#{prefix(url_options)}\#{collection_name}.xml;#{method}\#{query_string(url_options)}") 102 end 103 end_eval 104 end 105 end 106 def delete(*method_names) 107 method_names.flatten.each do |method| 108 @klass.class_eval <<-end_eval 109 def #{@klass}.#{method}(url_options = {}) 110 connection.delete("\#{prefix(url_options)}\#{collection_name}.xml;#{method}\#{query_string(url_options)}") 111 end 112 end_eval 113 end 114 end 115 end 116 117 class ElementCall 118 def initialize(klass) 119 @klass = klass 120 end 121 def get(*method_names) 122 method_names.flatten.each do |method| 123 @klass.class_eval <<-end_eval 124 def #{method}(url_options = {}) 125 #{@klass}.new(connection.get("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}/\#{id}.xml;#{method}\#{self.class.query_string(url_options)}")) 126 end 127 end_eval 128 end 129 end 130 def put(*method_names) 131 method_names.flatten.each do |method| 132 @klass.class_eval <<-end_eval 133 def #{method}(url_options = {}) 134 connection.put("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}/\#{id}.xml;#{method}\#{self.class.query_string(url_options)}") 135 end 136 end_eval 137 end 138 end 139 def delete(*method_names) 140 method_names.flatten.each do |method| 141 @klass.class_eval <<-end_eval 142 def #{method}(url_options = {}) 143 connection.delete("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}/\#{id}.xml;#{method}\#{self.class.query_string(url_options)}") 144 end 145 end_eval 146 end 147 end 148 end 149 150 class NewElementCall 151 def initialize(klass) 152 @klass = klass 153 end 154 def post(*method_names) 155 method_names.flatten.each do |method| 156 @klass.class_eval <<-end_eval 157 def #{method}(url_options = {}) 158 returning(connection.post("\#{self.class.prefix(prefix_options)}\#{self.class.collection_name}.xml/new;#{method}", to_xml)) do |response| 159 self.id = id_from_response(response) 160 end 161 end 162 end_eval 163 end 164 end 165 end 166 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