activeresource-five 5.0.0
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.
- checksums.yaml +7 -0
- data/README.rdoc +249 -0
- data/lib/active_resource.rb +44 -0
- data/lib/active_resource/associations.rb +175 -0
- data/lib/active_resource/associations/builder/association.rb +32 -0
- data/lib/active_resource/associations/builder/belongs_to.rb +14 -0
- data/lib/active_resource/associations/builder/has_many.rb +12 -0
- data/lib/active_resource/associations/builder/has_one.rb +12 -0
- data/lib/active_resource/base.rb +1626 -0
- data/lib/active_resource/callbacks.rb +20 -0
- data/lib/active_resource/collection.rb +92 -0
- data/lib/active_resource/connection.rb +299 -0
- data/lib/active_resource/custom_methods.rb +127 -0
- data/lib/active_resource/exceptions.rb +82 -0
- data/lib/active_resource/formats.rb +22 -0
- data/lib/active_resource/formats/json_format.rb +25 -0
- data/lib/active_resource/formats/xml_format.rb +25 -0
- data/lib/active_resource/http_mock.rb +375 -0
- data/lib/active_resource/log_subscriber.rb +15 -0
- data/lib/active_resource/observing.rb +0 -0
- data/lib/active_resource/railtie.rb +15 -0
- data/lib/active_resource/reflection.rb +77 -0
- data/lib/active_resource/schema.rb +57 -0
- data/lib/active_resource/singleton.rb +114 -0
- data/lib/active_resource/threadsafe_attributes.rb +65 -0
- data/lib/active_resource/validations.rb +174 -0
- data/lib/active_resource/version.rb +10 -0
- data/lib/activeresource.rb +1 -0
- metadata +156 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
3
|
+
module ActiveResource
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
CALLBACKS = [
|
8
|
+
:before_validation, :after_validation, :before_save, :around_save, :after_save,
|
9
|
+
:before_create, :around_create, :after_create, :before_update, :around_update,
|
10
|
+
:after_update, :before_destroy, :around_destroy, :after_destroy
|
11
|
+
]
|
12
|
+
|
13
|
+
included do
|
14
|
+
extend ActiveModel::Callbacks
|
15
|
+
include ActiveModel::Validations::Callbacks
|
16
|
+
|
17
|
+
define_model_callbacks :save, :create, :update, :destroy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
module ActiveResource # :nodoc:
|
5
|
+
class Collection # :nodoc:
|
6
|
+
SELF_DEFINE_METHODS = [:to_a, :collect!, :map!]
|
7
|
+
include Enumerable
|
8
|
+
delegate :to_yaml, :all?, *(Array.instance_methods(false) - SELF_DEFINE_METHODS), :to => :to_a
|
9
|
+
|
10
|
+
# The array of actual elements returned by index actions
|
11
|
+
attr_accessor :elements, :resource_class, :original_params
|
12
|
+
|
13
|
+
# ActiveResource::Collection is a wrapper to handle parsing index responses that
|
14
|
+
# do not directly map to Rails conventions.
|
15
|
+
#
|
16
|
+
# You can define a custom class that inherets from ActiveResource::Collection
|
17
|
+
# in order to to set the elements instance.
|
18
|
+
#
|
19
|
+
# GET /posts.json delivers following response body:
|
20
|
+
# {
|
21
|
+
# posts: [
|
22
|
+
# {
|
23
|
+
# title: "ActiveResource now has associations",
|
24
|
+
# body: "Lorem Ipsum"
|
25
|
+
# },
|
26
|
+
# {...}
|
27
|
+
# ],
|
28
|
+
# next_page: "/posts.json?page=2"
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
# A Post class can be setup to handle it with:
|
32
|
+
#
|
33
|
+
# class Post < ActiveResource::Base
|
34
|
+
# self.site = "http://example.com"
|
35
|
+
# self.collection_parser = PostCollection
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# And the collection parser:
|
39
|
+
#
|
40
|
+
# class PostCollection < ActiveResource::Collection
|
41
|
+
# attr_accessor :next_page
|
42
|
+
# def initialize(parsed = {})
|
43
|
+
# @elements = parsed['posts']
|
44
|
+
# @next_page = parsed['next_page']
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# The result from a find method that returns multiple entries will now be a
|
49
|
+
# PostParser instance. ActiveResource::Collection includes Enumerable and
|
50
|
+
# instances can be iterated over just like an array.
|
51
|
+
# @posts = Post.find(:all) # => PostCollection:xxx
|
52
|
+
# @posts.next_page # => "/posts.json?page=2"
|
53
|
+
# @posts.map(&:id) # =>[1, 3, 5 ...]
|
54
|
+
#
|
55
|
+
# The initialize method will receive the ActiveResource::Formats parsed result
|
56
|
+
# and should set @elements.
|
57
|
+
def initialize(elements = [])
|
58
|
+
@elements = elements
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_a
|
62
|
+
elements
|
63
|
+
end
|
64
|
+
|
65
|
+
def collect!
|
66
|
+
return elements unless block_given?
|
67
|
+
set = []
|
68
|
+
each { |o| set << yield(o) }
|
69
|
+
@elements = set
|
70
|
+
self
|
71
|
+
end
|
72
|
+
alias map! collect!
|
73
|
+
|
74
|
+
def first_or_create(attributes = {})
|
75
|
+
first || resource_class.create(original_params.update(attributes))
|
76
|
+
rescue NoMethodError
|
77
|
+
raise "Cannot create resource from resource type: #{resource_class.inspect}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def first_or_initialize(attributes = {})
|
81
|
+
first || resource_class.new(original_params.update(attributes))
|
82
|
+
rescue NoMethodError
|
83
|
+
raise "Cannot build resource from resource type: #{resource_class.inspect}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def where(clauses = {})
|
87
|
+
raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
|
88
|
+
new_clauses = original_params.merge(clauses)
|
89
|
+
resource_class.where(new_clauses)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'active_support/core_ext/benchmark'
|
2
|
+
require 'active_support/core_ext/uri'
|
3
|
+
require 'active_support/core_ext/object/inclusion'
|
4
|
+
require 'net/https'
|
5
|
+
require 'date'
|
6
|
+
require 'time'
|
7
|
+
require 'uri'
|
8
|
+
|
9
|
+
module ActiveResource
|
10
|
+
# Class to handle connections to remote web services.
|
11
|
+
# This class is used by ActiveResource::Base to interface with REST
|
12
|
+
# services.
|
13
|
+
class Connection
|
14
|
+
|
15
|
+
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
|
16
|
+
:put => 'Content-Type',
|
17
|
+
:post => 'Content-Type',
|
18
|
+
:patch => 'Content-Type',
|
19
|
+
:delete => 'Accept',
|
20
|
+
:head => 'Accept'
|
21
|
+
}
|
22
|
+
|
23
|
+
attr_reader :site, :user, :password, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options
|
24
|
+
attr_accessor :format
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def requests
|
28
|
+
@@requests ||= []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# The +site+ parameter is required and will set the +site+
|
33
|
+
# attribute to the URI for the remote resource service.
|
34
|
+
def initialize(site, format = ActiveResource::Formats::JsonFormat)
|
35
|
+
raise ArgumentError, 'Missing site URI' unless site
|
36
|
+
@proxy = @user = @password = nil
|
37
|
+
self.site = site
|
38
|
+
self.format = format
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set URI for remote service.
|
42
|
+
def site=(site)
|
43
|
+
@site = site.is_a?(URI) ? site : URI.parse(site)
|
44
|
+
@ssl_options ||= {} if @site.is_a?(URI::HTTPS)
|
45
|
+
@user = URI.parser.unescape(@site.user) if @site.user
|
46
|
+
@password = URI.parser.unescape(@site.password) if @site.password
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set the proxy for remote service.
|
50
|
+
def proxy=(proxy)
|
51
|
+
@proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Sets the user for remote service.
|
55
|
+
def user=(user)
|
56
|
+
@user = user
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sets the password for remote service.
|
60
|
+
def password=(password)
|
61
|
+
@password = password
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sets the auth type for remote service.
|
65
|
+
def auth_type=(auth_type)
|
66
|
+
@auth_type = legitimize_auth_type(auth_type)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the number of seconds after which HTTP requests to the remote service should time out.
|
70
|
+
def timeout=(timeout)
|
71
|
+
@timeout = timeout
|
72
|
+
end
|
73
|
+
|
74
|
+
# Sets the number of seconds after which HTTP connects to the remote service should time out.
|
75
|
+
def open_timeout=(timeout)
|
76
|
+
@open_timeout = timeout
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sets the number of seconds after which HTTP read requests to the remote service should time out.
|
80
|
+
def read_timeout=(timeout)
|
81
|
+
@read_timeout = timeout
|
82
|
+
end
|
83
|
+
|
84
|
+
# Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
|
85
|
+
def ssl_options=(options)
|
86
|
+
@ssl_options = options
|
87
|
+
end
|
88
|
+
|
89
|
+
# Executes a GET request.
|
90
|
+
# Used to get (find) resources.
|
91
|
+
def get(path, headers = {})
|
92
|
+
with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
|
96
|
+
# Used to delete resources.
|
97
|
+
def delete(path, headers = {})
|
98
|
+
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
|
102
|
+
# Used to update resources.
|
103
|
+
def patch(path, body = '', headers = {})
|
104
|
+
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
|
108
|
+
# Used to update resources.
|
109
|
+
def put(path, body = '', headers = {})
|
110
|
+
with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Executes a POST request.
|
114
|
+
# Used to create new resources.
|
115
|
+
def post(path, body = '', headers = {})
|
116
|
+
with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
|
117
|
+
end
|
118
|
+
|
119
|
+
# Executes a HEAD request.
|
120
|
+
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
|
121
|
+
def head(path, headers = {})
|
122
|
+
with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) }
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
# Makes a request to the remote service.
|
127
|
+
def request(method, path, *arguments)
|
128
|
+
result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
|
129
|
+
payload[:method] = method
|
130
|
+
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
|
131
|
+
payload[:result] = http.send(method, path, *arguments)
|
132
|
+
end
|
133
|
+
handle_response(result)
|
134
|
+
rescue Timeout::Error => e
|
135
|
+
raise TimeoutError.new(e.message)
|
136
|
+
rescue OpenSSL::SSL::SSLError => e
|
137
|
+
raise SSLError.new(e.message)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Handles response and error codes from the remote service.
|
141
|
+
def handle_response(response)
|
142
|
+
case response.code.to_i
|
143
|
+
when 301, 302, 303, 307
|
144
|
+
raise(Redirection.new(response))
|
145
|
+
when 200...400
|
146
|
+
response
|
147
|
+
when 400
|
148
|
+
raise(BadRequest.new(response))
|
149
|
+
when 401
|
150
|
+
raise(UnauthorizedAccess.new(response))
|
151
|
+
when 403
|
152
|
+
raise(ForbiddenAccess.new(response))
|
153
|
+
when 404
|
154
|
+
raise(ResourceNotFound.new(response))
|
155
|
+
when 405
|
156
|
+
raise(MethodNotAllowed.new(response))
|
157
|
+
when 409
|
158
|
+
raise(ResourceConflict.new(response))
|
159
|
+
when 410
|
160
|
+
raise(ResourceGone.new(response))
|
161
|
+
when 422
|
162
|
+
raise(ResourceInvalid.new(response))
|
163
|
+
when 401...500
|
164
|
+
raise(ClientError.new(response))
|
165
|
+
when 500...600
|
166
|
+
raise(ServerError.new(response))
|
167
|
+
else
|
168
|
+
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Creates new Net::HTTP instance for communication with the
|
173
|
+
# remote service and resources.
|
174
|
+
def http
|
175
|
+
configure_http(new_http)
|
176
|
+
end
|
177
|
+
|
178
|
+
def new_http
|
179
|
+
if @proxy
|
180
|
+
user = URI.parser.unescape(@proxy.user) if @proxy.user
|
181
|
+
password = URI.parser.unescape(@proxy.password) if @proxy.password
|
182
|
+
Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, user, password)
|
183
|
+
else
|
184
|
+
Net::HTTP.new(@site.host, @site.port)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def configure_http(http)
|
189
|
+
apply_ssl_options(http).tap do |https|
|
190
|
+
# Net::HTTP timeouts default to 60 seconds.
|
191
|
+
if defined? @timeout
|
192
|
+
https.open_timeout = @timeout
|
193
|
+
https.read_timeout = @timeout
|
194
|
+
end
|
195
|
+
https.open_timeout = @open_timeout if defined?(@open_timeout)
|
196
|
+
https.read_timeout = @read_timeout if defined?(@read_timeout)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def apply_ssl_options(http)
|
201
|
+
http.tap do |https|
|
202
|
+
# Skip config if site is already a https:// URI.
|
203
|
+
if defined? @ssl_options
|
204
|
+
http.use_ssl = true
|
205
|
+
|
206
|
+
# All the SSL options have corresponding http settings.
|
207
|
+
@ssl_options.each { |key, value| http.send "#{key}=", value }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def default_header
|
213
|
+
@default_header ||= {}
|
214
|
+
end
|
215
|
+
|
216
|
+
# Builds headers for request to remote service.
|
217
|
+
def build_request_headers(headers, http_method, uri)
|
218
|
+
authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers)
|
219
|
+
end
|
220
|
+
|
221
|
+
def response_auth_header
|
222
|
+
@response_auth_header ||= ""
|
223
|
+
end
|
224
|
+
|
225
|
+
def with_auth
|
226
|
+
retried ||= false
|
227
|
+
yield
|
228
|
+
rescue UnauthorizedAccess => e
|
229
|
+
raise if retried || auth_type != :digest
|
230
|
+
@response_auth_header = e.response['WWW-Authenticate']
|
231
|
+
retried = true
|
232
|
+
retry
|
233
|
+
end
|
234
|
+
|
235
|
+
def authorization_header(http_method, uri)
|
236
|
+
if @user || @password
|
237
|
+
if auth_type == :digest
|
238
|
+
{ 'Authorization' => digest_auth_header(http_method, uri) }
|
239
|
+
else
|
240
|
+
{ 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") }
|
241
|
+
end
|
242
|
+
else
|
243
|
+
{}
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def digest_auth_header(http_method, uri)
|
248
|
+
params = extract_params_from_response
|
249
|
+
|
250
|
+
request_uri = uri.path
|
251
|
+
request_uri << "?#{uri.query}" if uri.query
|
252
|
+
|
253
|
+
ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}")
|
254
|
+
ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}")
|
255
|
+
|
256
|
+
params.merge!('cnonce' => client_nonce)
|
257
|
+
request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":"))
|
258
|
+
"Digest #{auth_attributes_for(uri, request_digest, params)}"
|
259
|
+
end
|
260
|
+
|
261
|
+
def client_nonce
|
262
|
+
Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
|
263
|
+
end
|
264
|
+
|
265
|
+
def extract_params_from_response
|
266
|
+
params = {}
|
267
|
+
if response_auth_header =~ /^(\w+) (.*)/
|
268
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
269
|
+
end
|
270
|
+
params
|
271
|
+
end
|
272
|
+
|
273
|
+
def auth_attributes_for(uri, request_digest, params)
|
274
|
+
auth_attrs =
|
275
|
+
[
|
276
|
+
%Q(username="#{@user}"),
|
277
|
+
%Q(realm="#{params['realm']}"),
|
278
|
+
%Q(qop="#{params['qop']}"),
|
279
|
+
%Q(uri="#{uri.path}"),
|
280
|
+
%Q(nonce="#{params['nonce']}"),
|
281
|
+
%Q(nc="0"),
|
282
|
+
%Q(cnonce="#{params['cnonce']}"),
|
283
|
+
%Q(response="#{request_digest}")]
|
284
|
+
|
285
|
+
auth_attrs << %Q(opaque="#{params['opaque']}") unless params['opaque'].blank?
|
286
|
+
auth_attrs.join(", ")
|
287
|
+
end
|
288
|
+
|
289
|
+
def http_format_header(http_method)
|
290
|
+
{HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
|
291
|
+
end
|
292
|
+
|
293
|
+
def legitimize_auth_type(auth_type)
|
294
|
+
return :basic if auth_type.nil?
|
295
|
+
auth_type = auth_type.to_sym
|
296
|
+
auth_type.in?([:basic, :digest]) ? auth_type : :basic
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module ActiveResource
|
4
|
+
# A module to support custom REST methods and sub-resources, allowing you to break out
|
5
|
+
# of the "default" REST methods with your own custom resource requests. For example,
|
6
|
+
# say you use Rails to expose a REST service and configure your routes with:
|
7
|
+
#
|
8
|
+
# map.resources :people, :new => { :register => :post },
|
9
|
+
# :member => { :promote => :put, :deactivate => :delete }
|
10
|
+
# :collection => { :active => :get }
|
11
|
+
#
|
12
|
+
# This route set creates routes for the following HTTP requests:
|
13
|
+
#
|
14
|
+
# POST /people/new/register.json # PeopleController.register
|
15
|
+
# PATCH/PUT /people/1/promote.json # PeopleController.promote with :id => 1
|
16
|
+
# DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
|
17
|
+
# GET /people/active.json # PeopleController.active
|
18
|
+
#
|
19
|
+
# Using this module, Active Resource can use these custom REST methods just like the
|
20
|
+
# standard methods.
|
21
|
+
#
|
22
|
+
# class Person < ActiveResource::Base
|
23
|
+
# self.site = "https://37s.sunrise.com"
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
|
27
|
+
# # => { :id => 1, :name => 'Ryan' }
|
28
|
+
#
|
29
|
+
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.json
|
30
|
+
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.json
|
31
|
+
#
|
32
|
+
# Person.get(:active) # GET /people/active.json
|
33
|
+
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
34
|
+
#
|
35
|
+
module CustomMethods
|
36
|
+
extend ActiveSupport::Concern
|
37
|
+
|
38
|
+
included do
|
39
|
+
class << self
|
40
|
+
alias :orig_delete :delete
|
41
|
+
|
42
|
+
# Invokes a GET to a given custom REST method. For example:
|
43
|
+
#
|
44
|
+
# Person.get(:active) # GET /people/active.json
|
45
|
+
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
46
|
+
#
|
47
|
+
# Person.get(:active, :awesome => true) # GET /people/active.json?awesome=true
|
48
|
+
# # => [{:id => 1, :name => 'Ryan'}]
|
49
|
+
#
|
50
|
+
# Note: the objects returned from this method are not automatically converted
|
51
|
+
# into ActiveResource::Base instances - they are ordinary Hashes. If you are expecting
|
52
|
+
# ActiveResource::Base instances, use the <tt>find</tt> class method with the
|
53
|
+
# <tt>:from</tt> option. For example:
|
54
|
+
#
|
55
|
+
# Person.find(:all, :from => :active)
|
56
|
+
def get(custom_method_name, options = {})
|
57
|
+
hashified = format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body)
|
58
|
+
derooted = Formats.remove_root(hashified)
|
59
|
+
derooted.is_a?(Array) ? derooted.map { |e| Formats.remove_root(e) } : derooted
|
60
|
+
end
|
61
|
+
|
62
|
+
def post(custom_method_name, options = {}, body = '')
|
63
|
+
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
|
64
|
+
end
|
65
|
+
|
66
|
+
def patch(custom_method_name, options = {}, body = '')
|
67
|
+
connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
|
68
|
+
end
|
69
|
+
|
70
|
+
def put(custom_method_name, options = {}, body = '')
|
71
|
+
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete(custom_method_name, options = {})
|
75
|
+
# Need to jump through some hoops to retain the original class 'delete' method
|
76
|
+
if custom_method_name.is_a?(Symbol)
|
77
|
+
connection.delete(custom_method_collection_url(custom_method_name, options), headers)
|
78
|
+
else
|
79
|
+
orig_delete(custom_method_name, options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module ClassMethods
|
86
|
+
def custom_method_collection_url(method_name, options = {})
|
87
|
+
prefix_options, query_options = split_options(options)
|
88
|
+
"#{prefix(prefix_options)}#{collection_name}/#{method_name}#{format_extension}#{query_string(query_options)}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get(method_name, options = {})
|
93
|
+
self.class.format.decode(connection.get(custom_method_element_url(method_name, options), self.class.headers).body)
|
94
|
+
end
|
95
|
+
|
96
|
+
def post(method_name, options = {}, body = nil)
|
97
|
+
request_body = body.blank? ? encode : body
|
98
|
+
if new?
|
99
|
+
connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
|
100
|
+
else
|
101
|
+
connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def patch(method_name, options = {}, body = '')
|
106
|
+
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
|
107
|
+
end
|
108
|
+
|
109
|
+
def put(method_name, options = {}, body = '')
|
110
|
+
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete(method_name, options = {})
|
114
|
+
connection.delete(custom_method_element_url(method_name, options), self.class.headers)
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
private
|
119
|
+
def custom_method_element_url(method_name, options = {})
|
120
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def custom_method_new_element_url(method_name, options = {})
|
124
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|