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.
Files changed (33) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +2 -2
  3. data/README.rdoc +147 -49
  4. data/lib/active_resource.rb +12 -12
  5. data/lib/active_resource/active_job_serializer.rb +26 -0
  6. data/lib/active_resource/associations.rb +175 -0
  7. data/lib/active_resource/associations/builder/association.rb +33 -0
  8. data/lib/active_resource/associations/builder/belongs_to.rb +16 -0
  9. data/lib/active_resource/associations/builder/has_many.rb +14 -0
  10. data/lib/active_resource/associations/builder/has_one.rb +14 -0
  11. data/lib/active_resource/base.rb +444 -231
  12. data/lib/active_resource/callbacks.rb +22 -0
  13. data/lib/active_resource/collection.rb +94 -0
  14. data/lib/active_resource/connection.rb +112 -105
  15. data/lib/active_resource/custom_methods.rb +24 -14
  16. data/lib/active_resource/exceptions.rb +5 -3
  17. data/lib/active_resource/formats.rb +5 -3
  18. data/lib/active_resource/formats/json_format.rb +4 -1
  19. data/lib/active_resource/formats/xml_format.rb +4 -2
  20. data/lib/active_resource/http_mock.rb +69 -31
  21. data/lib/active_resource/log_subscriber.rb +14 -3
  22. data/lib/active_resource/observing.rb +0 -29
  23. data/lib/active_resource/railtie.rb +14 -3
  24. data/lib/active_resource/reflection.rb +78 -0
  25. data/lib/active_resource/schema.rb +4 -4
  26. data/lib/active_resource/singleton.rb +113 -0
  27. data/lib/active_resource/threadsafe_attributes.rb +66 -0
  28. data/lib/active_resource/validations.rb +56 -14
  29. data/lib/active_resource/version.rb +7 -5
  30. data/lib/activeresource.rb +3 -0
  31. metadata +78 -16
  32. data/CHANGELOG.md +0 -437
  33. data/examples/performance.rb +0 -70
@@ -1,26 +1,28 @@
1
- require 'active_support/core_ext/object/blank'
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. For example,
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
- # This route set creates routes for the following HTTP requests:
14
+ # This route set creates routes for the following HTTP requests:
13
15
  #
14
- # POST /people/new/register.json # PeopleController.register
15
- # 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
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 = "http://37s.sunrise.i:3000"
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 put(custom_method_name, options = {}, body = '')
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}.#{format.extension}#{query_string(query_options)}"
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 put(method_name, options = {}, body = '')
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}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
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}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
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['Location'] ? "#{super} => #{response['Location']}" : super
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['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
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, 'active_resource/formats/xml_format'
4
- autoload :JsonFormat, 'active_resource/formats/json_format'
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
- require 'active_support/json'
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
- require 'active_support/core_ext/hash/conversions'
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
- require 'active_support/core_ext/kernel/reporting'
2
- require 'active_support/core_ext/object/inclusion'
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. The HttpMock
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. This can be +get+, +post+, +put+, +delete+ or
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. This argument uses a
23
- # hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
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. This should be a string of Active Resource parseable content,
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. Uses the same hash format as
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(:#{method}, path, nil, request_headers)
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
- def headers_match?(req)
273
- # Ignore format header on equality if it's not defined
274
- format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method]
275
- if headers[format_header].present? || req.headers[format_header].blank?
276
- headers == req.headers
277
- else
278
- headers.dup.merge(format_header => req.headers[format_header]) == req.headers
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
- if @body.nil?
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 (other.is_a?(Response))
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
- @http ||= HttpMock.new(@site)
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
- info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
6
- info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
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
- app.config.active_resource.each do |k,v|
10
- ActiveResource::Base.send "#{k}=", v
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