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
@@ -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
|
-
|
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
|
-
:
|
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,
|
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.
|
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.
|
53
|
+
@proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
|
50
54
|
end
|
51
55
|
|
52
56
|
# Sets the user for remote service.
|
53
|
-
|
54
|
-
@user = user
|
55
|
-
end
|
57
|
+
attr_writer :user
|
56
58
|
|
57
59
|
# Sets the password for remote service.
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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 =
|
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 =
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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[
|
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
|
-
{
|
231
|
+
{ "Authorization" => digest_auth_header(http_method, uri) }
|
230
232
|
else
|
231
|
-
{
|
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
|
248
|
-
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(":"))
|
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
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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
|