extended_her 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +8 -0
  4. data/CONTRIBUTING.md +26 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +7 -0
  7. data/README.md +723 -0
  8. data/Rakefile +11 -0
  9. data/UPGRADE.md +32 -0
  10. data/examples/twitter-oauth/Gemfile +13 -0
  11. data/examples/twitter-oauth/app.rb +50 -0
  12. data/examples/twitter-oauth/config.ru +5 -0
  13. data/examples/twitter-oauth/views/index.haml +9 -0
  14. data/examples/twitter-search/Gemfile +12 -0
  15. data/examples/twitter-search/app.rb +55 -0
  16. data/examples/twitter-search/config.ru +5 -0
  17. data/examples/twitter-search/views/index.haml +9 -0
  18. data/extended_her.gemspec +27 -0
  19. data/lib/her.rb +23 -0
  20. data/lib/her/api.rb +108 -0
  21. data/lib/her/base.rb +17 -0
  22. data/lib/her/collection.rb +12 -0
  23. data/lib/her/errors.rb +5 -0
  24. data/lib/her/exceptions/exception.rb +4 -0
  25. data/lib/her/exceptions/record_invalid.rb +8 -0
  26. data/lib/her/exceptions/record_not_found.rb +13 -0
  27. data/lib/her/middleware.rb +9 -0
  28. data/lib/her/middleware/accept_json.rb +15 -0
  29. data/lib/her/middleware/first_level_parse_json.rb +34 -0
  30. data/lib/her/middleware/second_level_parse_json.rb +28 -0
  31. data/lib/her/model.rb +69 -0
  32. data/lib/her/model/base.rb +7 -0
  33. data/lib/her/model/hooks.rb +114 -0
  34. data/lib/her/model/http.rb +284 -0
  35. data/lib/her/model/introspection.rb +57 -0
  36. data/lib/her/model/orm.rb +191 -0
  37. data/lib/her/model/orm/comparison_methods.rb +20 -0
  38. data/lib/her/model/orm/create_methods.rb +29 -0
  39. data/lib/her/model/orm/destroy_methods.rb +53 -0
  40. data/lib/her/model/orm/error_methods.rb +19 -0
  41. data/lib/her/model/orm/fields_definition.rb +15 -0
  42. data/lib/her/model/orm/find_methods.rb +46 -0
  43. data/lib/her/model/orm/persistance_methods.rb +22 -0
  44. data/lib/her/model/orm/relation_mapper.rb +21 -0
  45. data/lib/her/model/orm/save_methods.rb +58 -0
  46. data/lib/her/model/orm/serialization_methods.rb +28 -0
  47. data/lib/her/model/orm/update_methods.rb +31 -0
  48. data/lib/her/model/paths.rb +82 -0
  49. data/lib/her/model/relationships.rb +191 -0
  50. data/lib/her/paginated_collection.rb +20 -0
  51. data/lib/her/relation.rb +94 -0
  52. data/lib/her/version.rb +3 -0
  53. data/spec/api_spec.rb +131 -0
  54. data/spec/collection_spec.rb +26 -0
  55. data/spec/middleware/accept_json_spec.rb +10 -0
  56. data/spec/middleware/first_level_parse_json_spec.rb +42 -0
  57. data/spec/middleware/second_level_parse_json_spec.rb +25 -0
  58. data/spec/model/hooks_spec.rb +406 -0
  59. data/spec/model/http_spec.rb +184 -0
  60. data/spec/model/introspection_spec.rb +59 -0
  61. data/spec/model/orm_spec.rb +552 -0
  62. data/spec/model/paths_spec.rb +286 -0
  63. data/spec/model/relationships_spec.rb +222 -0
  64. data/spec/model_spec.rb +31 -0
  65. data/spec/spec_helper.rb +46 -0
  66. metadata +222 -0
@@ -0,0 +1,9 @@
1
+ require "her/middleware/first_level_parse_json"
2
+ require "her/middleware/second_level_parse_json"
3
+ require "her/middleware/accept_json"
4
+
5
+ module Her
6
+ module Middleware
7
+ DefaultParseJSON = FirstLevelParseJSON
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Her
2
+ module Middleware
3
+ # This middleware adds a "Accept: application/json" HTTP header
4
+ class AcceptJSON < Faraday::Middleware
5
+ def add_header(headers)
6
+ headers.merge! "Accept" => "application/json"
7
+ end
8
+
9
+ def call(env)
10
+ add_header(env[:request_headers])
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Her
2
+ module Middleware
3
+ # This middleware treat the received first-level JSON structure as the resource data.
4
+ class FirstLevelParseJSON < Faraday::Response::Middleware
5
+ # Parse the response body
6
+ #
7
+ # @param [String] body The response body
8
+ # @return [Mixed] the parsed response
9
+ def parse(body)
10
+ json = MultiJson.load(body, :symbolize_keys => true)
11
+ errors = json.delete(:errors) || {}
12
+ metadata = json.delete(:metadata) || []
13
+ {
14
+ :data => json,
15
+ :errors => errors,
16
+ :metadata => metadata
17
+ }
18
+ end
19
+
20
+ # This method is triggered when the response has been received. It modifies
21
+ # the value of `env[:body]`.
22
+ #
23
+ # @param [Hash] env The response environment
24
+ def on_complete(env)
25
+ case env[:status]
26
+ when 204
27
+ env[:body] = parse('{}')
28
+ else
29
+ env[:body] = parse(env[:body])
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module Her
2
+ module Middleware
3
+ # This middleware expects the resource/collection data to be contained in the `data`
4
+ # key of the JSON object
5
+ class SecondLevelParseJSON < Faraday::Response::Middleware
6
+ # Parse the response body
7
+ #
8
+ # @param [String] body The response body
9
+ # @return [Mixed] the parsed response
10
+ def parse(body)
11
+ json = MultiJson.load(body, :symbolize_keys => true)
12
+ {
13
+ :data => json[:data],
14
+ :errors => json[:errors],
15
+ :metadata => json[:metadata]
16
+ }
17
+ end
18
+
19
+ # This method is triggered when the response has been received. It modifies
20
+ # the value of `env[:body]`.
21
+ #
22
+ # @param [Hash] env The response environment
23
+ def on_complete(env)
24
+ env[:body] = parse(env[:body])
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,69 @@
1
+ require 'her/model/base'
2
+ require 'her/model/http'
3
+ require 'her/model/orm/comparison_methods'
4
+ require 'her/model/orm/create_methods'
5
+ require 'her/model/orm/destroy_methods'
6
+ require 'her/model/orm/error_methods'
7
+ require 'her/model/orm/fields_definition'
8
+ require 'her/model/orm/find_methods'
9
+ require 'her/model/orm/persistance_methods'
10
+ require 'her/model/orm/relation_mapper'
11
+ require 'her/model/orm/save_methods'
12
+ require 'her/model/orm/serialization_methods'
13
+ require 'her/model/orm/update_methods'
14
+ require 'her/model/orm'
15
+ require 'her/model/relationships'
16
+ require 'her/model/hooks'
17
+ require 'her/model/introspection'
18
+ require 'her/model/paths'
19
+
20
+ module Her
21
+ # This module is the main element of Her. After creating a Her::API object,
22
+ # include this module in your models to get a few magic methods defined in them.
23
+ #
24
+ # @example
25
+ # class User
26
+ # include Her::Model
27
+ # end
28
+ #
29
+ # @user = User.new(:name => "Rémi")
30
+ # @user.save
31
+ module Model
32
+ extend ActiveSupport::Concern
33
+
34
+ # Instance methods
35
+ include Her::Model::ORM
36
+ include Her::Model::Introspection
37
+ include Her::Model::Paths
38
+ include Her::Model::Relationships
39
+
40
+ # Class methods
41
+ included do
42
+ extend Her::Model::Base
43
+ extend Her::Model::HTTP
44
+ extend Her::Model::Hooks
45
+
46
+ # Define default settings
47
+ root_element name.demodulize.underscore
48
+ collection_path root_element.pluralize
49
+ resource_path [collection_path, '/:id'].join
50
+ uses_api Her::API.default_api
51
+ end
52
+
53
+ # Returns true if attribute_name is
54
+ # * in orm data
55
+ # * a relationship
56
+ def has_key?(attribute_name)
57
+ has_data?(attribute_name) ||
58
+ has_relationship?(attribute_name)
59
+ end
60
+
61
+ # Returns
62
+ # * the value of the attribute_nane attribute if it's in orm data
63
+ # * the resource/collection corrsponding to attribute_name if it's a relationship
64
+ def [](attribute_name)
65
+ get_data(attribute_name) ||
66
+ get_relationship(attribute_name)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,7 @@
1
+ module Her
2
+ module Model
3
+ # This module includes basic functionnality to Her::Model
4
+ module Base
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,114 @@
1
+ module Her
2
+ module Model
3
+ # Her supports hooks/callbacks that are triggered whenever resources are created, updated or destroyed.
4
+ #
5
+ # @example Defining a hook with a block
6
+ # class User
7
+ # include Her::Model
8
+ # before_save { |resource| resource.internal_id = 42 }
9
+ # end
10
+ #
11
+ # @example Defining a hook with a method name
12
+ # class User
13
+ # include Her::Model
14
+ # before_save :set_internal_id
15
+ #
16
+ # private
17
+ # def set_internal_id
18
+ # self.internal_id = 42
19
+ # end
20
+ # end
21
+ module Hooks
22
+ # Add a *before save* callback. Triggered before a resource is created or updated.
23
+ # @param [Symbol, &block] method A method or a block to be called
24
+ def before_save(method=nil, &block); set_hook(:before, :save, method || block); end
25
+
26
+ # Add a *before create* callback. Triggered before a resource is created.
27
+ # @param [Symbol, &block] method A method or a block to be called
28
+ def before_create(method=nil, &block); set_hook(:before, :create, method || block); end
29
+
30
+ # Add a *before update* callback. Triggered before a resource is updated.
31
+ # @param [Symbol, &block] method A method or a block to be called
32
+ def before_update(method=nil, &block); set_hook(:before, :update, method || block); end
33
+
34
+ # Add a *before destroy* callback. Triggered before a resource is destroyed.
35
+ # @param [Symbol, &block] method A method or a block to be called
36
+ def before_destroy(method=nil, &block); set_hook(:before, :destroy, method || block); end
37
+
38
+ # Do not add a *before find* callback. Only *after find* is supported.
39
+ def before_find(method=nil, &block); raise NoMethodError, "undefined method `before_find' for #{self}"; end
40
+
41
+ # Add a *after save* callback. Triggered after a resource is created or updated.
42
+ # @param [Symbol, &block] method A method or a block to be called
43
+ def after_save(method=nil, &block); set_hook(:after, :save, method || block); end
44
+
45
+ # Add a *after create* callback. Triggered after a resource is created.
46
+ # @param [Symbol, &block] method A method or a block to be called
47
+ def after_create(method=nil, &block); set_hook(:after, :create, method || block); end
48
+
49
+ # Add a *after update* callback. Triggered after a resource is updated.
50
+ # @param [Symbol, &block] method A method or a block to be called
51
+ def after_update(method=nil, &block); set_hook(:after, :update, method || block); end
52
+
53
+ # Add a *after destroy* callback. Triggered after a resource is destroyed.
54
+ # @param [Symbol, &block] method A method or a block to be called
55
+ def after_destroy(method=nil, &block); set_hook(:after, :destroy, method || block); end
56
+
57
+ # Add a *after find* callback. Triggered after a resource is found.
58
+ # @param [Symbol, &block] method A method or a block to be called
59
+ def after_find(method=nil, &block); set_hook(:after, :find, method || block); end
60
+
61
+ # Wrap a block between “before” and “after” hooks
62
+ # @private
63
+ def wrap_in_hooks(resource, *hooks)
64
+ perform_before_hooks(resource, *hooks)
65
+ yield(resource, resource.class) if block_given?
66
+ perform_after_hooks(resource, *hooks.reverse)
67
+ end
68
+
69
+ # @private
70
+ def hooks
71
+ @her_hooks ||= begin
72
+ if superclass.respond_to?(:hooks)
73
+ superclass.hooks.dup
74
+ else
75
+ {}
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+ # @private
82
+ def set_hook(time, name, action)
83
+ (self.hooks["#{time}_#{name}".to_sym] ||= []) << action
84
+ end
85
+
86
+ # @private
87
+ def perform_hook(record, time, name)
88
+ Array(self.hooks["#{time}_#{name}".to_sym]).each do |hook|
89
+ if hook.is_a? Symbol
90
+ record.send(hook)
91
+ else
92
+ hook.call(record)
93
+ end
94
+ end
95
+ end
96
+
97
+ # Perform “after” hooks on a resource
98
+ # @private
99
+ def perform_after_hooks(resource, *hooks)
100
+ hooks.each do |hook|
101
+ perform_hook(resource, :after, hook)
102
+ end
103
+ end
104
+
105
+ # Perform “before” hooks on a resource
106
+ # @private
107
+ def perform_before_hooks(resource, *hooks)
108
+ hooks.each do |hook|
109
+ perform_hook(resource, :before, hook)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,284 @@
1
+ module Her
2
+ module Model
3
+ # This module interacts with Her::API to fetch HTTP data
4
+ module HTTP
5
+ # Automatically inherit a superclass' api
6
+ def her_api
7
+ @her_api ||= begin
8
+ if superclass.respond_to?(:her_api)
9
+ superclass.her_api
10
+ else
11
+ Her::API.default_api
12
+ end
13
+ end
14
+ end
15
+
16
+ # Link a model with a Her::API object
17
+ def uses_api(api)
18
+ @her_api = api
19
+ end
20
+
21
+ # Main request wrapper around Her::API. Used to make custom request to the API.
22
+ # @private
23
+ def request(attrs={})
24
+ initial_attrs = attrs.dup
25
+ started = Time.now.to_f
26
+ parsed_data = her_api.request(attrs)
27
+ request_time = Time.now.to_f - started
28
+ log(initial_attrs, request_time)
29
+
30
+ if block_given?
31
+ yield parsed_data
32
+ else
33
+ parsed_data
34
+ end
35
+ end
36
+
37
+ def log(attrs, time)
38
+ return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger.respond_to?(:debug)
39
+
40
+ method = attrs.delete(:_method).to_s.upcase
41
+ path = attrs.delete(:_path)
42
+
43
+ Rails.logger.debug("* HER request: #{method} #{path} [#{time}s] #{attrs}")
44
+ end
45
+
46
+ # Make a GET request and return either a collection or a resource
47
+ #
48
+ # @example
49
+ # class User
50
+ # include Her::Model
51
+ # end
52
+ #
53
+ # @popular_users = User.get(:popular)
54
+ # # Fetched via GET "/users/popular"
55
+ def get(path, attrs={})
56
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
57
+ get_raw(path, attrs) do |parsed_data|
58
+ if parsed_data[:data].is_a?(Array)
59
+ new_collection(parsed_data)
60
+ else
61
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
62
+ end
63
+ end
64
+ end
65
+
66
+ # Make a GET request and return the parsed JSON response (not mapped to objects)
67
+ def get_raw(path, attrs={}, &block)
68
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
69
+ request(attrs.merge(:_method => :get, :_path => path), &block)
70
+ end
71
+
72
+ # Make a GET request and return a collection of resources
73
+ def get_collection(path=nil, attrs={})
74
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
75
+ get_raw(path, attrs) do |parsed_data|
76
+ new_collection(parsed_data)
77
+ end
78
+ end
79
+
80
+ # Make a GET request and return a collection of resources
81
+ def get_resource(path, attrs={})
82
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
83
+ get_raw(path, attrs) do |parsed_data|
84
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
85
+ end
86
+ end
87
+
88
+ # Make a POST request and return either a collection or a resource
89
+ def post(path, attrs={})
90
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
91
+ post_raw(path, attrs) do |parsed_data|
92
+ if parsed_data[:data].is_a?(Array)
93
+ new_collection(parsed_data)
94
+ else
95
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
96
+ end
97
+ end
98
+ end
99
+
100
+ # Make a POST request and return the parsed JSON response (not mapped to objects)
101
+ def post_raw(path, attrs={}, &block)
102
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
103
+ request(attrs.merge(:_method => :post, :_path => path), &block)
104
+ end
105
+
106
+ # Make a POST request and return a collection of resources
107
+ def post_collection(path, attrs={})
108
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
109
+ post_raw(path, attrs) do |parsed_data|
110
+ new_collection(parsed_data)
111
+ end
112
+ end
113
+
114
+ # Make a POST request and return a collection of resources
115
+ def post_resource(path, attrs={})
116
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
117
+ post_raw(path, attrs) do |parsed_data|
118
+ new(parse(parsed_data[:data]))
119
+ end
120
+ end
121
+
122
+ # Make a PUT request and return either a collection or a resource
123
+ def put(path, attrs={})
124
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
125
+ put_raw(path, attrs) do |parsed_data|
126
+ if parsed_data[:data].is_a?(Array)
127
+ new_collection(parsed_data)
128
+ else
129
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
130
+ end
131
+ end
132
+ end
133
+
134
+ # Make a PUT request and return the parsed JSON response (not mapped to objects)
135
+ def put_raw(path, attrs={}, &block)
136
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
137
+ request(attrs.merge(:_method => :put, :_path => path), &block)
138
+ end
139
+
140
+ # Make a PUT request and return a collection of resources
141
+ def put_collection(path, attrs={})
142
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
143
+ put_raw(path, attrs) do |parsed_data|
144
+ new_collection(parsed_data)
145
+ end
146
+ end
147
+
148
+ # Make a PUT request and return a collection of resources
149
+ def put_resource(path, attrs={})
150
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
151
+ put_raw(path, attrs) do |parsed_data|
152
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
153
+ end
154
+ end
155
+
156
+ # Make a PATCH request and return either a collection or a resource
157
+ def patch(path, attrs={})
158
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
159
+ patch_raw(path, attrs) do |parsed_data|
160
+ if parsed_data[:data].is_a?(Array)
161
+ new_collection(parsed_data)
162
+ else
163
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
164
+ end
165
+ end
166
+ end
167
+
168
+ # Make a PATCH request and return the parsed JSON response (not mapped to objects)
169
+ def patch_raw(path, attrs={}, &block)
170
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
171
+ request(attrs.merge(:_method => :patch, :_path => path), &block)
172
+ end
173
+
174
+ # Make a PATCH request and return a collection of resources
175
+ def patch_collection(path, attrs={})
176
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
177
+ patch_raw(path, attrs) do |parsed_data|
178
+ new_collection(parsed_data)
179
+ end
180
+ end
181
+
182
+ # Make a PATCH request and return a collection of resources
183
+ def patch_resource(path, attrs={})
184
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
185
+ patch_raw(path, attrs) do |parsed_data|
186
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
187
+ end
188
+ end
189
+
190
+ # Make a DELETE request and return either a collection or a resource
191
+ def delete(path, attrs={})
192
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
193
+ delete_raw(path, attrs) do |parsed_data|
194
+ if parsed_data[:data].is_a?(Array)
195
+ new_collection(parsed_data)
196
+ else
197
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
198
+ end
199
+ end
200
+ end
201
+
202
+ # Make a DELETE request and return the parsed JSON response (not mapped to objects)
203
+ def delete_raw(path, attrs={}, &block)
204
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
205
+ request(attrs.merge(:_method => :delete, :_path => path), &block)
206
+ end
207
+
208
+ # Make a DELETE request and return a collection of resources
209
+ def delete_collection(path, attrs={})
210
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
211
+ delete_raw(path, attrs) do |parsed_data|
212
+ new_collection(parsed_data)
213
+ end
214
+ end
215
+
216
+ # Make a DELETE request and return a collection of resources
217
+ def delete_resource(path, attrs={})
218
+ path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
219
+ delete_raw(path, attrs) do |parsed_data|
220
+ new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
221
+ end
222
+ end
223
+
224
+ # Define custom GET requests
225
+ #
226
+ # @example
227
+ # class User
228
+ # include Her::Model
229
+ # custom_get :popular
230
+ # end
231
+ #
232
+ # User.popular
233
+ # # Fetched from GET "/users/popular"
234
+ def custom_get(*paths)
235
+ metaclass = (class << self; self; end)
236
+ paths.each do |path|
237
+ metaclass.send(:define_method, path.to_sym) do |*attrs|
238
+ get(path, attrs.first || Hash.new)
239
+ end
240
+ end
241
+ end
242
+
243
+ # Define custom POST requests
244
+ def custom_post(*paths)
245
+ metaclass = (class << self; self; end)
246
+ paths.each do |path|
247
+ metaclass.send(:define_method, path.to_sym) do |*attrs|
248
+ post(path, attrs.first || Hash.new)
249
+ end
250
+ end
251
+ end
252
+
253
+ # Define custom PUT requests
254
+ def custom_put(*paths)
255
+ metaclass = (class << self; self; end)
256
+ paths.each do |path|
257
+ metaclass.send(:define_method, path.to_sym) do |*attrs|
258
+ put(path, attrs.first || Hash.new)
259
+ end
260
+ end
261
+ end
262
+
263
+ # Define custom PATCH requests
264
+ def custom_patch(*paths)
265
+ metaclass = (class << self; self; end)
266
+ paths.each do |path|
267
+ metaclass.send(:define_method, path.to_sym) do |*attrs|
268
+ patch(path, attrs.first || Hash.new)
269
+ end
270
+ end
271
+ end
272
+
273
+ # Define custom DELETE requests
274
+ def custom_delete(*paths)
275
+ metaclass = (class << self; self; end)
276
+ paths.each do |path|
277
+ metaclass.send(:define_method, path.to_sym) do |*attrs|
278
+ delete(path, attrs.first || Hash.new)
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end