activeresource 3.2.22.5 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/wrap"
4
+
5
+ module ActiveResource
6
+ module Callbacks
7
+ extend ActiveSupport::Concern
8
+
9
+ CALLBACKS = [
10
+ :before_validation, :after_validation, :before_save, :around_save, :after_save,
11
+ :before_create, :around_create, :after_create, :before_update, :around_update,
12
+ :after_update, :before_destroy, :around_destroy, :after_destroy
13
+ ]
14
+
15
+ included do
16
+ extend ActiveModel::Callbacks
17
+ include ActiveModel::Validations::Callbacks
18
+
19
+ define_model_callbacks :save, :create, :update, :destroy
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+ require "active_support/inflector"
5
+
6
+ module ActiveResource # :nodoc:
7
+ class Collection # :nodoc:
8
+ SELF_DEFINE_METHODS = [:to_a, :collect!, :map!]
9
+ include Enumerable
10
+ delegate :to_yaml, :all?, *(Array.instance_methods(false) - SELF_DEFINE_METHODS), to: :to_a
11
+
12
+ # The array of actual elements returned by index actions
13
+ attr_accessor :elements, :resource_class, :original_params
14
+
15
+ # ActiveResource::Collection is a wrapper to handle parsing index responses that
16
+ # do not directly map to Rails conventions.
17
+ #
18
+ # You can define a custom class that inherets from ActiveResource::Collection
19
+ # in order to to set the elements instance.
20
+ #
21
+ # GET /posts.json delivers following response body:
22
+ # {
23
+ # posts: [
24
+ # {
25
+ # title: "ActiveResource now has associations",
26
+ # body: "Lorem Ipsum"
27
+ # },
28
+ # {...}
29
+ # ],
30
+ # next_page: "/posts.json?page=2"
31
+ # }
32
+ #
33
+ # A Post class can be setup to handle it with:
34
+ #
35
+ # class Post < ActiveResource::Base
36
+ # self.site = "http://example.com"
37
+ # self.collection_parser = PostCollection
38
+ # end
39
+ #
40
+ # And the collection parser:
41
+ #
42
+ # class PostCollection < ActiveResource::Collection
43
+ # attr_accessor :next_page
44
+ # def initialize(parsed = {})
45
+ # @elements = parsed['posts']
46
+ # @next_page = parsed['next_page']
47
+ # end
48
+ # end
49
+ #
50
+ # The result from a find method that returns multiple entries will now be a
51
+ # PostParser instance. ActiveResource::Collection includes Enumerable and
52
+ # instances can be iterated over just like an array.
53
+ # @posts = Post.find(:all) # => PostCollection:xxx
54
+ # @posts.next_page # => "/posts.json?page=2"
55
+ # @posts.map(&:id) # =>[1, 3, 5 ...]
56
+ #
57
+ # The initialize method will receive the ActiveResource::Formats parsed result
58
+ # and should set @elements.
59
+ def initialize(elements = [])
60
+ @elements = elements
61
+ end
62
+
63
+ def to_a
64
+ elements
65
+ end
66
+
67
+ def collect!
68
+ return elements unless block_given?
69
+ set = []
70
+ each { |o| set << yield(o) }
71
+ @elements = set
72
+ self
73
+ end
74
+ alias map! collect!
75
+
76
+ def first_or_create(attributes = {})
77
+ first || resource_class.create(original_params.update(attributes))
78
+ rescue NoMethodError
79
+ raise "Cannot create resource from resource type: #{resource_class.inspect}"
80
+ end
81
+
82
+ def first_or_initialize(attributes = {})
83
+ first || resource_class.new(original_params.update(attributes))
84
+ rescue NoMethodError
85
+ raise "Cannot build resource from resource type: #{resource_class.inspect}"
86
+ end
87
+
88
+ def where(clauses = {})
89
+ raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
90
+ new_clauses = original_params.merge(clauses)
91
+ resource_class.where(new_clauses)
92
+ end
93
+ end
94
+ end
@@ -1,26 +1,28 @@
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'
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
- HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
16
- :put => 'Content-Type',
17
- :post => 'Content-Type',
18
- :delete => 'Accept',
19
- :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"
20
22
  }
21
23
 
22
- attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options
23
- 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
24
26
 
25
27
  class << self
26
28
  def requests
@@ -30,34 +32,35 @@ module ActiveResource
30
32
 
31
33
  # The +site+ parameter is required and will set the +site+
32
34
  # attribute to the URI for the remote resource service.
33
- def initialize(site, format = ActiveResource::Formats::JsonFormat)
34
- raise ArgumentError, 'Missing site URI' unless site
35
- @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
36
38
  self.site = site
37
39
  self.format = format
40
+ self.logger = logger
38
41
  end
39
42
 
40
43
  # Set URI for remote service.
41
44
  def site=(site)
42
- @site = site.is_a?(URI) ? site : URI.parser.parse(site)
45
+ @site = site.is_a?(URI) ? site : URI.parse(site)
46
+ @ssl_options ||= {} if @site.is_a?(URI::HTTPS)
43
47
  @user = URI.parser.unescape(@site.user) if @site.user
44
48
  @password = URI.parser.unescape(@site.password) if @site.password
45
49
  end
46
50
 
47
51
  # Set the proxy for remote service.
48
52
  def proxy=(proxy)
49
- @proxy = proxy.is_a?(URI) ? proxy : URI.parser.parse(proxy)
53
+ @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
50
54
  end
51
55
 
52
56
  # Sets the user for remote service.
53
- def user=(user)
54
- @user = user
55
- end
57
+ attr_writer :user
56
58
 
57
59
  # Sets the password for remote service.
58
- def password=(password)
59
- @password = password
60
- end
60
+ attr_writer :password
61
+
62
+ # Sets the bearer token for remote service.
63
+ attr_writer :bearer_token
61
64
 
62
65
  # Sets the auth type for remote service.
63
66
  def auth_type=(auth_type)
@@ -65,14 +68,16 @@ module ActiveResource
65
68
  end
66
69
 
67
70
  # Sets the number of seconds after which HTTP requests to the remote service should time out.
68
- def timeout=(timeout)
69
- @timeout = timeout
70
- end
71
+ attr_writer :timeout
72
+
73
+ # Sets the number of seconds after which HTTP connects to the remote service should time out.
74
+ attr_writer :open_timeout
75
+
76
+ # Sets the number of seconds after which HTTP read requests to the remote service should time out.
77
+ attr_writer :read_timeout
71
78
 
72
79
  # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
73
- def ssl_options=(opts={})
74
- @ssl_options = opts
75
- end
80
+ attr_writer :ssl_options
76
81
 
77
82
  # Executes a GET request.
78
83
  # Used to get (find) resources.
@@ -86,15 +91,21 @@ module ActiveResource
86
91
  with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
87
92
  end
88
93
 
94
+ # Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
95
+ # Used to update resources.
96
+ def patch(path, body = "", headers = {})
97
+ with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
98
+ end
99
+
89
100
  # Executes a PUT request (see HTTP protocol documentation if unfamiliar).
90
101
  # Used to update resources.
91
- def put(path, body = '', headers = {})
102
+ def put(path, body = "", headers = {})
92
103
  with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
93
104
  end
94
105
 
95
106
  # Executes a POST request.
96
107
  # Used to create new resources.
97
- def post(path, body = '', headers = {})
108
+ def post(path, body = "", headers = {})
98
109
  with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
99
110
  end
100
111
 
@@ -122,32 +133,32 @@ module ActiveResource
122
133
  # Handles response and error codes from the remote service.
123
134
  def handle_response(response)
124
135
  case response.code.to_i
125
- when 301, 302, 303, 307
126
- raise(Redirection.new(response))
127
- when 200...400
128
- response
129
- when 400
130
- raise(BadRequest.new(response))
131
- when 401
132
- raise(UnauthorizedAccess.new(response))
133
- when 403
134
- raise(ForbiddenAccess.new(response))
135
- when 404
136
- raise(ResourceNotFound.new(response))
137
- when 405
138
- raise(MethodNotAllowed.new(response))
139
- when 409
140
- raise(ResourceConflict.new(response))
141
- when 410
142
- raise(ResourceGone.new(response))
143
- when 422
144
- raise(ResourceInvalid.new(response))
145
- when 401...500
146
- raise(ClientError.new(response))
147
- when 500...600
148
- raise(ServerError.new(response))
149
- else
150
- raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
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}"))
151
162
  end
152
163
  end
153
164
 
@@ -159,45 +170,36 @@ module ActiveResource
159
170
 
160
171
  def new_http
161
172
  if @proxy
162
- Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
173
+ user = URI.parser.unescape(@proxy.user) if @proxy.user
174
+ password = URI.parser.unescape(@proxy.password) if @proxy.password
175
+ Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, user, password)
163
176
  else
164
177
  Net::HTTP.new(@site.host, @site.port)
165
178
  end
166
179
  end
167
180
 
168
181
  def configure_http(http)
169
- http = apply_ssl_options(http)
170
-
171
- # Net::HTTP timeouts default to 60 seconds.
172
- if @timeout
173
- http.open_timeout = @timeout
174
- http.read_timeout = @timeout
182
+ apply_ssl_options(http).tap do |https|
183
+ # Net::HTTP timeouts default to 60 seconds.
184
+ if defined? @timeout
185
+ https.open_timeout = @timeout
186
+ https.read_timeout = @timeout
187
+ end
188
+ https.open_timeout = @open_timeout if defined?(@open_timeout)
189
+ https.read_timeout = @read_timeout if defined?(@read_timeout)
175
190
  end
176
-
177
- http
178
191
  end
179
192
 
180
193
  def apply_ssl_options(http)
181
- return http unless @site.is_a?(URI::HTTPS)
182
-
183
- http.use_ssl = true
184
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
185
- return http unless defined?(@ssl_options)
194
+ http.tap do |https|
195
+ # Skip config if site is already a https:// URI.
196
+ if defined? @ssl_options
197
+ http.use_ssl = true
186
198
 
187
- http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
188
- http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
189
-
190
- http.cert = @ssl_options[:cert] if @ssl_options[:cert]
191
- http.key = @ssl_options[:key] if @ssl_options[:key]
192
-
193
- http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
194
- http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
195
-
196
- http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
197
- http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
198
- http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
199
-
200
- http
199
+ # All the SSL options have corresponding http settings.
200
+ @ssl_options.each { |key, value| http.send "#{key}=", value }
201
+ end
202
+ end
201
203
  end
202
204
 
203
205
  def default_header
@@ -218,7 +220,7 @@ module ActiveResource
218
220
  yield
219
221
  rescue UnauthorizedAccess => e
220
222
  raise if retried || auth_type != :digest
221
- @response_auth_header = e.response['WWW-Authenticate']
223
+ @response_auth_header = e.response["WWW-Authenticate"]
222
224
  retried = true
223
225
  retry
224
226
  end
@@ -226,10 +228,12 @@ module ActiveResource
226
228
  def authorization_header(http_method, uri)
227
229
  if @user || @password
228
230
  if auth_type == :digest
229
- { 'Authorization' => digest_auth_header(http_method, uri) }
231
+ { "Authorization" => digest_auth_header(http_method, uri) }
230
232
  else
231
- { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") }
233
+ { "Authorization" => "Basic " + ["#{@user}:#{@password}"].pack("m").delete("\r\n") }
232
234
  end
235
+ elsif @bearer_token
236
+ { "Authorization" => "Bearer #{@bearer_token}" }
233
237
  else
234
238
  {}
235
239
  end
@@ -244,8 +248,8 @@ module ActiveResource
244
248
  ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}")
245
249
  ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}")
246
250
 
247
- params.merge!('cnonce' => client_nonce)
248
- request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":"))
251
+ params["cnonce"] = client_nonce
252
+ request_digest = Digest::MD5.hexdigest([ha1, params["nonce"], "0", params["cnonce"], params["qop"], ha2].join(":"))
249
253
  "Digest #{auth_attributes_for(uri, request_digest, params)}"
250
254
  end
251
255
 
@@ -262,26 +266,29 @@ module ActiveResource
262
266
  end
263
267
 
264
268
  def auth_attributes_for(uri, request_digest, params)
265
- [
266
- %Q(username="#{@user}"),
267
- %Q(realm="#{params['realm']}"),
268
- %Q(qop="#{params['qop']}"),
269
- %Q(uri="#{uri.path}"),
270
- %Q(nonce="#{params['nonce']}"),
271
- %Q(nc="0"),
272
- %Q(cnonce="#{params['cnonce']}"),
273
- %Q(opaque="#{params['opaque']}"),
274
- %Q(response="#{request_digest}")].join(", ")
269
+ auth_attrs =
270
+ [
271
+ %Q(username="#{@user}"),
272
+ %Q(realm="#{params['realm']}"),
273
+ %Q(qop="#{params['qop']}"),
274
+ %Q(uri="#{uri.path}"),
275
+ %Q(nonce="#{params['nonce']}"),
276
+ 'nc="0"',
277
+ %Q(cnonce="#{params['cnonce']}"),
278
+ %Q(response="#{request_digest}")]
279
+
280
+ auth_attrs << %Q(opaque="#{params['opaque']}") unless params["opaque"].blank?
281
+ auth_attrs.join(", ")
275
282
  end
276
283
 
277
284
  def http_format_header(http_method)
278
- {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
285
+ { HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type }
279
286
  end
280
287
 
281
288
  def legitimize_auth_type(auth_type)
282
289
  return :basic if auth_type.nil?
283
290
  auth_type = auth_type.to_sym
284
- auth_type.in?([:basic, :digest]) ? auth_type : :basic
291
+ auth_type.in?([:basic, :digest, :bearer]) ? auth_type : :basic
285
292
  end
286
293
  end
287
294
  end