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,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveResource::Associations::Builder
|
4
|
+
class Association #:nodoc:
|
5
|
+
# providing a Class-Variable, which will have a different store of subclasses
|
6
|
+
class_attribute :valid_options
|
7
|
+
self.valid_options = [:class_name]
|
8
|
+
|
9
|
+
# would identify subclasses of association
|
10
|
+
class_attribute :macro
|
11
|
+
|
12
|
+
attr_reader :model, :name, :options, :klass
|
13
|
+
|
14
|
+
def self.build(model, name, options)
|
15
|
+
new(model, name, options).build
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(model, name, options)
|
19
|
+
@model, @name, @options = model, name, options
|
20
|
+
end
|
21
|
+
|
22
|
+
def build
|
23
|
+
validate_options
|
24
|
+
model.create_reflection(self.class.macro, name, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def validate_options
|
30
|
+
options.assert_valid_keys(self.class.valid_options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveResource::Associations::Builder
|
4
|
+
class BelongsTo < Association
|
5
|
+
self.valid_options += [:foreign_key]
|
6
|
+
|
7
|
+
self.macro = :belongs_to
|
8
|
+
|
9
|
+
def build
|
10
|
+
validate_options
|
11
|
+
reflection = model.create_reflection(self.class.macro, name, options)
|
12
|
+
model.defines_belongs_to_finder_method(reflection)
|
13
|
+
reflection
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveResource::Associations::Builder
|
4
|
+
class HasMany < Association
|
5
|
+
self.macro = :has_many
|
6
|
+
|
7
|
+
def build
|
8
|
+
validate_options
|
9
|
+
model.create_reflection(self.class.macro, name, options).tap do |reflection|
|
10
|
+
model.defines_has_many_finder_method(reflection)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveResource::Associations::Builder
|
4
|
+
class HasOne < Association
|
5
|
+
self.macro = :has_one
|
6
|
+
|
7
|
+
def build
|
8
|
+
validate_options
|
9
|
+
model.create_reflection(self.class.macro, name, options).tap do |reflection|
|
10
|
+
model.defines_has_one_finder_method(reflection)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/active_resource/base.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
|
14
|
-
require
|
15
|
-
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/class/attribute_accessors"
|
5
|
+
require "active_support/core_ext/class/attribute"
|
6
|
+
require "active_support/core_ext/hash/indifferent_access"
|
7
|
+
require "active_support/core_ext/kernel/reporting"
|
8
|
+
require "active_support/core_ext/module/delegation"
|
9
|
+
require "active_support/core_ext/module/aliasing"
|
10
|
+
require "active_support/core_ext/object/blank"
|
11
|
+
require "active_support/core_ext/object/to_query"
|
12
|
+
require "active_support/core_ext/object/duplicable"
|
13
|
+
require "set"
|
14
|
+
require "uri"
|
15
|
+
|
16
|
+
require "active_support/core_ext/uri"
|
17
|
+
require "active_resource/connection"
|
18
|
+
require "active_resource/formats"
|
19
|
+
require "active_resource/schema"
|
20
|
+
require "active_resource/log_subscriber"
|
21
|
+
require "active_resource/associations"
|
22
|
+
require "active_resource/reflection"
|
23
|
+
require "active_resource/threadsafe_attributes"
|
24
|
+
|
25
|
+
require "active_model/serializers/xml"
|
19
26
|
|
20
27
|
module ActiveResource
|
21
28
|
# ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
|
@@ -24,29 +31,29 @@ module ActiveResource
|
|
24
31
|
#
|
25
32
|
# == Automated mapping
|
26
33
|
#
|
27
|
-
# Active Resource objects represent your RESTful resources as manipulatable Ruby objects.
|
34
|
+
# Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
|
28
35
|
# to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
|
29
36
|
# Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
|
30
37
|
# URI of the resources.
|
31
38
|
#
|
32
39
|
# class Person < ActiveResource::Base
|
33
|
-
# self.site = "
|
40
|
+
# self.site = "https://api.people.com"
|
34
41
|
# end
|
35
42
|
#
|
36
|
-
# Now the Person class is mapped to RESTful resources located at <tt>
|
43
|
+
# Now the Person class is mapped to RESTful resources located at <tt>https://api.people.com/people/</tt>, and
|
37
44
|
# you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have
|
38
45
|
# an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
|
39
46
|
#
|
40
47
|
# class PersonResource < ActiveResource::Base
|
41
|
-
# self.site = "
|
48
|
+
# self.site = "https://api.people.com"
|
42
49
|
# self.element_name = "person"
|
43
50
|
# end
|
44
51
|
#
|
45
52
|
# If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
|
46
53
|
#
|
47
54
|
# class PersonResource < ActiveResource::Base
|
48
|
-
# self.site = "
|
49
|
-
# self.proxy = "
|
55
|
+
# self.site = "https://api.people.com"
|
56
|
+
# self.proxy = "https://user:password@proxy.people.com:8080"
|
50
57
|
# end
|
51
58
|
#
|
52
59
|
#
|
@@ -76,7 +83,7 @@ module ActiveResource
|
|
76
83
|
#
|
77
84
|
# Since simple CRUD/life cycle methods can't accomplish every task, Active Resource also supports
|
78
85
|
# defining your own custom REST methods. To invoke them, Active Resource provides the <tt>get</tt>,
|
79
|
-
# <tt>post</tt>, <tt>put</tt> and <tt
|
86
|
+
# <tt>post</tt>, <tt>put</tt> and <tt>delete</tt> methods where you can specify a custom REST method
|
80
87
|
# name to invoke.
|
81
88
|
#
|
82
89
|
# # POST to the custom 'register' REST method, i.e. POST /people/new/register.json.
|
@@ -102,7 +109,7 @@ module ActiveResource
|
|
102
109
|
# You can validate resources client side by overriding validation methods in the base class.
|
103
110
|
#
|
104
111
|
# class Person < ActiveResource::Base
|
105
|
-
# self.site = "
|
112
|
+
# self.site = "https://api.people.com"
|
106
113
|
# protected
|
107
114
|
# def validate
|
108
115
|
# errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
|
@@ -113,47 +120,64 @@ module ActiveResource
|
|
113
120
|
#
|
114
121
|
# == Authentication
|
115
122
|
#
|
116
|
-
# Many REST APIs
|
117
|
-
#
|
123
|
+
# Many REST APIs require authentication. The HTTP spec describes two ways to
|
124
|
+
# make requests with a username and password (see RFC 2617).
|
118
125
|
#
|
119
|
-
#
|
120
|
-
#
|
126
|
+
# Basic authentication simply sends a username and password along with HTTP
|
127
|
+
# requests. These sensitive credentials are sent unencrypted, visible to
|
128
|
+
# any onlooker, so this scheme should only be used with SSL.
|
129
|
+
#
|
130
|
+
# Digest authentication sends a crytographic hash of the username, password,
|
131
|
+
# HTTP method, URI, and a single-use secret key provided by the server.
|
132
|
+
# Sensitive credentials aren't visible to onlookers, so digest authentication
|
133
|
+
# doesn't require SSL. However, this doesn't mean the connection is secure!
|
134
|
+
# Just the username and password.
|
135
|
+
#
|
136
|
+
# (You really, really want to use SSL. There's little reason not to.)
|
137
|
+
#
|
138
|
+
# === Picking an authentication scheme
|
139
|
+
#
|
140
|
+
# Basic authentication is the default. To switch to digest authentication,
|
141
|
+
# set +auth_type+ to +:digest+:
|
121
142
|
#
|
122
143
|
# class Person < ActiveResource::Base
|
123
|
-
# self.
|
144
|
+
# self.auth_type = :digest
|
124
145
|
# end
|
125
146
|
#
|
126
|
-
#
|
147
|
+
# === Setting the username and password
|
148
|
+
#
|
149
|
+
# Set +user+ and +password+ on the class, or include them in the +site+ URL.
|
127
150
|
#
|
128
151
|
# class Person < ActiveResource::Base
|
129
|
-
#
|
152
|
+
# # Set user and password directly:
|
130
153
|
# self.user = "ryan"
|
131
154
|
# self.password = "password"
|
132
|
-
# end
|
133
155
|
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
# Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
|
138
|
-
# as usernames. In those situations you should use the separate user and password option.
|
156
|
+
# # Or include them in the site:
|
157
|
+
# self.site = "https://ryan:password@api.people.com"
|
158
|
+
# end
|
139
159
|
#
|
140
160
|
# === Certificate Authentication
|
141
161
|
#
|
142
|
-
#
|
162
|
+
# You can also authenticate using an X509 certificate. <tt>See ssl_options=</tt> for all options.
|
143
163
|
#
|
144
164
|
# class Person < ActiveResource::Base
|
145
165
|
# self.site = "https://secure.api.people.com/"
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
166
|
+
#
|
167
|
+
# File.open(pem_file_path, 'rb') do |pem_file|
|
168
|
+
# self.ssl_options = {
|
169
|
+
# cert: OpenSSL::X509::Certificate.new(pem_file),
|
170
|
+
# key: OpenSSL::PKey::RSA.new(pem_file),
|
171
|
+
# ca_path: "/path/to/OpenSSL/formatted/CA_Certs",
|
172
|
+
# verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
173
|
+
# end
|
150
174
|
# end
|
151
175
|
#
|
152
176
|
#
|
153
177
|
# == Errors & Validation
|
154
178
|
#
|
155
179
|
# Error handling and validation is handled in much the same manner as you're used to seeing in
|
156
|
-
# Active Record.
|
180
|
+
# Active Record. Both the response code in the HTTP response and the body of the response are used to
|
157
181
|
# indicate that an error occurred.
|
158
182
|
#
|
159
183
|
# === Resource errors
|
@@ -162,7 +186,7 @@ module ActiveResource
|
|
162
186
|
# response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
|
163
187
|
# exception.
|
164
188
|
#
|
165
|
-
# # GET
|
189
|
+
# # GET https://api.people.com/people/999.json
|
166
190
|
# ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
|
167
191
|
#
|
168
192
|
#
|
@@ -184,7 +208,7 @@ module ActiveResource
|
|
184
208
|
# * Other - ActiveResource::ConnectionError
|
185
209
|
#
|
186
210
|
# These custom exceptions allow you to deal with resource errors more naturally and with more precision
|
187
|
-
# rather than returning a general HTTP error.
|
211
|
+
# rather than returning a general HTTP error. For example:
|
188
212
|
#
|
189
213
|
# begin
|
190
214
|
# ryan = Person.find(my_id)
|
@@ -198,7 +222,7 @@ module ActiveResource
|
|
198
222
|
# an ActiveResource::MissingPrefixParam will be raised.
|
199
223
|
#
|
200
224
|
# class Comment < ActiveResource::Base
|
201
|
-
# self.site = "
|
225
|
+
# self.site = "https://someip.com/posts/:post_id"
|
202
226
|
# end
|
203
227
|
#
|
204
228
|
# Comment.find(1)
|
@@ -207,8 +231,8 @@ module ActiveResource
|
|
207
231
|
# === Validation errors
|
208
232
|
#
|
209
233
|
# Active Resource supports validations on resources and will return errors if any of these validations fail
|
210
|
-
# (e.g., "First name can not be blank" and so on).
|
211
|
-
# a response code of <tt>422</tt> and an
|
234
|
+
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
|
235
|
+
# a response code of <tt>422</tt> and an JSON or XML representation of the validation errors. The save operation will
|
212
236
|
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
|
213
237
|
#
|
214
238
|
# ryan = Person.find(1)
|
@@ -216,20 +240,29 @@ module ActiveResource
|
|
216
240
|
# ryan.save # => false
|
217
241
|
#
|
218
242
|
# # When
|
219
|
-
# # PUT
|
243
|
+
# # PUT https://api.people.com/people/1.xml
|
220
244
|
# # or
|
221
|
-
# # PUT
|
245
|
+
# # PUT https://api.people.com/people/1.json
|
222
246
|
# # is requested with invalid values, the response is:
|
223
247
|
# #
|
224
248
|
# # Response (422):
|
225
249
|
# # <errors><error>First cannot be empty</error></errors>
|
226
250
|
# # or
|
227
|
-
# # {"errors":["
|
251
|
+
# # {"errors":{"first":["cannot be empty"]}}
|
228
252
|
# #
|
229
253
|
#
|
230
254
|
# ryan.errors.invalid?(:first) # => true
|
231
255
|
# ryan.errors.full_messages # => ['First cannot be empty']
|
232
256
|
#
|
257
|
+
# For backwards-compatibility with older endpoints, the following formats are also supported in JSON responses:
|
258
|
+
#
|
259
|
+
# # {"errors":['First cannot be empty']}
|
260
|
+
# # This was the required format for previous versions of ActiveResource
|
261
|
+
# # {"first":["cannot be empty"]}
|
262
|
+
# # This was the default format produced by respond_with in ActionController <3.2.1
|
263
|
+
#
|
264
|
+
# Parsing either of these formats will result in a deprecation warning.
|
265
|
+
#
|
233
266
|
# Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
|
234
267
|
#
|
235
268
|
# === Timeouts
|
@@ -239,7 +272,7 @@ module ActiveResource
|
|
239
272
|
# amount of time before Active Resource times out with the +timeout+ variable.
|
240
273
|
#
|
241
274
|
# class Person < ActiveResource::Base
|
242
|
-
# self.site = "
|
275
|
+
# self.site = "https://api.people.com"
|
243
276
|
# self.timeout = 5
|
244
277
|
# end
|
245
278
|
#
|
@@ -255,15 +288,39 @@ module ActiveResource
|
|
255
288
|
# Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
|
256
289
|
# sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
|
257
290
|
# <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
|
291
|
+
#
|
292
|
+
# Active Resource also supports distinct +open_timeout+ (time to connect) and +read_timeout+ (how long to
|
293
|
+
# wait for an upstream response). This is inline with supported +Net::HTTP+ timeout configuration and allows
|
294
|
+
# for finer control of client timeouts depending on context.
|
295
|
+
#
|
296
|
+
# class Person < ActiveResource::Base
|
297
|
+
# self.site = "https://api.people.com"
|
298
|
+
# self.open_timeout = 2
|
299
|
+
# self.read_timeout = 10
|
300
|
+
# end
|
258
301
|
class Base
|
259
302
|
##
|
260
303
|
# :singleton-method:
|
261
304
|
# The logger for diagnosing and tracing Active Resource calls.
|
262
|
-
|
305
|
+
cattr_reader :logger
|
306
|
+
|
307
|
+
def self.logger=(logger)
|
308
|
+
self._connection = nil
|
309
|
+
@@logger = logger
|
310
|
+
end
|
263
311
|
|
264
312
|
class_attribute :_format
|
313
|
+
class_attribute :_collection_parser
|
314
|
+
class_attribute :include_format_in_path
|
315
|
+
self.include_format_in_path = true
|
316
|
+
|
317
|
+
class_attribute :connection_class
|
318
|
+
self.connection_class = Connection
|
265
319
|
|
266
320
|
class << self
|
321
|
+
include ThreadsafeAttributes
|
322
|
+
threadsafe_attribute :_headers, :_connection, :_user, :_password, :_site, :_proxy
|
323
|
+
|
267
324
|
# Creates a schema for this resource - setting the attributes that are
|
268
325
|
# known prior to fetching an instance from the remote system.
|
269
326
|
#
|
@@ -276,39 +333,39 @@ module ActiveResource
|
|
276
333
|
# remote system.
|
277
334
|
#
|
278
335
|
# example:
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
282
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
336
|
+
# class Person < ActiveResource::Base
|
337
|
+
# schema do
|
338
|
+
# # define each attribute separately
|
339
|
+
# attribute 'name', :string
|
340
|
+
#
|
341
|
+
# # or use the convenience methods and pass >=1 attribute names
|
342
|
+
# string 'eye_color', 'hair_color'
|
343
|
+
# integer 'age'
|
344
|
+
# float 'height', 'weight'
|
345
|
+
#
|
346
|
+
# # unsupported types should be left as strings
|
347
|
+
# # overload the accessor methods if you need to convert them
|
348
|
+
# attribute 'created_at', 'string'
|
349
|
+
# end
|
292
350
|
# end
|
293
|
-
# end
|
294
351
|
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
299
|
-
#
|
352
|
+
# p = Person.new
|
353
|
+
# p.respond_to? :name # => true
|
354
|
+
# p.respond_to? :age # => true
|
355
|
+
# p.name # => nil
|
356
|
+
# p.age # => nil
|
300
357
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
#
|
358
|
+
# j = Person.find_by_name('John')
|
359
|
+
# <person><name>John</name><age>34</age><num_children>3</num_children></person>
|
360
|
+
# j.respond_to? :name # => true
|
361
|
+
# j.respond_to? :age # => true
|
362
|
+
# j.name # => 'John'
|
363
|
+
# j.age # => '34' # note this is a string!
|
364
|
+
# j.num_children # => '3' # note this is a string!
|
307
365
|
#
|
308
|
-
#
|
366
|
+
# p.num_children # => NoMethodError
|
309
367
|
#
|
310
|
-
# Attribute-types must be one of:
|
311
|
-
# string, integer, float
|
368
|
+
# Attribute-types must be one of: <tt>string, text, integer, float, decimal, datetime, timestamp, time, date, binary, boolean</tt>
|
312
369
|
#
|
313
370
|
# Note: at present the attribute-type doesn't do anything, but stay
|
314
371
|
# tuned...
|
@@ -328,12 +385,12 @@ module ActiveResource
|
|
328
385
|
@schema ||= {}.with_indifferent_access
|
329
386
|
@known_attributes ||= []
|
330
387
|
|
331
|
-
schema_definition.attrs.each do |k,v|
|
388
|
+
schema_definition.attrs.each do |k, v|
|
332
389
|
@schema[k] = v
|
333
390
|
@known_attributes << k
|
334
391
|
end
|
335
392
|
|
336
|
-
schema
|
393
|
+
@schema
|
337
394
|
else
|
338
395
|
@schema ||= nil
|
339
396
|
end
|
@@ -349,9 +406,9 @@ module ActiveResource
|
|
349
406
|
#
|
350
407
|
# example:
|
351
408
|
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
409
|
+
# class Person < ActiveResource::Base
|
410
|
+
# schema = {'name' => :string, 'age' => :integer }
|
411
|
+
# end
|
355
412
|
#
|
356
413
|
# The keys/values can be strings or symbols. They will be converted to
|
357
414
|
# strings.
|
@@ -367,7 +424,7 @@ module ActiveResource
|
|
367
424
|
raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
|
368
425
|
|
369
426
|
schema do
|
370
|
-
the_schema.each {|k,v| attribute(k,v) }
|
427
|
+
the_schema.each { |k, v| attribute(k, v) }
|
371
428
|
end
|
372
429
|
end
|
373
430
|
|
@@ -375,33 +432,33 @@ module ActiveResource
|
|
375
432
|
# from the provided <tt>schema</tt>
|
376
433
|
# Attributes that are known will cause your resource to return 'true'
|
377
434
|
# when <tt>respond_to?</tt> is called on them. A known attribute will
|
378
|
-
# return nil if not set (rather than <
|
435
|
+
# return nil if not set (rather than <tt>MethodNotFound</tt>); thus
|
379
436
|
# known attributes can be used with <tt>validates_presence_of</tt>
|
380
437
|
# without a getter-method.
|
381
438
|
def known_attributes
|
382
439
|
@known_attributes ||= []
|
383
440
|
end
|
384
441
|
|
385
|
-
# Gets the URI of the REST resources to map for this class.
|
442
|
+
# Gets the URI of the REST resources to map for this class. The site variable is required for
|
386
443
|
# Active Resource's mapping to work.
|
387
444
|
def site
|
388
445
|
# Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
|
389
446
|
#
|
390
447
|
# With superclass_delegating_reader
|
391
448
|
#
|
392
|
-
# Parent.site = '
|
393
|
-
# Subclass.site # => '
|
449
|
+
# Parent.site = 'https://anonymous@test.com'
|
450
|
+
# Subclass.site # => 'https://anonymous@test.com'
|
394
451
|
# Subclass.site.user = 'david'
|
395
|
-
# Parent.site # => '
|
452
|
+
# Parent.site # => 'https://david@test.com'
|
396
453
|
#
|
397
454
|
# Without superclass_delegating_reader (expected behavior)
|
398
455
|
#
|
399
|
-
# Parent.site = '
|
400
|
-
# Subclass.site # => '
|
456
|
+
# Parent.site = 'https://anonymous@test.com'
|
457
|
+
# Subclass.site # => 'https://anonymous@test.com'
|
401
458
|
# Subclass.site.user = 'david' # => TypeError: can't modify frozen object
|
402
459
|
#
|
403
|
-
if
|
404
|
-
|
460
|
+
if _site_defined?
|
461
|
+
_site
|
405
462
|
elsif superclass != Object && superclass.site
|
406
463
|
superclass.site.dup.freeze
|
407
464
|
end
|
@@ -410,21 +467,21 @@ module ActiveResource
|
|
410
467
|
# Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
|
411
468
|
# The site variable is required for Active Resource's mapping to work.
|
412
469
|
def site=(site)
|
413
|
-
|
470
|
+
self._connection = nil
|
414
471
|
if site.nil?
|
415
|
-
|
472
|
+
self._site = nil
|
416
473
|
else
|
417
|
-
|
418
|
-
|
419
|
-
|
474
|
+
self._site = create_site_uri_from(site)
|
475
|
+
self._user = URI.parser.unescape(_site.user) if _site.user
|
476
|
+
self._password = URI.parser.unescape(_site.password) if _site.password
|
420
477
|
end
|
421
478
|
end
|
422
479
|
|
423
480
|
# Gets the \proxy variable if a proxy is required
|
424
481
|
def proxy
|
425
482
|
# Not using superclass_delegating_reader. See +site+ for explanation
|
426
|
-
if
|
427
|
-
|
483
|
+
if _proxy_defined?
|
484
|
+
_proxy
|
428
485
|
elsif superclass != Object && superclass.proxy
|
429
486
|
superclass.proxy.dup.freeze
|
430
487
|
end
|
@@ -432,15 +489,15 @@ module ActiveResource
|
|
432
489
|
|
433
490
|
# Sets the URI of the http proxy to the value in the +proxy+ argument.
|
434
491
|
def proxy=(proxy)
|
435
|
-
|
436
|
-
|
492
|
+
self._connection = nil
|
493
|
+
self._proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
|
437
494
|
end
|
438
495
|
|
439
496
|
# Gets the \user for REST HTTP authentication.
|
440
497
|
def user
|
441
498
|
# Not using superclass_delegating_reader. See +site+ for explanation
|
442
|
-
if
|
443
|
-
|
499
|
+
if _user_defined?
|
500
|
+
_user
|
444
501
|
elsif superclass != Object && superclass.user
|
445
502
|
superclass.user.dup.freeze
|
446
503
|
end
|
@@ -448,15 +505,15 @@ module ActiveResource
|
|
448
505
|
|
449
506
|
# Sets the \user for REST HTTP authentication.
|
450
507
|
def user=(user)
|
451
|
-
|
452
|
-
|
508
|
+
self._connection = nil
|
509
|
+
self._user = user
|
453
510
|
end
|
454
511
|
|
455
512
|
# Gets the \password for REST HTTP authentication.
|
456
513
|
def password
|
457
514
|
# Not using superclass_delegating_reader. See +site+ for explanation
|
458
|
-
if
|
459
|
-
|
515
|
+
if _password_defined?
|
516
|
+
_password
|
460
517
|
elsif superclass != Object && superclass.password
|
461
518
|
superclass.password.dup.freeze
|
462
519
|
end
|
@@ -464,8 +521,8 @@ module ActiveResource
|
|
464
521
|
|
465
522
|
# Sets the \password for REST HTTP authentication.
|
466
523
|
def password=(password)
|
467
|
-
|
468
|
-
|
524
|
+
self._connection = nil
|
525
|
+
self._password = password
|
469
526
|
end
|
470
527
|
|
471
528
|
def auth_type
|
@@ -475,7 +532,7 @@ module ActiveResource
|
|
475
532
|
end
|
476
533
|
|
477
534
|
def auth_type=(auth_type)
|
478
|
-
|
535
|
+
self._connection = nil
|
479
536
|
@auth_type = auth_type
|
480
537
|
end
|
481
538
|
|
@@ -501,12 +558,34 @@ module ActiveResource
|
|
501
558
|
self._format || ActiveResource::Formats::JsonFormat
|
502
559
|
end
|
503
560
|
|
561
|
+
# Sets the parser to use when a collection is returned. The parser must be Enumerable.
|
562
|
+
def collection_parser=(parser_instance)
|
563
|
+
parser_instance = parser_instance.constantize if parser_instance.is_a?(String)
|
564
|
+
self._collection_parser = parser_instance
|
565
|
+
end
|
566
|
+
|
567
|
+
def collection_parser
|
568
|
+
self._collection_parser || ActiveResource::Collection
|
569
|
+
end
|
570
|
+
|
504
571
|
# Sets the number of seconds after which requests to the REST API should time out.
|
505
572
|
def timeout=(timeout)
|
506
|
-
|
573
|
+
self._connection = nil
|
507
574
|
@timeout = timeout
|
508
575
|
end
|
509
576
|
|
577
|
+
# Sets the number of seconds after which connection attempts to the REST API should time out.
|
578
|
+
def open_timeout=(timeout)
|
579
|
+
self._connection = nil
|
580
|
+
@open_timeout = timeout
|
581
|
+
end
|
582
|
+
|
583
|
+
# Sets the number of seconds after which reads to the REST API should time out.
|
584
|
+
def read_timeout=(timeout)
|
585
|
+
self._connection = nil
|
586
|
+
@read_timeout = timeout
|
587
|
+
end
|
588
|
+
|
510
589
|
# Gets the number of seconds after which requests to the REST API should time out.
|
511
590
|
def timeout
|
512
591
|
if defined?(@timeout)
|
@@ -516,6 +595,24 @@ module ActiveResource
|
|
516
595
|
end
|
517
596
|
end
|
518
597
|
|
598
|
+
# Gets the number of seconds after which connection attempts to the REST API should time out.
|
599
|
+
def open_timeout
|
600
|
+
if defined?(@open_timeout)
|
601
|
+
@open_timeout
|
602
|
+
elsif superclass != Object && superclass.open_timeout
|
603
|
+
superclass.open_timeout
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
# Gets the number of seconds after which reads to the REST API should time out.
|
608
|
+
def read_timeout
|
609
|
+
if defined?(@read_timeout)
|
610
|
+
@read_timeout
|
611
|
+
elsif superclass != Object && superclass.read_timeout
|
612
|
+
superclass.read_timeout
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
519
616
|
# Options that will get applied to an SSL connection.
|
520
617
|
#
|
521
618
|
# * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
|
@@ -527,9 +624,9 @@ module ActiveResource
|
|
527
624
|
# * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
|
528
625
|
# * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
|
529
626
|
# * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
|
530
|
-
def ssl_options=(
|
531
|
-
|
532
|
-
@ssl_options
|
627
|
+
def ssl_options=(options)
|
628
|
+
self._connection = nil
|
629
|
+
@ssl_options = options
|
533
630
|
end
|
534
631
|
|
535
632
|
# Returns the SSL options hash.
|
@@ -545,22 +642,31 @@ module ActiveResource
|
|
545
642
|
# The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
|
546
643
|
# or not (defaults to <tt>false</tt>).
|
547
644
|
def connection(refresh = false)
|
548
|
-
if
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
645
|
+
if _connection_defined? || superclass == Object
|
646
|
+
self._connection = connection_class.new(
|
647
|
+
site, format, logger: logger
|
648
|
+
) if refresh || _connection.nil?
|
649
|
+
_connection.proxy = proxy if proxy
|
650
|
+
_connection.user = user if user
|
651
|
+
_connection.password = password if password
|
652
|
+
_connection.auth_type = auth_type if auth_type
|
653
|
+
_connection.timeout = timeout if timeout
|
654
|
+
_connection.open_timeout = open_timeout if open_timeout
|
655
|
+
_connection.read_timeout = read_timeout if read_timeout
|
656
|
+
_connection.ssl_options = ssl_options if ssl_options
|
657
|
+
_connection
|
557
658
|
else
|
558
659
|
superclass.connection
|
559
660
|
end
|
560
661
|
end
|
561
662
|
|
562
663
|
def headers
|
563
|
-
|
664
|
+
headers_state = self._headers || {}
|
665
|
+
if superclass != Object
|
666
|
+
self._headers = superclass.headers.merge(headers_state)
|
667
|
+
else
|
668
|
+
headers_state
|
669
|
+
end
|
564
670
|
end
|
565
671
|
|
566
672
|
attr_writer :element_name
|
@@ -578,20 +684,28 @@ module ActiveResource
|
|
578
684
|
attr_writer :primary_key
|
579
685
|
|
580
686
|
def primary_key
|
581
|
-
@primary_key
|
687
|
+
if defined?(@primary_key)
|
688
|
+
@primary_key
|
689
|
+
elsif superclass != Object && superclass.primary_key
|
690
|
+
primary_key = superclass.primary_key
|
691
|
+
return primary_key if primary_key.is_a?(Symbol)
|
692
|
+
primary_key.dup.freeze
|
693
|
+
else
|
694
|
+
"id"
|
695
|
+
end
|
582
696
|
end
|
583
697
|
|
584
698
|
# Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
|
585
699
|
# This method is regenerated at runtime based on what the \prefix is set to.
|
586
|
-
def prefix(options={})
|
700
|
+
def prefix(options = {})
|
587
701
|
default = site.path
|
588
|
-
default <<
|
702
|
+
default << "/" unless default[-1..-1] == "/"
|
589
703
|
# generate the actual method based on the current site path
|
590
704
|
self.prefix = default
|
591
705
|
prefix(options)
|
592
706
|
end
|
593
707
|
|
594
|
-
# An attribute reader for the source string for the resource path \prefix.
|
708
|
+
# An attribute reader for the source string for the resource path \prefix. This
|
595
709
|
# method is regenerated at runtime based on what the \prefix is set to.
|
596
710
|
def prefix_source
|
597
711
|
prefix # generate #prefix and #prefix_source methods first
|
@@ -600,7 +714,7 @@ module ActiveResource
|
|
600
714
|
|
601
715
|
# Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
|
602
716
|
# Default value is <tt>site.path</tt>.
|
603
|
-
def prefix=(value =
|
717
|
+
def prefix=(value = "/")
|
604
718
|
# Replace :placeholders with '#{embedded options[:lookups]}'
|
605
719
|
prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.parser.escape options[#{key}].to_s}" }
|
606
720
|
|
@@ -624,12 +738,17 @@ module ActiveResource
|
|
624
738
|
alias_method :set_element_name, :element_name= #:nodoc:
|
625
739
|
alias_method :set_collection_name, :collection_name= #:nodoc:
|
626
740
|
|
627
|
-
|
741
|
+
def format_extension
|
742
|
+
include_format_in_path ? ".#{format.extension}" : ""
|
743
|
+
end
|
744
|
+
|
745
|
+
# Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
|
628
746
|
# will split from the \prefix options.
|
629
747
|
#
|
630
748
|
# ==== Options
|
631
749
|
# +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
|
632
|
-
#
|
750
|
+
# would yield a URL like <tt>/accounts/19/purchases.json</tt>).
|
751
|
+
#
|
633
752
|
# +query_options+ - A \hash to add items to the query string for the request.
|
634
753
|
#
|
635
754
|
# ==== Examples
|
@@ -637,7 +756,7 @@ module ActiveResource
|
|
637
756
|
# # => /posts/1.json
|
638
757
|
#
|
639
758
|
# class Comment < ActiveResource::Base
|
640
|
-
# self.site = "
|
759
|
+
# self.site = "https://37s.sunrise.com/posts/:post_id"
|
641
760
|
# end
|
642
761
|
#
|
643
762
|
# Comment.element_path(1, :post_id => 5)
|
@@ -653,30 +772,60 @@ module ActiveResource
|
|
653
772
|
check_prefix_options(prefix_options)
|
654
773
|
|
655
774
|
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
656
|
-
"#{prefix(prefix_options)}#{collection_name}/#{URI.
|
775
|
+
"#{prefix(prefix_options)}#{collection_name}/#{URI.encode_www_form_component(id.to_s)}#{format_extension}#{query_string(query_options)}"
|
776
|
+
end
|
777
|
+
|
778
|
+
# Gets the element url for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
|
779
|
+
# will split from the \prefix options.
|
780
|
+
#
|
781
|
+
# ==== Options
|
782
|
+
# +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
|
783
|
+
# would yield a URL like <tt>https://37s.sunrise.com/accounts/19/purchases.json</tt>).
|
784
|
+
#
|
785
|
+
# +query_options+ - A \hash to add items to the query string for the request.
|
786
|
+
#
|
787
|
+
# ==== Examples
|
788
|
+
# Post.element_url(1)
|
789
|
+
# # => https://37s.sunrise.com/posts/1.json
|
790
|
+
#
|
791
|
+
# class Comment < ActiveResource::Base
|
792
|
+
# self.site = "https://37s.sunrise.com/posts/:post_id"
|
793
|
+
# end
|
794
|
+
#
|
795
|
+
# Comment.element_url(1, :post_id => 5)
|
796
|
+
# # => https://37s.sunrise.com/posts/5/comments/1.json
|
797
|
+
#
|
798
|
+
# Comment.element_url(1, :post_id => 5, :active => 1)
|
799
|
+
# # => https://37s.sunrise.com/posts/5/comments/1.json?active=1
|
800
|
+
#
|
801
|
+
# Comment.element_url(1, {:post_id => 5}, {:active => 1})
|
802
|
+
# # => https://37s.sunrise.com/posts/5/comments/1.json?active=1
|
803
|
+
#
|
804
|
+
def element_url(id, prefix_options = {}, query_options = nil)
|
805
|
+
URI.join(site, element_path(id, prefix_options, query_options)).to_s
|
657
806
|
end
|
658
807
|
|
659
808
|
# Gets the new element path for REST resources.
|
660
809
|
#
|
661
810
|
# ==== Options
|
662
811
|
# * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
|
663
|
-
#
|
812
|
+
# would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
|
664
813
|
#
|
665
814
|
# ==== Examples
|
666
815
|
# Post.new_element_path
|
667
816
|
# # => /posts/new.json
|
668
817
|
#
|
669
818
|
# class Comment < ActiveResource::Base
|
670
|
-
# self.site = "
|
819
|
+
# self.site = "https://37s.sunrise.com/posts/:post_id"
|
671
820
|
# end
|
672
821
|
#
|
673
822
|
# Comment.collection_path(:post_id => 5)
|
674
823
|
# # => /posts/5/comments/new.json
|
675
824
|
def new_element_path(prefix_options = {})
|
676
|
-
"#{prefix(prefix_options)}#{collection_name}/new
|
825
|
+
"#{prefix(prefix_options)}#{collection_name}/new#{format_extension}"
|
677
826
|
end
|
678
827
|
|
679
|
-
# Gets the collection path for the REST resources.
|
828
|
+
# Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
|
680
829
|
# will split from the +prefix_options+.
|
681
830
|
#
|
682
831
|
# ==== Options
|
@@ -700,7 +849,7 @@ module ActiveResource
|
|
700
849
|
def collection_path(prefix_options = {}, query_options = nil)
|
701
850
|
check_prefix_options(prefix_options)
|
702
851
|
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
703
|
-
"#{prefix(prefix_options)}#{collection_name}
|
852
|
+
"#{prefix(prefix_options)}#{collection_name}#{format_extension}#{query_string(query_options)}"
|
704
853
|
end
|
705
854
|
|
706
855
|
alias_method :set_primary_key, :primary_key= #:nodoc:
|
@@ -714,7 +863,7 @@ module ActiveResource
|
|
714
863
|
# Returns the new resource instance.
|
715
864
|
#
|
716
865
|
def build(attributes = {})
|
717
|
-
attrs = self.format.decode(connection.get("#{new_element_path}").body).merge(attributes)
|
866
|
+
attrs = self.format.decode(connection.get("#{new_element_path(attributes)}", headers).body).merge(attributes)
|
718
867
|
self.new(attrs)
|
719
868
|
end
|
720
869
|
|
@@ -724,8 +873,8 @@ module ActiveResource
|
|
724
873
|
# ryan = Person.new(:first => 'ryan')
|
725
874
|
# ryan.save
|
726
875
|
#
|
727
|
-
# Returns the newly created resource.
|
728
|
-
# exception will be raised (see <tt>save</tt>).
|
876
|
+
# Returns the newly created resource. If a failure has occurred an
|
877
|
+
# exception will be raised (see <tt>save</tt>). If the resource is invalid and
|
729
878
|
# has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
|
730
879
|
# while <tt>new?</tt> will still return <tt>true</tt>.
|
731
880
|
#
|
@@ -746,11 +895,22 @@ module ActiveResource
|
|
746
895
|
self.new(attributes).tap { |resource| resource.save }
|
747
896
|
end
|
748
897
|
|
749
|
-
#
|
898
|
+
# Creates a new resource (just like <tt>create</tt>) and makes a request to the
|
899
|
+
# remote service that it be saved, but runs validations and raises
|
900
|
+
# <tt>ActiveResource::ResourceInvalid</tt>, making it equivalent to the following
|
901
|
+
# simultaneous calls:
|
902
|
+
#
|
903
|
+
# ryan = Person.new(:first => 'ryan')
|
904
|
+
# ryan.save!
|
905
|
+
def create!(attributes = {})
|
906
|
+
self.new(attributes).tap { |resource| resource.save! }
|
907
|
+
end
|
908
|
+
|
909
|
+
# Core method for finding resources. Used similarly to Active Record's +find+ method.
|
750
910
|
#
|
751
911
|
# ==== Arguments
|
752
|
-
# The first argument is considered to be the scope of the query.
|
753
|
-
# resources are returned from the request.
|
912
|
+
# The first argument is considered to be the scope of the query. That is, how many
|
913
|
+
# resources are returned from the request. It can be one of the following.
|
754
914
|
#
|
755
915
|
# * <tt>:one</tt> - Returns a single resource.
|
756
916
|
# * <tt>:first</tt> - Returns the first resource found.
|
@@ -794,9 +954,9 @@ module ActiveResource
|
|
794
954
|
# # => GET /people/1/street_addresses/1.json
|
795
955
|
#
|
796
956
|
# == Failure or missing data
|
797
|
-
#
|
798
|
-
#
|
799
|
-
#
|
957
|
+
# A failure to find the requested object raises a ResourceNotFound
|
958
|
+
# exception if the find was called with an id.
|
959
|
+
# With any other scope, find returns nil when no data is returned.
|
800
960
|
#
|
801
961
|
# Person.find(1)
|
802
962
|
# # => raises ResourceNotFound
|
@@ -810,11 +970,18 @@ module ActiveResource
|
|
810
970
|
options = arguments.slice!(0) || {}
|
811
971
|
|
812
972
|
case scope
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
973
|
+
when :all
|
974
|
+
find_every(options)
|
975
|
+
when :first
|
976
|
+
collection = find_every(options)
|
977
|
+
collection && collection.first
|
978
|
+
when :last
|
979
|
+
collection = find_every(options)
|
980
|
+
collection && collection.last
|
981
|
+
when :one
|
982
|
+
find_one(options)
|
983
|
+
else
|
984
|
+
find_single(scope, options)
|
818
985
|
end
|
819
986
|
end
|
820
987
|
|
@@ -833,12 +1000,17 @@ module ActiveResource
|
|
833
1000
|
find(:last, *args)
|
834
1001
|
end
|
835
1002
|
|
836
|
-
# This is an alias for find(:all).
|
1003
|
+
# This is an alias for find(:all). You can pass in all the same
|
837
1004
|
# arguments to this method as you can to <tt>find(:all)</tt>
|
838
1005
|
def all(*args)
|
839
1006
|
find(:all, *args)
|
840
1007
|
end
|
841
1008
|
|
1009
|
+
def where(clauses = {})
|
1010
|
+
raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
|
1011
|
+
find(:all, params: clauses)
|
1012
|
+
end
|
1013
|
+
|
842
1014
|
|
843
1015
|
# Deletes the resources with the ID in the +id+ parameter.
|
844
1016
|
#
|
@@ -855,7 +1027,7 @@ module ActiveResource
|
|
855
1027
|
# # Let's assume a request to events/5/cancel.json
|
856
1028
|
# Event.delete(params[:id]) # sends DELETE /events/5
|
857
1029
|
def delete(id, options = {})
|
858
|
-
connection.delete(element_path(id, options))
|
1030
|
+
connection.delete(element_path(id, options), headers)
|
859
1031
|
end
|
860
1032
|
|
861
1033
|
# Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
|
@@ -870,7 +1042,7 @@ module ActiveResource
|
|
870
1042
|
prefix_options, query_options = split_options(options[:params])
|
871
1043
|
path = element_path(id, prefix_options, query_options)
|
872
1044
|
response = connection.head(path, headers)
|
873
|
-
response.code.to_i
|
1045
|
+
(200..206).include? response.code.to_i
|
874
1046
|
end
|
875
1047
|
# id && !find_single(id, options).nil?
|
876
1048
|
rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
|
@@ -891,14 +1063,14 @@ module ActiveResource
|
|
891
1063
|
begin
|
892
1064
|
case from = options[:from]
|
893
1065
|
when Symbol
|
894
|
-
instantiate_collection(get(from, options[:params]))
|
1066
|
+
instantiate_collection(get(from, options[:params]), options[:params])
|
895
1067
|
when String
|
896
1068
|
path = "#{from}#{query_string(options[:params])}"
|
897
|
-
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
|
1069
|
+
instantiate_collection(format.decode(connection.get(path, headers).body) || [], options[:params])
|
898
1070
|
else
|
899
1071
|
prefix_options, query_options = split_options(options[:params])
|
900
1072
|
path = collection_path(prefix_options, query_options)
|
901
|
-
instantiate_collection(
|
1073
|
+
instantiate_collection((format.decode(connection.get(path, headers).body) || []), query_options, prefix_options)
|
902
1074
|
end
|
903
1075
|
rescue ActiveResource::ResourceNotFound
|
904
1076
|
# Swallowing ResourceNotFound exceptions and return nil - as per
|
@@ -925,8 +1097,11 @@ module ActiveResource
|
|
925
1097
|
instantiate_record(format.decode(connection.get(path, headers).body), prefix_options)
|
926
1098
|
end
|
927
1099
|
|
928
|
-
def instantiate_collection(collection, prefix_options = {})
|
929
|
-
collection.
|
1100
|
+
def instantiate_collection(collection, original_params = {}, prefix_options = {})
|
1101
|
+
collection_parser.new(collection).tap do |parser|
|
1102
|
+
parser.resource_class = self
|
1103
|
+
parser.original_params = original_params
|
1104
|
+
end.collect! { |record| instantiate_record(record, prefix_options) }
|
930
1105
|
end
|
931
1106
|
|
932
1107
|
def instantiate_record(record, prefix_options = {})
|
@@ -938,12 +1113,12 @@ module ActiveResource
|
|
938
1113
|
|
939
1114
|
# Accepts a URI and creates the site URI from that.
|
940
1115
|
def create_site_uri_from(site)
|
941
|
-
site.is_a?(URI) ? site.dup : URI.
|
1116
|
+
site.is_a?(URI) ? site.dup : URI.parse(site)
|
942
1117
|
end
|
943
1118
|
|
944
1119
|
# Accepts a URI and creates the proxy URI from that.
|
945
1120
|
def create_proxy_uri_from(proxy)
|
946
|
-
proxy.is_a?(URI) ? proxy.dup : URI.
|
1121
|
+
proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
|
947
1122
|
end
|
948
1123
|
|
949
1124
|
# contains a set of the current prefix parameters.
|
@@ -962,8 +1137,8 @@ module ActiveResource
|
|
962
1137
|
prefix_options, query_options = {}, {}
|
963
1138
|
|
964
1139
|
(options || {}).each do |key, value|
|
965
|
-
next if key.blank?
|
966
|
-
(prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
|
1140
|
+
next if key.blank?
|
1141
|
+
(prefix_parameters.include?(key.to_s.to_sym) ? prefix_options : query_options)[key.to_s.to_sym] = value
|
967
1142
|
end
|
968
1143
|
|
969
1144
|
[ prefix_options, query_options ]
|
@@ -984,7 +1159,7 @@ module ActiveResource
|
|
984
1159
|
# gathered from the provided <tt>schema</tt>, or from the attributes
|
985
1160
|
# set on this instance after it has been fetched from the remote system.
|
986
1161
|
def known_attributes
|
987
|
-
self.class.known_attributes + self.attributes.keys.map(&:to_s)
|
1162
|
+
(self.class.known_attributes + self.attributes.keys.map(&:to_s)).uniq
|
988
1163
|
end
|
989
1164
|
|
990
1165
|
|
@@ -1003,7 +1178,7 @@ module ActiveResource
|
|
1003
1178
|
@attributes = {}.with_indifferent_access
|
1004
1179
|
@prefix_options = {}
|
1005
1180
|
@persisted = persisted
|
1006
|
-
load(attributes)
|
1181
|
+
load(attributes, false, persisted)
|
1007
1182
|
end
|
1008
1183
|
|
1009
1184
|
# Returns a \clone of the resource that hasn't been assigned an +id+ yet and
|
@@ -1014,7 +1189,7 @@ module ActiveResource
|
|
1014
1189
|
# not_ryan.new? # => true
|
1015
1190
|
#
|
1016
1191
|
# Any active resource member attributes will NOT be cloned, though all other
|
1017
|
-
# attributes are.
|
1192
|
+
# attributes are. This is to prevent the conflict between any +prefix_options+
|
1018
1193
|
# that refer to the original parent resource and the newly cloned parent
|
1019
1194
|
# resource that does not exist.
|
1020
1195
|
#
|
@@ -1028,13 +1203,13 @@ module ActiveResource
|
|
1028
1203
|
# not_ryan.hash # => {:not => "an ARes instance"}
|
1029
1204
|
def clone
|
1030
1205
|
# Clone all attributes except the pk and any nested ARes
|
1031
|
-
cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
|
1206
|
+
cloned = Hash[attributes.reject { |k, v| k == self.class.primary_key || v.is_a?(ActiveResource::Base) }.map { |k, v| [k, v.clone] }]
|
1032
1207
|
# Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
|
1033
|
-
# attempts to convert hashes into member objects and arrays into collections of objects.
|
1208
|
+
# attempts to convert hashes into member objects and arrays into collections of objects. We want
|
1034
1209
|
# the raw objects to be cloned so we bypass load by directly setting the attributes hash.
|
1035
1210
|
resource = self.class.new({})
|
1036
1211
|
resource.prefix_options = self.prefix_options
|
1037
|
-
resource.send :instance_variable_set,
|
1212
|
+
resource.send :instance_variable_set, "@attributes", cloned
|
1038
1213
|
resource
|
1039
1214
|
end
|
1040
1215
|
|
@@ -1082,7 +1257,7 @@ module ActiveResource
|
|
1082
1257
|
attributes[self.class.primary_key] = id
|
1083
1258
|
end
|
1084
1259
|
|
1085
|
-
# Test for equality.
|
1260
|
+
# Test for equality. Resource are equal if and only if +other+ is the same object or
|
1086
1261
|
# is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
|
1087
1262
|
#
|
1088
1263
|
# ==== Examples
|
@@ -1138,7 +1313,7 @@ module ActiveResource
|
|
1138
1313
|
end
|
1139
1314
|
end
|
1140
1315
|
|
1141
|
-
# Saves (+POST+) or \updates (+PUT+) a resource.
|
1316
|
+
# Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
|
1142
1317
|
# +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
|
1143
1318
|
# is Json for the final object as it looked after the \save (which would include attributes like +created_at+
|
1144
1319
|
# that weren't part of the original submit).
|
@@ -1152,7 +1327,9 @@ module ActiveResource
|
|
1152
1327
|
# my_company.size = 10
|
1153
1328
|
# my_company.save # sends PUT /companies/1 (update)
|
1154
1329
|
def save
|
1155
|
-
|
1330
|
+
run_callbacks :save do
|
1331
|
+
new? ? create : update
|
1332
|
+
end
|
1156
1333
|
end
|
1157
1334
|
|
1158
1335
|
# Saves the resource.
|
@@ -1185,11 +1362,13 @@ module ActiveResource
|
|
1185
1362
|
# new_person.destroy
|
1186
1363
|
# Person.find(new_id) # 404 (Resource Not Found)
|
1187
1364
|
def destroy
|
1188
|
-
|
1365
|
+
run_callbacks :destroy do
|
1366
|
+
connection.delete(element_path, self.class.headers)
|
1367
|
+
end
|
1189
1368
|
end
|
1190
1369
|
|
1191
1370
|
# Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
|
1192
|
-
# found on the remote service.
|
1371
|
+
# found on the remote service. Using this method, you can check for
|
1193
1372
|
# resources that may have been deleted between the object's instantiation
|
1194
1373
|
# and actions on it.
|
1195
1374
|
#
|
@@ -1205,13 +1384,13 @@ module ActiveResource
|
|
1205
1384
|
# Person.delete(guys_id)
|
1206
1385
|
# that_guy.exists? # => false
|
1207
1386
|
def exists?
|
1208
|
-
!new? && self.class.exists?(to_param, :
|
1387
|
+
!new? && self.class.exists?(to_param, params: prefix_options)
|
1209
1388
|
end
|
1210
1389
|
|
1211
1390
|
# Returns the serialized string representation of the resource in the configured
|
1212
1391
|
# serialization format specified in ActiveResource::Base.format. The options
|
1213
1392
|
# applicable depend on the configured encoding format.
|
1214
|
-
def encode(options={})
|
1393
|
+
def encode(options = {})
|
1215
1394
|
send("to_#{self.class.format.extension}", options)
|
1216
1395
|
end
|
1217
1396
|
|
@@ -1227,11 +1406,11 @@ module ActiveResource
|
|
1227
1406
|
# my_branch.reload
|
1228
1407
|
# my_branch.name # => "Wilson Road"
|
1229
1408
|
def reload
|
1230
|
-
self.load(self.class.find(to_param, :
|
1409
|
+
self.load(self.class.find(to_param, params: @prefix_options).attributes, false, true)
|
1231
1410
|
end
|
1232
1411
|
|
1233
1412
|
# A method to manually load attributes from a \hash. Recursively loads collections of
|
1234
|
-
# resources.
|
1413
|
+
# resources. This method is called in +initialize+ and +create+ when a \hash of attributes
|
1235
1414
|
# is provided.
|
1236
1415
|
#
|
1237
1416
|
# ==== Examples
|
@@ -1251,8 +1430,12 @@ module ActiveResource
|
|
1251
1430
|
# your_supplier = Supplier.new
|
1252
1431
|
# your_supplier.load(my_attrs)
|
1253
1432
|
# your_supplier.save
|
1254
|
-
def load(attributes, remove_root = false)
|
1255
|
-
|
1433
|
+
def load(attributes, remove_root = false, persisted = false)
|
1434
|
+
unless attributes.respond_to?(:to_hash)
|
1435
|
+
raise ArgumentError, "expected attributes to be able to convert to Hash, got #{attributes.inspect}"
|
1436
|
+
end
|
1437
|
+
|
1438
|
+
attributes = attributes.to_hash
|
1256
1439
|
@prefix_options, attributes = split_options(attributes)
|
1257
1440
|
|
1258
1441
|
if attributes.keys.size == 1
|
@@ -1264,21 +1447,21 @@ module ActiveResource
|
|
1264
1447
|
attributes.each do |key, value|
|
1265
1448
|
@attributes[key.to_s] =
|
1266
1449
|
case value
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
end
|
1450
|
+
when Array
|
1451
|
+
resource = nil
|
1452
|
+
value.map do |attrs|
|
1453
|
+
if attrs.is_a?(Hash)
|
1454
|
+
resource ||= find_or_create_resource_for_collection(key)
|
1455
|
+
resource.new(attrs, persisted)
|
1456
|
+
else
|
1457
|
+
attrs.duplicable? ? attrs.dup : attrs
|
1276
1458
|
end
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1459
|
+
end
|
1460
|
+
when Hash
|
1461
|
+
resource = find_or_create_resource_for(key)
|
1462
|
+
resource.new(value, persisted)
|
1463
|
+
else
|
1464
|
+
value.duplicable? ? value.dup : value
|
1282
1465
|
end
|
1283
1466
|
end
|
1284
1467
|
self
|
@@ -1286,14 +1469,14 @@ module ActiveResource
|
|
1286
1469
|
|
1287
1470
|
# Updates a single attribute and then saves the object.
|
1288
1471
|
#
|
1289
|
-
# Note: Unlike ActiveRecord::Base.update_attribute
|
1472
|
+
# Note: <tt>Unlike ActiveRecord::Base.update_attribute</tt>, this method <b>is</b>
|
1290
1473
|
# subject to normal validation routines as an update sends the whole body
|
1291
|
-
# of the resource in the request.
|
1474
|
+
# of the resource in the request. (See Validations).
|
1292
1475
|
#
|
1293
1476
|
# As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
|
1294
1477
|
#
|
1295
1478
|
# If the saving fails because of a connection or remote service error, an
|
1296
|
-
# exception will be raised.
|
1479
|
+
# exception will be raised. If saving fails because the resource is
|
1297
1480
|
# invalid then <tt>false</tt> will be returned.
|
1298
1481
|
def update_attribute(name, value)
|
1299
1482
|
self.send("#{name}=".to_sym, value)
|
@@ -1304,7 +1487,7 @@ module ActiveResource
|
|
1304
1487
|
# and requests that the record be saved.
|
1305
1488
|
#
|
1306
1489
|
# If the saving fails because of a connection or remote service error, an
|
1307
|
-
# exception will be raised.
|
1490
|
+
# exception will be raised. If saving fails because the resource is
|
1308
1491
|
# invalid then <tt>false</tt> will be returned.
|
1309
1492
|
#
|
1310
1493
|
# Note: Though this request can be made with a partial set of the
|
@@ -1320,7 +1503,7 @@ module ActiveResource
|
|
1320
1503
|
# A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
|
1321
1504
|
# +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
|
1322
1505
|
# <tt>my_person.respond_to?(:name?)</tt>.
|
1323
|
-
def
|
1506
|
+
def respond_to_missing?(method, include_priv = false)
|
1324
1507
|
method_name = method.to_s
|
1325
1508
|
if attributes.nil?
|
1326
1509
|
super
|
@@ -1335,12 +1518,20 @@ module ActiveResource
|
|
1335
1518
|
end
|
1336
1519
|
end
|
1337
1520
|
|
1338
|
-
def to_json(options={})
|
1339
|
-
super(include_root_in_json ? { :
|
1521
|
+
def to_json(options = {})
|
1522
|
+
super(include_root_in_json ? { root: self.class.element_name }.merge(options) : options)
|
1340
1523
|
end
|
1341
1524
|
|
1342
|
-
def to_xml(options={})
|
1343
|
-
super({ :
|
1525
|
+
def to_xml(options = {})
|
1526
|
+
super({ root: self.class.element_name }.merge(options))
|
1527
|
+
end
|
1528
|
+
|
1529
|
+
def read_attribute_for_serialization(n)
|
1530
|
+
if !attributes[n].nil?
|
1531
|
+
attributes[n]
|
1532
|
+
elsif respond_to?(n)
|
1533
|
+
send(n)
|
1534
|
+
end
|
1344
1535
|
end
|
1345
1536
|
|
1346
1537
|
protected
|
@@ -1350,37 +1541,45 @@ module ActiveResource
|
|
1350
1541
|
|
1351
1542
|
# Update the resource on the remote service.
|
1352
1543
|
def update
|
1353
|
-
|
1354
|
-
|
1544
|
+
run_callbacks :update do
|
1545
|
+
connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
|
1546
|
+
load_attributes_from_response(response)
|
1547
|
+
end
|
1355
1548
|
end
|
1356
1549
|
end
|
1357
1550
|
|
1358
1551
|
# Create (i.e., \save to the remote service) the \new resource.
|
1359
1552
|
def create
|
1360
|
-
|
1361
|
-
self.
|
1362
|
-
|
1553
|
+
run_callbacks :create do
|
1554
|
+
connection.post(collection_path, encode, self.class.headers).tap do |response|
|
1555
|
+
self.id = id_from_response(response)
|
1556
|
+
load_attributes_from_response(response)
|
1557
|
+
end
|
1363
1558
|
end
|
1364
1559
|
end
|
1365
1560
|
|
1366
1561
|
def load_attributes_from_response(response)
|
1367
|
-
if
|
1368
|
-
(response[
|
1369
|
-
!response.body.nil? && response.body.strip.size > 0
|
1370
|
-
load(self.class.format.decode(response.body), true)
|
1562
|
+
if response_code_allows_body?(response.code.to_i) &&
|
1563
|
+
(response["Content-Length"].nil? || response["Content-Length"] != "0") &&
|
1564
|
+
!response.body.nil? && response.body.strip.size > 0
|
1565
|
+
load(self.class.format.decode(response.body), true, true)
|
1371
1566
|
@persisted = true
|
1372
1567
|
end
|
1373
1568
|
end
|
1374
1569
|
|
1375
1570
|
# Takes a response from a typical create post and pulls the ID out
|
1376
1571
|
def id_from_response(response)
|
1377
|
-
response[
|
1572
|
+
response["Location"][/\/([^\/]*?)(\.\w+)?$/, 1] if response["Location"]
|
1378
1573
|
end
|
1379
1574
|
|
1380
1575
|
def element_path(options = nil)
|
1381
1576
|
self.class.element_path(to_param, options || prefix_options)
|
1382
1577
|
end
|
1383
1578
|
|
1579
|
+
def element_url(options = nil)
|
1580
|
+
self.class.element_url(to_param, options || prefix_options)
|
1581
|
+
end
|
1582
|
+
|
1384
1583
|
def new_element_path
|
1385
1584
|
self.class.new_element_path(prefix_options)
|
1386
1585
|
end
|
@@ -1391,17 +1590,14 @@ module ActiveResource
|
|
1391
1590
|
|
1392
1591
|
private
|
1393
1592
|
|
1394
|
-
def read_attribute_for_serialization(n)
|
1395
|
-
attributes[n]
|
1396
|
-
end
|
1397
|
-
|
1398
1593
|
# Determine whether the response is allowed to have a body per HTTP 1.1 spec section 4.4.1
|
1399
1594
|
def response_code_allows_body?(c)
|
1400
|
-
!((100..199).include?(c) || [204,304].include?(c))
|
1595
|
+
!((100..199).include?(c) || [204, 304].include?(c))
|
1401
1596
|
end
|
1402
1597
|
|
1403
1598
|
# Tries to find a resource for a given collection name; if it fails, then the resource is created
|
1404
1599
|
def find_or_create_resource_for_collection(name)
|
1600
|
+
return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
|
1405
1601
|
find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
|
1406
1602
|
end
|
1407
1603
|
|
@@ -1409,10 +1605,10 @@ module ActiveResource
|
|
1409
1605
|
# if it fails, then the resource is created
|
1410
1606
|
def find_or_create_resource_in_modules(resource_name, module_names)
|
1411
1607
|
receiver = Object
|
1412
|
-
namespaces = module_names[0, module_names.size-1].map do |module_name|
|
1608
|
+
namespaces = module_names[0, module_names.size - 1].map do |module_name|
|
1413
1609
|
receiver = receiver.const_get(module_name)
|
1414
1610
|
end
|
1415
|
-
const_args =
|
1611
|
+
const_args = [resource_name, false]
|
1416
1612
|
if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
|
1417
1613
|
namespace.const_get(*const_args)
|
1418
1614
|
else
|
@@ -1422,13 +1618,18 @@ module ActiveResource
|
|
1422
1618
|
|
1423
1619
|
# Tries to find a resource for a given name; if it fails, then the resource is created
|
1424
1620
|
def find_or_create_resource_for(name)
|
1621
|
+
return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
|
1425
1622
|
resource_name = name.to_s.camelize
|
1426
1623
|
|
1427
|
-
const_args =
|
1428
|
-
|
1624
|
+
const_args = [resource_name, false]
|
1625
|
+
|
1626
|
+
if !const_valid?(*const_args)
|
1627
|
+
# resource_name is not a valid ruby module name and cannot be created normally
|
1628
|
+
find_or_create_resource_for(:UnnamedResource)
|
1629
|
+
elsif self.class.const_defined?(*const_args)
|
1429
1630
|
self.class.const_get(*const_args)
|
1430
1631
|
else
|
1431
|
-
ancestors = self.class.name.split("::")
|
1632
|
+
ancestors = self.class.name.to_s.split("::")
|
1432
1633
|
if ancestors.size > 1
|
1433
1634
|
find_or_create_resource_in_modules(resource_name, ancestors)
|
1434
1635
|
else
|
@@ -1441,6 +1642,13 @@ module ActiveResource
|
|
1441
1642
|
end
|
1442
1643
|
end
|
1443
1644
|
|
1645
|
+
def const_valid?(*const_args)
|
1646
|
+
self.class.const_defined?(*const_args)
|
1647
|
+
true
|
1648
|
+
rescue NameError
|
1649
|
+
false
|
1650
|
+
end
|
1651
|
+
|
1444
1652
|
# Create and return a class definition for a resource inside the current resource
|
1445
1653
|
def create_resource_for(resource_name)
|
1446
1654
|
resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
|
@@ -1474,9 +1682,14 @@ module ActiveResource
|
|
1474
1682
|
|
1475
1683
|
class Base
|
1476
1684
|
extend ActiveModel::Naming
|
1477
|
-
|
1685
|
+
extend ActiveResource::Associations
|
1686
|
+
|
1687
|
+
include Callbacks, CustomMethods, Validations
|
1478
1688
|
include ActiveModel::Conversion
|
1479
1689
|
include ActiveModel::Serializers::JSON
|
1480
1690
|
include ActiveModel::Serializers::Xml
|
1691
|
+
include ActiveResource::Reflection
|
1481
1692
|
end
|
1693
|
+
|
1694
|
+
ActiveSupport.run_load_hooks(:active_resource, Base)
|
1482
1695
|
end
|