activeresource 3.2.22.5 → 5.1.1
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 +5 -5
- data/MIT-LICENSE +2 -2
- data/README.rdoc +147 -49
- data/lib/active_resource.rb +12 -12
- data/lib/active_resource/active_job_serializer.rb +26 -0
- data/lib/active_resource/associations.rb +175 -0
- data/lib/active_resource/associations/builder/association.rb +33 -0
- data/lib/active_resource/associations/builder/belongs_to.rb +16 -0
- data/lib/active_resource/associations/builder/has_many.rb +14 -0
- data/lib/active_resource/associations/builder/has_one.rb +14 -0
- data/lib/active_resource/base.rb +444 -231
- data/lib/active_resource/callbacks.rb +22 -0
- data/lib/active_resource/collection.rb +94 -0
- data/lib/active_resource/connection.rb +112 -105
- data/lib/active_resource/custom_methods.rb +24 -14
- data/lib/active_resource/exceptions.rb +5 -3
- data/lib/active_resource/formats.rb +5 -3
- 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 +69 -31
- data/lib/active_resource/log_subscriber.rb +14 -3
- data/lib/active_resource/observing.rb +0 -29
- data/lib/active_resource/railtie.rb +14 -3
- data/lib/active_resource/reflection.rb +78 -0
- data/lib/active_resource/schema.rb +4 -4
- data/lib/active_resource/singleton.rb +113 -0
- data/lib/active_resource/threadsafe_attributes.rb +66 -0
- data/lib/active_resource/validations.rb +56 -14
- data/lib/active_resource/version.rb +7 -5
- data/lib/activeresource.rb +3 -0
- metadata +78 -16
- data/CHANGELOG.md +0 -437
- data/examples/performance.rb +0 -70
@@ -1,26 +1,28 @@
|
|
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
|
5
|
-
# of the "default" REST methods with your own custom resource requests.
|
7
|
+
# of the "default" REST methods with your own custom resource requests. For example,
|
6
8
|
# say you use Rails to expose a REST service and configure your routes with:
|
7
9
|
#
|
8
10
|
# map.resources :people, :new => { :register => :post },
|
9
11
|
# :member => { :promote => :put, :deactivate => :delete }
|
10
12
|
# :collection => { :active => :get }
|
11
13
|
#
|
12
|
-
#
|
14
|
+
# This route set creates routes for the following HTTP requests:
|
13
15
|
#
|
14
|
-
# POST
|
15
|
-
# PUT
|
16
|
-
# DELETE
|
17
|
-
# GET
|
16
|
+
# POST /people/new/register.json # PeopleController.register
|
17
|
+
# PATCH/PUT /people/1/promote.json # PeopleController.promote with :id => 1
|
18
|
+
# DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
|
19
|
+
# GET /people/active.json # PeopleController.active
|
18
20
|
#
|
19
21
|
# Using this module, Active Resource can use these custom REST methods just like the
|
20
22
|
# standard methods.
|
21
23
|
#
|
22
24
|
# class Person < ActiveResource::Base
|
23
|
-
# self.site = "
|
25
|
+
# self.site = "https://37s.sunrise.com"
|
24
26
|
# end
|
25
27
|
#
|
26
28
|
# Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
|
@@ -59,11 +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
|
68
|
+
def patch(custom_method_name, options = {}, body = "")
|
69
|
+
connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
|
70
|
+
end
|
71
|
+
|
72
|
+
def put(custom_method_name, options = {}, body = "")
|
67
73
|
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
|
68
74
|
end
|
69
75
|
|
@@ -81,7 +87,7 @@ module ActiveResource
|
|
81
87
|
module ClassMethods
|
82
88
|
def custom_method_collection_url(method_name, options = {})
|
83
89
|
prefix_options, query_options = split_options(options)
|
84
|
-
"#{prefix(prefix_options)}#{collection_name}/#{method_name}
|
90
|
+
"#{prefix(prefix_options)}#{collection_name}/#{method_name}#{format_extension}#{query_string(query_options)}"
|
85
91
|
end
|
86
92
|
end
|
87
93
|
|
@@ -98,7 +104,11 @@ module ActiveResource
|
|
98
104
|
end
|
99
105
|
end
|
100
106
|
|
101
|
-
def
|
107
|
+
def patch(method_name, options = {}, body = "")
|
108
|
+
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
|
109
|
+
end
|
110
|
+
|
111
|
+
def put(method_name, options = {}, body = "")
|
102
112
|
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
|
103
113
|
end
|
104
114
|
|
@@ -109,11 +119,11 @@ module ActiveResource
|
|
109
119
|
|
110
120
|
private
|
111
121
|
def custom_method_element_url(method_name, options = {})
|
112
|
-
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}
|
122
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
113
123
|
end
|
114
124
|
|
115
125
|
def custom_method_new_element_url(method_name, options = {})
|
116
|
-
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}
|
126
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
117
127
|
end
|
118
128
|
end
|
119
129
|
end
|
@@ -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
|
#
|
@@ -12,7 +14,7 @@ module ActiveResource
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def self.remove_root(data)
|
15
|
-
if data.is_a?(Hash) && data.keys.size == 1
|
17
|
+
if data.is_a?(Hash) && data.keys.size == 1 && data.values.first.is_a?(Enumerable)
|
16
18
|
data.values.first
|
17
19
|
else
|
18
20
|
data
|
@@ -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,10 +1,12 @@
|
|
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:
|
6
8
|
|
7
|
-
# One thing that has always been a pain with remote web services is testing.
|
9
|
+
# One thing that has always been a pain with remote web services is testing. The HttpMock
|
8
10
|
# class makes it easy to test your Active Resource models by creating a set of mock responses to specific
|
9
11
|
# requests.
|
10
12
|
#
|
@@ -15,17 +17,17 @@ module ActiveResource
|
|
15
17
|
#
|
16
18
|
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
17
19
|
#
|
18
|
-
# * <tt>http_method</tt> - The HTTP method to listen for.
|
20
|
+
# * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +patch+, +put+, +delete+ or
|
19
21
|
# +head+.
|
20
22
|
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
|
21
23
|
# called.
|
22
|
-
# * <tt>request_headers</tt> - Headers that are expected along with the request.
|
23
|
-
# hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>.
|
24
|
+
# * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
|
25
|
+
# hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
|
24
26
|
# if your tests sends a request with identical headers.
|
25
|
-
# * <tt>body</tt> - The data to be returned.
|
27
|
+
# * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
|
26
28
|
# such as Json.
|
27
29
|
# * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
|
28
|
-
# * <tt>response_headers</tt> - Headers to be returned with the response.
|
30
|
+
# * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
|
29
31
|
# <tt>request_headers</tt> listed above.
|
30
32
|
#
|
31
33
|
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
|
@@ -55,7 +57,7 @@ module ActiveResource
|
|
55
57
|
@responses = responses
|
56
58
|
end
|
57
59
|
|
58
|
-
[ :post, :put, :get, :delete, :head ].each do |method|
|
60
|
+
[ :post, :patch, :put, :get, :delete, :head ].each do |method|
|
59
61
|
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
60
62
|
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
|
61
63
|
# end
|
@@ -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
|
#
|
@@ -133,7 +134,7 @@ module ActiveResource
|
|
133
134
|
#
|
134
135
|
# === Example
|
135
136
|
#
|
136
|
-
# Request.new(
|
137
|
+
# Request.new(method, path, nil, request_headers)
|
137
138
|
#
|
138
139
|
# @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
|
139
140
|
#
|
@@ -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
|
|
@@ -214,10 +215,34 @@ module ActiveResource
|
|
214
215
|
requests.clear
|
215
216
|
responses.clear
|
216
217
|
end
|
218
|
+
|
219
|
+
# Enables all ActiveResource::Connection instances to use real
|
220
|
+
# Net::HTTP instance instead of a mock.
|
221
|
+
def enable_net_connection!
|
222
|
+
@@net_connection_enabled = true
|
223
|
+
end
|
224
|
+
|
225
|
+
# Sets all ActiveResource::Connection to use HttpMock instances.
|
226
|
+
def disable_net_connection!
|
227
|
+
@@net_connection_enabled = false
|
228
|
+
end
|
229
|
+
|
230
|
+
# Checks if real requests can be used instead of the default mock used in tests.
|
231
|
+
def net_connection_enabled?
|
232
|
+
if defined?(@@net_connection_enabled)
|
233
|
+
@@net_connection_enabled
|
234
|
+
else
|
235
|
+
@@net_connection_enabled = false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def net_connection_disabled?
|
240
|
+
!net_connection_enabled?
|
241
|
+
end
|
217
242
|
end
|
218
243
|
|
219
244
|
# body? methods
|
220
|
-
{ true => %w(post put),
|
245
|
+
{ true => %w(post patch put),
|
221
246
|
false => %w(get delete head) }.each do |has_body, methods|
|
222
247
|
methods.each do |method|
|
223
248
|
# def post(path, body, headers)
|
@@ -269,15 +294,15 @@ module ActiveResource
|
|
269
294
|
|
270
295
|
private
|
271
296
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
279
305
|
end
|
280
|
-
end
|
281
306
|
end
|
282
307
|
|
283
308
|
class Response
|
@@ -285,18 +310,14 @@ module ActiveResource
|
|
285
310
|
|
286
311
|
def initialize(body, message = 200, headers = {})
|
287
312
|
@body, @message, @headers = body, message.to_s, headers
|
288
|
-
@code = @message[0,3].to_i
|
313
|
+
@code = @message[0, 3].to_i
|
289
314
|
|
290
315
|
resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
|
291
316
|
if resp_cls && !resp_cls.body_permitted?
|
292
317
|
@body = nil
|
293
318
|
end
|
294
319
|
|
295
|
-
|
296
|
-
self['Content-Length'] = "0"
|
297
|
-
else
|
298
|
-
self['Content-Length'] = body.size.to_s
|
299
|
-
end
|
320
|
+
self["Content-Length"] = @body.nil? ? "0" : body.size.to_s
|
300
321
|
end
|
301
322
|
|
302
323
|
# Returns true if code is 2xx,
|
@@ -316,7 +337,7 @@ module ActiveResource
|
|
316
337
|
# Returns true if the other is a Response with an equal body, equal message
|
317
338
|
# and equal headers. Otherwise it returns false.
|
318
339
|
def ==(other)
|
319
|
-
if
|
340
|
+
if other.is_a?(Response)
|
320
341
|
other.body == body && other.message == message && other.headers == headers
|
321
342
|
else
|
322
343
|
false
|
@@ -328,7 +349,24 @@ module ActiveResource
|
|
328
349
|
private
|
329
350
|
silence_warnings do
|
330
351
|
def http
|
331
|
-
|
352
|
+
if unstub_http?
|
353
|
+
@http = configure_http(new_http)
|
354
|
+
elsif stub_http?
|
355
|
+
@http = http_stub
|
356
|
+
end
|
357
|
+
@http ||= http_stub
|
358
|
+
end
|
359
|
+
|
360
|
+
def http_stub
|
361
|
+
HttpMock.new(@site)
|
362
|
+
end
|
363
|
+
|
364
|
+
def unstub_http?
|
365
|
+
HttpMock.net_connection_enabled? && defined?(@http) && @http.kind_of?(HttpMock)
|
366
|
+
end
|
367
|
+
|
368
|
+
def stub_http?
|
369
|
+
HttpMock.net_connection_disabled? && defined?(@http) && @http.kind_of?(Net::HTTP)
|
332
370
|
end
|
333
371
|
end
|
334
372
|
end
|
@@ -1,9 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveResource
|
2
4
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
5
|
def request(event)
|
4
6
|
result = event.payload[:result]
|
5
|
-
|
6
|
-
|
7
|
+
|
8
|
+
# When result is nil, the connection could not even be initiated
|
9
|
+
# with the server, so we log an internal synthetic error response (523).
|
10
|
+
code = result.try(:code) || 523 # matches CloudFlare's convention
|
11
|
+
message = result.try(:message) || "ActiveResource connection error"
|
12
|
+
body = result.try(:body) || ""
|
13
|
+
|
14
|
+
log_level_method = code.to_i < 400 ? :info : :error
|
15
|
+
|
16
|
+
send log_level_method, "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
|
17
|
+
send log_level_method, "--> %d %s %d (%.1fms)" % [code, message, body.to_s.length, event.duration]
|
7
18
|
end
|
8
19
|
|
9
20
|
def logger
|
@@ -12,4 +23,4 @@ module ActiveResource
|
|
12
23
|
end
|
13
24
|
end
|
14
25
|
|
15
|
-
ActiveResource::LogSubscriber.attach_to :active_resource
|
26
|
+
ActiveResource::LogSubscriber.attach_to :active_resource
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module ActiveResource
|
2
|
-
module Observing
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
include ActiveModel::Observing
|
5
|
-
|
6
|
-
included do
|
7
|
-
%w( create save update destroy ).each do |method|
|
8
|
-
# def create_with_notifications(*args, &block)
|
9
|
-
# notify_observers(:before_create)
|
10
|
-
# if result = create_without_notifications(*args, &block)
|
11
|
-
# notify_observers(:after_create)
|
12
|
-
# end
|
13
|
-
# result
|
14
|
-
# end
|
15
|
-
# alias_method_chain(create, :notifications)
|
16
|
-
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
17
|
-
def #{method}_with_notifications(*args, &block)
|
18
|
-
notify_observers(:before_#{method})
|
19
|
-
if result = #{method}_without_notifications(*args, &block)
|
20
|
-
notify_observers(:after_#{method})
|
21
|
-
end
|
22
|
-
result
|
23
|
-
end
|
24
|
-
EOS
|
25
|
-
alias_method_chain(method, :notifications)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_resource"
|
2
4
|
require "rails"
|
3
5
|
|
@@ -6,9 +8,18 @@ module ActiveResource
|
|
6
8
|
config.active_resource = ActiveSupport::OrderedOptions.new
|
7
9
|
|
8
10
|
initializer "active_resource.set_configs" do |app|
|
9
|
-
|
10
|
-
|
11
|
+
ActiveSupport.on_load(:active_resource) do
|
12
|
+
app.config.active_resource.each do |k, v|
|
13
|
+
send "#{k}=", v
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer "active_resource.add_active_job_serializer" do |app|
|
19
|
+
if defined? app.config.active_job.custom_serializers
|
20
|
+
require "active_resource/active_job_serializer"
|
21
|
+
app.config.active_job.custom_serializers << ActiveResource::ActiveJobSerializer
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
14
|
-
end
|
25
|
+
end
|