extended_her 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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