activeresource 5.0.0 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeresource might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/MIT-LICENSE +20 -0
- data/README.rdoc +48 -10
- data/lib/active_resource.rb +7 -5
- data/lib/active_resource/active_job_serializer.rb +26 -0
- data/lib/active_resource/associations.rb +10 -10
- data/lib/active_resource/associations/builder/association.rb +5 -4
- data/lib/active_resource/associations/builder/belongs_to.rb +4 -2
- data/lib/active_resource/associations/builder/has_many.rb +3 -1
- data/lib/active_resource/associations/builder/has_one.rb +4 -2
- data/lib/active_resource/base.rb +141 -76
- data/lib/active_resource/callbacks.rb +3 -1
- data/lib/active_resource/collection.rb +5 -3
- data/lib/active_resource/connection.rb +70 -75
- data/lib/active_resource/custom_methods.rb +8 -6
- data/lib/active_resource/exceptions.rb +5 -3
- data/lib/active_resource/formats.rb +4 -2
- data/lib/active_resource/formats/json_format.rb +4 -1
- data/lib/active_resource/formats/xml_format.rb +4 -2
- data/lib/active_resource/http_mock.rb +18 -20
- data/lib/active_resource/log_subscriber.rb +14 -3
- data/lib/active_resource/railtie.rb +13 -3
- data/lib/active_resource/reflection.rb +10 -9
- data/lib/active_resource/schema.rb +5 -3
- data/lib/active_resource/singleton.rb +25 -26
- data/lib/active_resource/threadsafe_attributes.rb +30 -29
- data/lib/active_resource/validations.rb +12 -10
- data/lib/active_resource/version.rb +4 -2
- data/lib/activeresource.rb +3 -1
- metadata +9 -7
@@ -1,11 +1,13 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "active_support/inflector"
|
3
5
|
|
4
6
|
module ActiveResource # :nodoc:
|
5
7
|
class Collection # :nodoc:
|
6
8
|
SELF_DEFINE_METHODS = [:to_a, :collect!, :map!]
|
7
9
|
include Enumerable
|
8
|
-
delegate :to_yaml, :all?, *(Array.instance_methods(false) - SELF_DEFINE_METHODS), :
|
10
|
+
delegate :to_yaml, :all?, *(Array.instance_methods(false) - SELF_DEFINE_METHODS), to: :to_a
|
9
11
|
|
10
12
|
# The array of actual elements returned by index actions
|
11
13
|
attr_accessor :elements, :resource_class, :original_params
|
@@ -1,27 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/benchmark"
|
4
|
+
require "active_support/core_ext/uri"
|
5
|
+
require "active_support/core_ext/object/inclusion"
|
6
|
+
require "net/https"
|
7
|
+
require "date"
|
8
|
+
require "time"
|
9
|
+
require "uri"
|
8
10
|
|
9
11
|
module ActiveResource
|
10
12
|
# Class to handle connections to remote web services.
|
11
13
|
# This class is used by ActiveResource::Base to interface with REST
|
12
14
|
# services.
|
13
15
|
class Connection
|
14
|
-
|
15
|
-
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:head => 'Accept'
|
16
|
+
HTTP_FORMAT_HEADER_NAMES = { get: "Accept",
|
17
|
+
put: "Content-Type",
|
18
|
+
post: "Content-Type",
|
19
|
+
patch: "Content-Type",
|
20
|
+
delete: "Accept",
|
21
|
+
head: "Accept"
|
21
22
|
}
|
22
23
|
|
23
|
-
attr_reader :site, :user, :password, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options
|
24
|
-
attr_accessor :format
|
24
|
+
attr_reader :site, :user, :password, :bearer_token, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options
|
25
|
+
attr_accessor :format, :logger
|
25
26
|
|
26
27
|
class << self
|
27
28
|
def requests
|
@@ -31,11 +32,12 @@ module ActiveResource
|
|
31
32
|
|
32
33
|
# The +site+ parameter is required and will set the +site+
|
33
34
|
# attribute to the URI for the remote resource service.
|
34
|
-
def initialize(site, format = ActiveResource::Formats::JsonFormat)
|
35
|
-
raise ArgumentError,
|
36
|
-
@proxy = @user = @password = nil
|
35
|
+
def initialize(site, format = ActiveResource::Formats::JsonFormat, logger: nil)
|
36
|
+
raise ArgumentError, "Missing site URI" unless site
|
37
|
+
@proxy = @user = @password = @bearer_token = nil
|
37
38
|
self.site = site
|
38
39
|
self.format = format
|
40
|
+
self.logger = logger
|
39
41
|
end
|
40
42
|
|
41
43
|
# Set URI for remote service.
|
@@ -52,14 +54,13 @@ module ActiveResource
|
|
52
54
|
end
|
53
55
|
|
54
56
|
# Sets the user for remote service.
|
55
|
-
|
56
|
-
@user = user
|
57
|
-
end
|
57
|
+
attr_writer :user
|
58
58
|
|
59
59
|
# Sets the password for remote service.
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
attr_writer :password
|
61
|
+
|
62
|
+
# Sets the bearer token for remote service.
|
63
|
+
attr_writer :bearer_token
|
63
64
|
|
64
65
|
# Sets the auth type for remote service.
|
65
66
|
def auth_type=(auth_type)
|
@@ -67,24 +68,16 @@ module ActiveResource
|
|
67
68
|
end
|
68
69
|
|
69
70
|
# Sets the number of seconds after which HTTP requests to the remote service should time out.
|
70
|
-
|
71
|
-
@timeout = timeout
|
72
|
-
end
|
71
|
+
attr_writer :timeout
|
73
72
|
|
74
73
|
# Sets the number of seconds after which HTTP connects to the remote service should time out.
|
75
|
-
|
76
|
-
@open_timeout = timeout
|
77
|
-
end
|
74
|
+
attr_writer :open_timeout
|
78
75
|
|
79
76
|
# Sets the number of seconds after which HTTP read requests to the remote service should time out.
|
80
|
-
|
81
|
-
@read_timeout = timeout
|
82
|
-
end
|
77
|
+
attr_writer :read_timeout
|
83
78
|
|
84
79
|
# Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
|
85
|
-
|
86
|
-
@ssl_options = options
|
87
|
-
end
|
80
|
+
attr_writer :ssl_options
|
88
81
|
|
89
82
|
# Executes a GET request.
|
90
83
|
# Used to get (find) resources.
|
@@ -100,19 +93,19 @@ module ActiveResource
|
|
100
93
|
|
101
94
|
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
|
102
95
|
# Used to update resources.
|
103
|
-
def patch(path, body =
|
96
|
+
def patch(path, body = "", headers = {})
|
104
97
|
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
|
105
98
|
end
|
106
99
|
|
107
100
|
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
|
108
101
|
# Used to update resources.
|
109
|
-
def put(path, body =
|
102
|
+
def put(path, body = "", headers = {})
|
110
103
|
with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
|
111
104
|
end
|
112
105
|
|
113
106
|
# Executes a POST request.
|
114
107
|
# Used to create new resources.
|
115
|
-
def post(path, body =
|
108
|
+
def post(path, body = "", headers = {})
|
116
109
|
with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
|
117
110
|
end
|
118
111
|
|
@@ -140,32 +133,32 @@ module ActiveResource
|
|
140
133
|
# Handles response and error codes from the remote service.
|
141
134
|
def handle_response(response)
|
142
135
|
case response.code.to_i
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
136
|
+
when 301, 302, 303, 307
|
137
|
+
raise(Redirection.new(response))
|
138
|
+
when 200...400
|
139
|
+
response
|
140
|
+
when 400
|
141
|
+
raise(BadRequest.new(response))
|
142
|
+
when 401
|
143
|
+
raise(UnauthorizedAccess.new(response))
|
144
|
+
when 403
|
145
|
+
raise(ForbiddenAccess.new(response))
|
146
|
+
when 404
|
147
|
+
raise(ResourceNotFound.new(response))
|
148
|
+
when 405
|
149
|
+
raise(MethodNotAllowed.new(response))
|
150
|
+
when 409
|
151
|
+
raise(ResourceConflict.new(response))
|
152
|
+
when 410
|
153
|
+
raise(ResourceGone.new(response))
|
154
|
+
when 422
|
155
|
+
raise(ResourceInvalid.new(response))
|
156
|
+
when 401...500
|
157
|
+
raise(ClientError.new(response))
|
158
|
+
when 500...600
|
159
|
+
raise(ServerError.new(response))
|
160
|
+
else
|
161
|
+
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
169
162
|
end
|
170
163
|
end
|
171
164
|
|
@@ -227,7 +220,7 @@ module ActiveResource
|
|
227
220
|
yield
|
228
221
|
rescue UnauthorizedAccess => e
|
229
222
|
raise if retried || auth_type != :digest
|
230
|
-
@response_auth_header = e.response[
|
223
|
+
@response_auth_header = e.response["WWW-Authenticate"]
|
231
224
|
retried = true
|
232
225
|
retry
|
233
226
|
end
|
@@ -235,10 +228,12 @@ module ActiveResource
|
|
235
228
|
def authorization_header(http_method, uri)
|
236
229
|
if @user || @password
|
237
230
|
if auth_type == :digest
|
238
|
-
{
|
231
|
+
{ "Authorization" => digest_auth_header(http_method, uri) }
|
239
232
|
else
|
240
|
-
{
|
233
|
+
{ "Authorization" => "Basic " + ["#{@user}:#{@password}"].pack("m").delete("\r\n") }
|
241
234
|
end
|
235
|
+
elsif @bearer_token
|
236
|
+
{ "Authorization" => "Bearer #{@bearer_token}" }
|
242
237
|
else
|
243
238
|
{}
|
244
239
|
end
|
@@ -253,8 +248,8 @@ module ActiveResource
|
|
253
248
|
ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}")
|
254
249
|
ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}")
|
255
250
|
|
256
|
-
params
|
257
|
-
request_digest = Digest::MD5.hexdigest([ha1, params[
|
251
|
+
params["cnonce"] = client_nonce
|
252
|
+
request_digest = Digest::MD5.hexdigest([ha1, params["nonce"], "0", params["cnonce"], params["qop"], ha2].join(":"))
|
258
253
|
"Digest #{auth_attributes_for(uri, request_digest, params)}"
|
259
254
|
end
|
260
255
|
|
@@ -278,22 +273,22 @@ module ActiveResource
|
|
278
273
|
%Q(qop="#{params['qop']}"),
|
279
274
|
%Q(uri="#{uri.path}"),
|
280
275
|
%Q(nonce="#{params['nonce']}"),
|
281
|
-
|
276
|
+
'nc="0"',
|
282
277
|
%Q(cnonce="#{params['cnonce']}"),
|
283
278
|
%Q(response="#{request_digest}")]
|
284
279
|
|
285
|
-
auth_attrs << %Q(opaque="#{params['opaque']}") unless params[
|
280
|
+
auth_attrs << %Q(opaque="#{params['opaque']}") unless params["opaque"].blank?
|
286
281
|
auth_attrs.join(", ")
|
287
282
|
end
|
288
283
|
|
289
284
|
def http_format_header(http_method)
|
290
|
-
{HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
|
285
|
+
{ HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type }
|
291
286
|
end
|
292
287
|
|
293
288
|
def legitimize_auth_type(auth_type)
|
294
289
|
return :basic if auth_type.nil?
|
295
290
|
auth_type = auth_type.to_sym
|
296
|
-
auth_type.in?([:basic, :digest]) ? auth_type : :basic
|
291
|
+
auth_type.in?([:basic, :digest, :bearer]) ? auth_type : :basic
|
297
292
|
end
|
298
293
|
end
|
299
294
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
2
4
|
|
3
5
|
module ActiveResource
|
4
6
|
# A module to support custom REST methods and sub-resources, allowing you to break out
|
@@ -59,15 +61,15 @@ module ActiveResource
|
|
59
61
|
derooted.is_a?(Array) ? derooted.map { |e| Formats.remove_root(e) } : derooted
|
60
62
|
end
|
61
63
|
|
62
|
-
def post(custom_method_name, options = {}, body =
|
64
|
+
def post(custom_method_name, options = {}, body = "")
|
63
65
|
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
|
64
66
|
end
|
65
67
|
|
66
|
-
def patch(custom_method_name, options = {}, body =
|
68
|
+
def patch(custom_method_name, options = {}, body = "")
|
67
69
|
connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
|
68
70
|
end
|
69
71
|
|
70
|
-
def put(custom_method_name, options = {}, body =
|
72
|
+
def put(custom_method_name, options = {}, body = "")
|
71
73
|
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
|
72
74
|
end
|
73
75
|
|
@@ -102,11 +104,11 @@ module ActiveResource
|
|
102
104
|
end
|
103
105
|
end
|
104
106
|
|
105
|
-
def patch(method_name, options = {}, body =
|
107
|
+
def patch(method_name, options = {}, body = "")
|
106
108
|
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
|
107
109
|
end
|
108
110
|
|
109
|
-
def put(method_name, options = {}, body =
|
111
|
+
def put(method_name, options = {}, body = "")
|
110
112
|
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
|
111
113
|
end
|
112
114
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveResource
|
2
4
|
class ConnectionError < StandardError # :nodoc:
|
3
5
|
attr_reader :response
|
@@ -8,7 +10,7 @@ module ActiveResource
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def to_s
|
11
|
-
message = "Failed."
|
13
|
+
message = "Failed.".dup
|
12
14
|
message << " Response code = #{response.code}." if response.respond_to?(:code)
|
13
15
|
message << " Response message = #{response.message}." if response.respond_to?(:message)
|
14
16
|
message
|
@@ -34,7 +36,7 @@ module ActiveResource
|
|
34
36
|
# 3xx Redirection
|
35
37
|
class Redirection < ConnectionError # :nodoc:
|
36
38
|
def to_s
|
37
|
-
response[
|
39
|
+
response["Location"] ? "#{super} => #{response['Location']}" : super
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
@@ -76,7 +78,7 @@ module ActiveResource
|
|
76
78
|
# 405 Method Not Allowed
|
77
79
|
class MethodNotAllowed < ClientError # :nodoc:
|
78
80
|
def allowed_methods
|
79
|
-
@response[
|
81
|
+
@response["Allow"].split(",").map { |verb| verb.strip.downcase.to_sym }
|
80
82
|
end
|
81
83
|
end
|
82
84
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveResource
|
2
4
|
module Formats
|
3
|
-
autoload :XmlFormat,
|
4
|
-
autoload :JsonFormat,
|
5
|
+
autoload :XmlFormat, "active_resource/formats/xml_format"
|
6
|
+
autoload :JsonFormat, "active_resource/formats/json_format"
|
5
7
|
|
6
8
|
# Lookup the format class from a mime type reference symbol. Example:
|
7
9
|
#
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/json"
|
2
4
|
|
3
5
|
module ActiveResource
|
4
6
|
module Formats
|
@@ -18,6 +20,7 @@ module ActiveResource
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def decode(json)
|
23
|
+
return nil if json.nil?
|
21
24
|
Formats.remove_root(ActiveSupport::JSON.decode(json))
|
22
25
|
end
|
23
26
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/conversions"
|
2
4
|
|
3
5
|
module ActiveResource
|
4
6
|
module Formats
|
@@ -13,7 +15,7 @@ module ActiveResource
|
|
13
15
|
"application/xml"
|
14
16
|
end
|
15
17
|
|
16
|
-
def encode(hash, options={})
|
18
|
+
def encode(hash, options = {})
|
17
19
|
hash.to_xml(options)
|
18
20
|
end
|
19
21
|
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/kernel/reporting"
|
4
|
+
require "active_support/core_ext/object/inclusion"
|
3
5
|
|
4
6
|
module ActiveResource
|
5
7
|
class InvalidRequestError < StandardError; end #:nodoc:
|
@@ -74,12 +76,11 @@ module ActiveResource
|
|
74
76
|
private
|
75
77
|
|
76
78
|
def delete_duplicate_responses(request)
|
77
|
-
@responses.delete_if {|r| r[0] == request }
|
79
|
+
@responses.delete_if { |r| r[0] == request }
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
81
83
|
class << self
|
82
|
-
|
83
84
|
# Returns an array of all request objects that have been sent to the mock. You can use this to check
|
84
85
|
# if your model actually sent an HTTP request.
|
85
86
|
#
|
@@ -203,9 +204,9 @@ module ActiveResource
|
|
203
204
|
end
|
204
205
|
|
205
206
|
def delete_responses_to_replace(new_responses)
|
206
|
-
new_responses.each{|nr|
|
207
|
+
new_responses.each { |nr|
|
207
208
|
request_to_remove = nr[0]
|
208
|
-
@@responses = responses.delete_if{|r| r[0] == request_to_remove}
|
209
|
+
@@responses = responses.delete_if { |r| r[0] == request_to_remove }
|
209
210
|
}
|
210
211
|
end
|
211
212
|
|
@@ -238,7 +239,6 @@ module ActiveResource
|
|
238
239
|
def net_connection_disabled?
|
239
240
|
!net_connection_enabled?
|
240
241
|
end
|
241
|
-
|
242
242
|
end
|
243
243
|
|
244
244
|
# body? methods
|
@@ -294,15 +294,15 @@ module ActiveResource
|
|
294
294
|
|
295
295
|
private
|
296
296
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
297
|
+
def headers_match?(req)
|
298
|
+
# Ignore format header on equality if it's not defined
|
299
|
+
format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method]
|
300
|
+
if headers[format_header].present? || req.headers[format_header].blank?
|
301
|
+
headers == req.headers
|
302
|
+
else
|
303
|
+
headers.dup.merge(format_header => req.headers[format_header]) == req.headers
|
304
|
+
end
|
304
305
|
end
|
305
|
-
end
|
306
306
|
end
|
307
307
|
|
308
308
|
class Response
|
@@ -310,15 +310,14 @@ module ActiveResource
|
|
310
310
|
|
311
311
|
def initialize(body, message = 200, headers = {})
|
312
312
|
@body, @message, @headers = body, message.to_s, headers
|
313
|
-
@code = @message[0,3].to_i
|
313
|
+
@code = @message[0, 3].to_i
|
314
314
|
|
315
315
|
resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
|
316
316
|
if resp_cls && !resp_cls.body_permitted?
|
317
317
|
@body = nil
|
318
318
|
end
|
319
319
|
|
320
|
-
self[
|
321
|
-
|
320
|
+
self["Content-Length"] = @body.nil? ? "0" : body.size.to_s
|
322
321
|
end
|
323
322
|
|
324
323
|
# Returns true if code is 2xx,
|
@@ -338,7 +337,7 @@ module ActiveResource
|
|
338
337
|
# Returns true if the other is a Response with an equal body, equal message
|
339
338
|
# and equal headers. Otherwise it returns false.
|
340
339
|
def ==(other)
|
341
|
-
if
|
340
|
+
if other.is_a?(Response)
|
342
341
|
other.body == body && other.message == message && other.headers == headers
|
343
342
|
else
|
344
343
|
false
|
@@ -369,7 +368,6 @@ module ActiveResource
|
|
369
368
|
def stub_http?
|
370
369
|
HttpMock.net_connection_disabled? && defined?(@http) && @http.kind_of?(Net::HTTP)
|
371
370
|
end
|
372
|
-
|
373
371
|
end
|
374
372
|
end
|
375
373
|
end
|