activeresource 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeresource might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.rdoc +53 -48
- data/lib/active_resource.rb +4 -7
- data/lib/active_resource/associations.rb +168 -0
- data/lib/active_resource/associations/builder/association.rb +32 -0
- data/lib/active_resource/associations/builder/belongs_to.rb +14 -0
- data/lib/active_resource/associations/builder/has_many.rb +12 -0
- data/lib/active_resource/associations/builder/has_one.rb +12 -0
- data/lib/active_resource/base.rb +213 -132
- data/lib/active_resource/callbacks.rb +20 -0
- data/lib/active_resource/collection.rb +85 -0
- data/lib/active_resource/connection.rb +28 -30
- data/lib/active_resource/custom_methods.rb +18 -10
- data/lib/active_resource/http_mock.rb +11 -14
- data/lib/active_resource/observing.rb +2 -0
- data/lib/active_resource/reflection.rb +77 -0
- data/lib/active_resource/schema.rb +0 -2
- data/lib/active_resource/singleton.rb +114 -0
- data/lib/active_resource/validations.rb +46 -7
- data/lib/active_resource/version.rb +4 -4
- metadata +69 -22
- data/CHANGELOG.md +0 -437
- data/MIT-LICENSE +0 -20
- data/examples/performance.rb +0 -70
@@ -0,0 +1,14 @@
|
|
1
|
+
module ActiveResource::Associations::Builder
|
2
|
+
class BelongsTo < Association
|
3
|
+
self.valid_options += [:foreign_key]
|
4
|
+
|
5
|
+
self.macro = :belongs_to
|
6
|
+
|
7
|
+
def build
|
8
|
+
validate_options
|
9
|
+
reflection = model.create_reflection(self.class.macro, name, options)
|
10
|
+
model.defines_belongs_to_finder_method(reflection.name, reflection.klass, reflection.foreign_key)
|
11
|
+
return reflection
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveResource::Associations::Builder
|
2
|
+
class HasMany < Association
|
3
|
+
self.macro = :has_many
|
4
|
+
|
5
|
+
def build
|
6
|
+
validate_options
|
7
|
+
model.create_reflection(self.class.macro, name, options).tap do |reflection|
|
8
|
+
model.defines_has_many_finder_method(reflection.name, reflection.klass)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveResource::Associations::Builder
|
2
|
+
class HasOne < Association
|
3
|
+
self.macro = :has_one
|
4
|
+
|
5
|
+
def build
|
6
|
+
validate_options
|
7
|
+
model.create_reflection(self.class.macro, name, options).tap do |reflection|
|
8
|
+
model.defines_has_one_finder_method(reflection.name, reflection.klass)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/active_resource/base.rb
CHANGED
@@ -16,6 +16,8 @@ require 'active_resource/connection'
|
|
16
16
|
require 'active_resource/formats'
|
17
17
|
require 'active_resource/schema'
|
18
18
|
require 'active_resource/log_subscriber'
|
19
|
+
require 'active_resource/associations'
|
20
|
+
require 'active_resource/reflection'
|
19
21
|
|
20
22
|
module ActiveResource
|
21
23
|
# ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
|
@@ -24,29 +26,29 @@ module ActiveResource
|
|
24
26
|
#
|
25
27
|
# == Automated mapping
|
26
28
|
#
|
27
|
-
# Active Resource objects represent your RESTful resources as manipulatable Ruby objects.
|
29
|
+
# Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
|
28
30
|
# to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
|
29
31
|
# Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
|
30
32
|
# URI of the resources.
|
31
33
|
#
|
32
34
|
# class Person < ActiveResource::Base
|
33
|
-
# self.site = "
|
35
|
+
# self.site = "https://api.people.com"
|
34
36
|
# end
|
35
37
|
#
|
36
|
-
# Now the Person class is mapped to RESTful resources located at <tt>
|
38
|
+
# Now the Person class is mapped to RESTful resources located at <tt>https://api.people.com/people/</tt>, and
|
37
39
|
# you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have
|
38
40
|
# an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
|
39
41
|
#
|
40
42
|
# class PersonResource < ActiveResource::Base
|
41
|
-
# self.site = "
|
43
|
+
# self.site = "https://api.people.com"
|
42
44
|
# self.element_name = "person"
|
43
45
|
# end
|
44
46
|
#
|
45
47
|
# If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
|
46
48
|
#
|
47
49
|
# class PersonResource < ActiveResource::Base
|
48
|
-
# self.site = "
|
49
|
-
# self.proxy = "
|
50
|
+
# self.site = "https://api.people.com"
|
51
|
+
# self.proxy = "https://user:password@proxy.people.com:8080"
|
50
52
|
# end
|
51
53
|
#
|
52
54
|
#
|
@@ -102,7 +104,7 @@ module ActiveResource
|
|
102
104
|
# You can validate resources client side by overriding validation methods in the base class.
|
103
105
|
#
|
104
106
|
# class Person < ActiveResource::Base
|
105
|
-
# self.site = "
|
107
|
+
# self.site = "https://api.people.com"
|
106
108
|
# protected
|
107
109
|
# def validate
|
108
110
|
# errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
|
@@ -113,47 +115,64 @@ module ActiveResource
|
|
113
115
|
#
|
114
116
|
# == Authentication
|
115
117
|
#
|
116
|
-
# Many REST APIs
|
117
|
-
#
|
118
|
+
# Many REST APIs require authentication. The HTTP spec describes two ways to
|
119
|
+
# make requests with a username and password (see RFC 2617).
|
118
120
|
#
|
119
|
-
#
|
120
|
-
#
|
121
|
+
# Basic authentication simply sends a username and password along with HTTP
|
122
|
+
# requests. These sensitive credentials are sent unencrypted, visible to
|
123
|
+
# any onlooker, so this scheme should only be used with SSL.
|
124
|
+
#
|
125
|
+
# Digest authentication sends a crytographic hash of the username, password,
|
126
|
+
# HTTP method, URI, and a single-use secret key provided by the server.
|
127
|
+
# Sensitive credentials aren't visible to onlookers, so digest authentication
|
128
|
+
# doesn't require SSL. However, this doesn't mean the connection is secure!
|
129
|
+
# Just the username and password.
|
130
|
+
#
|
131
|
+
# (You really, really want to use SSL. There's little reason not to.)
|
132
|
+
#
|
133
|
+
# === Picking an authentication scheme
|
134
|
+
#
|
135
|
+
# Basic authentication is the default. To switch to digest authentication,
|
136
|
+
# set +auth_type+ to +:digest+:
|
121
137
|
#
|
122
138
|
# class Person < ActiveResource::Base
|
123
|
-
# self.
|
139
|
+
# self.auth_type = :digest
|
124
140
|
# end
|
125
141
|
#
|
126
|
-
#
|
142
|
+
# === Setting the username and password
|
143
|
+
#
|
144
|
+
# Set +user+ and +password+ on the class, or include them in the +site+ URL.
|
127
145
|
#
|
128
146
|
# class Person < ActiveResource::Base
|
129
|
-
#
|
147
|
+
# # Set user and password directly:
|
130
148
|
# self.user = "ryan"
|
131
149
|
# self.password = "password"
|
132
|
-
# end
|
133
150
|
#
|
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.
|
151
|
+
# # Or include them in the site:
|
152
|
+
# self.site = "https://ryan:password@api.people.com"
|
153
|
+
# end
|
139
154
|
#
|
140
155
|
# === Certificate Authentication
|
141
156
|
#
|
142
|
-
#
|
157
|
+
# You can also authenticate using an X509 certificate. <tt>See ssl_options=</tt> for all options.
|
143
158
|
#
|
144
159
|
# class Person < ActiveResource::Base
|
145
160
|
# self.site = "https://secure.api.people.com/"
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
161
|
+
#
|
162
|
+
# File.open(pem_file_path, 'rb') do |pem_file|
|
163
|
+
# self.ssl_options = {
|
164
|
+
# cert: OpenSSL::X509::Certificate.new(pem_file),
|
165
|
+
# key: OpenSSL::PKey::RSA.new(pem_file),
|
166
|
+
# ca_path: "/path/to/OpenSSL/formatted/CA_Certs",
|
167
|
+
# verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
168
|
+
# end
|
150
169
|
# end
|
151
170
|
#
|
152
171
|
#
|
153
172
|
# == Errors & Validation
|
154
173
|
#
|
155
174
|
# Error handling and validation is handled in much the same manner as you're used to seeing in
|
156
|
-
# Active Record.
|
175
|
+
# Active Record. Both the response code in the HTTP response and the body of the response are used to
|
157
176
|
# indicate that an error occurred.
|
158
177
|
#
|
159
178
|
# === Resource errors
|
@@ -162,7 +181,7 @@ module ActiveResource
|
|
162
181
|
# response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
|
163
182
|
# exception.
|
164
183
|
#
|
165
|
-
# # GET
|
184
|
+
# # GET https://api.people.com/people/999.json
|
166
185
|
# ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
|
167
186
|
#
|
168
187
|
#
|
@@ -184,7 +203,7 @@ module ActiveResource
|
|
184
203
|
# * Other - ActiveResource::ConnectionError
|
185
204
|
#
|
186
205
|
# These custom exceptions allow you to deal with resource errors more naturally and with more precision
|
187
|
-
# rather than returning a general HTTP error.
|
206
|
+
# rather than returning a general HTTP error. For example:
|
188
207
|
#
|
189
208
|
# begin
|
190
209
|
# ryan = Person.find(my_id)
|
@@ -198,7 +217,7 @@ module ActiveResource
|
|
198
217
|
# an ActiveResource::MissingPrefixParam will be raised.
|
199
218
|
#
|
200
219
|
# class Comment < ActiveResource::Base
|
201
|
-
# self.site = "
|
220
|
+
# self.site = "https://someip.com/posts/:post_id"
|
202
221
|
# end
|
203
222
|
#
|
204
223
|
# Comment.find(1)
|
@@ -207,8 +226,8 @@ module ActiveResource
|
|
207
226
|
# === Validation errors
|
208
227
|
#
|
209
228
|
# 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
|
229
|
+
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
|
230
|
+
# a response code of <tt>422</tt> and an JSON or XML representation of the validation errors. The save operation will
|
212
231
|
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
|
213
232
|
#
|
214
233
|
# ryan = Person.find(1)
|
@@ -216,20 +235,29 @@ module ActiveResource
|
|
216
235
|
# ryan.save # => false
|
217
236
|
#
|
218
237
|
# # When
|
219
|
-
# # PUT
|
238
|
+
# # PUT https://api.people.com/people/1.xml
|
220
239
|
# # or
|
221
|
-
# # PUT
|
240
|
+
# # PUT https://api.people.com/people/1.json
|
222
241
|
# # is requested with invalid values, the response is:
|
223
242
|
# #
|
224
243
|
# # Response (422):
|
225
244
|
# # <errors><error>First cannot be empty</error></errors>
|
226
245
|
# # or
|
227
|
-
# # {"errors":["
|
246
|
+
# # {"errors":{"first":["cannot be empty"]}}
|
228
247
|
# #
|
229
248
|
#
|
230
249
|
# ryan.errors.invalid?(:first) # => true
|
231
250
|
# ryan.errors.full_messages # => ['First cannot be empty']
|
232
251
|
#
|
252
|
+
# For backwards-compatibility with older endpoints, the following formats are also supported in JSON responses:
|
253
|
+
#
|
254
|
+
# # {"errors":['First cannot be empty']}
|
255
|
+
# # This was the required format for previous versions of ActiveResource
|
256
|
+
# # {"first":["cannot be empty"]}
|
257
|
+
# # This was the default format produced by respond_with in ActionController <3.2.1
|
258
|
+
#
|
259
|
+
# Parsing either of these formats will result in a deprecation warning.
|
260
|
+
#
|
233
261
|
# Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
|
234
262
|
#
|
235
263
|
# === Timeouts
|
@@ -239,7 +267,7 @@ module ActiveResource
|
|
239
267
|
# amount of time before Active Resource times out with the +timeout+ variable.
|
240
268
|
#
|
241
269
|
# class Person < ActiveResource::Base
|
242
|
-
# self.site = "
|
270
|
+
# self.site = "https://api.people.com"
|
243
271
|
# self.timeout = 5
|
244
272
|
# end
|
245
273
|
#
|
@@ -262,6 +290,9 @@ module ActiveResource
|
|
262
290
|
cattr_accessor :logger
|
263
291
|
|
264
292
|
class_attribute :_format
|
293
|
+
class_attribute :_collection_parser
|
294
|
+
class_attribute :include_format_in_path
|
295
|
+
self.include_format_in_path = true
|
265
296
|
|
266
297
|
class << self
|
267
298
|
# Creates a schema for this resource - setting the attributes that are
|
@@ -276,39 +307,39 @@ module ActiveResource
|
|
276
307
|
# remote system.
|
277
308
|
#
|
278
309
|
# example:
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
282
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
310
|
+
# class Person < ActiveResource::Base
|
311
|
+
# schema do
|
312
|
+
# # define each attribute separately
|
313
|
+
# attribute 'name', :string
|
314
|
+
#
|
315
|
+
# # or use the convenience methods and pass >=1 attribute names
|
316
|
+
# string 'eye_color', 'hair_color'
|
317
|
+
# integer 'age'
|
318
|
+
# float 'height', 'weight'
|
319
|
+
#
|
320
|
+
# # unsupported types should be left as strings
|
321
|
+
# # overload the accessor methods if you need to convert them
|
322
|
+
# attribute 'created_at', 'string'
|
323
|
+
# end
|
292
324
|
# end
|
293
|
-
# end
|
294
325
|
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
299
|
-
#
|
326
|
+
# p = Person.new
|
327
|
+
# p.respond_to? :name # => true
|
328
|
+
# p.respond_to? :age # => true
|
329
|
+
# p.name # => nil
|
330
|
+
# p.age # => nil
|
300
331
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
#
|
332
|
+
# j = Person.find_by_name('John')
|
333
|
+
# <person><name>John</name><age>34</age><num_children>3</num_children></person>
|
334
|
+
# j.respond_to? :name # => true
|
335
|
+
# j.respond_to? :age # => true
|
336
|
+
# j.name # => 'John'
|
337
|
+
# j.age # => '34' # note this is a string!
|
338
|
+
# j.num_children # => '3' # note this is a string!
|
307
339
|
#
|
308
|
-
#
|
340
|
+
# p.num_children # => NoMethodError
|
309
341
|
#
|
310
|
-
# Attribute-types must be one of:
|
311
|
-
# string, integer, float
|
342
|
+
# Attribute-types must be one of: <tt>string, integer, float</tt>
|
312
343
|
#
|
313
344
|
# Note: at present the attribute-type doesn't do anything, but stay
|
314
345
|
# tuned...
|
@@ -349,9 +380,9 @@ module ActiveResource
|
|
349
380
|
#
|
350
381
|
# example:
|
351
382
|
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
383
|
+
# class Person < ActiveResource::Base
|
384
|
+
# schema = {'name' => :string, 'age' => :integer }
|
385
|
+
# end
|
355
386
|
#
|
356
387
|
# The keys/values can be strings or symbols. They will be converted to
|
357
388
|
# strings.
|
@@ -375,29 +406,29 @@ module ActiveResource
|
|
375
406
|
# from the provided <tt>schema</tt>
|
376
407
|
# Attributes that are known will cause your resource to return 'true'
|
377
408
|
# when <tt>respond_to?</tt> is called on them. A known attribute will
|
378
|
-
# return nil if not set (rather than <
|
409
|
+
# return nil if not set (rather than <tt>MethodNotFound</tt>); thus
|
379
410
|
# known attributes can be used with <tt>validates_presence_of</tt>
|
380
411
|
# without a getter-method.
|
381
412
|
def known_attributes
|
382
413
|
@known_attributes ||= []
|
383
414
|
end
|
384
415
|
|
385
|
-
# Gets the URI of the REST resources to map for this class.
|
416
|
+
# Gets the URI of the REST resources to map for this class. The site variable is required for
|
386
417
|
# Active Resource's mapping to work.
|
387
418
|
def site
|
388
419
|
# Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
|
389
420
|
#
|
390
421
|
# With superclass_delegating_reader
|
391
422
|
#
|
392
|
-
# Parent.site = '
|
393
|
-
# Subclass.site # => '
|
423
|
+
# Parent.site = 'https://anonymous@test.com'
|
424
|
+
# Subclass.site # => 'https://anonymous@test.com'
|
394
425
|
# Subclass.site.user = 'david'
|
395
|
-
# Parent.site # => '
|
426
|
+
# Parent.site # => 'https://david@test.com'
|
396
427
|
#
|
397
428
|
# Without superclass_delegating_reader (expected behavior)
|
398
429
|
#
|
399
|
-
# Parent.site = '
|
400
|
-
# Subclass.site # => '
|
430
|
+
# Parent.site = 'https://anonymous@test.com'
|
431
|
+
# Subclass.site # => 'https://anonymous@test.com'
|
401
432
|
# Subclass.site.user = 'david' # => TypeError: can't modify frozen object
|
402
433
|
#
|
403
434
|
if defined?(@site)
|
@@ -501,6 +532,16 @@ module ActiveResource
|
|
501
532
|
self._format || ActiveResource::Formats::JsonFormat
|
502
533
|
end
|
503
534
|
|
535
|
+
# Sets the parser to use when a collection is returned. The parser must be Enumerable.
|
536
|
+
def collection_parser=(parser_instance)
|
537
|
+
parser_instance = parser_instance.constantize if parser_instance.is_a?(String)
|
538
|
+
self._collection_parser = parser_instance
|
539
|
+
end
|
540
|
+
|
541
|
+
def collection_parser
|
542
|
+
self._collection_parser || ActiveResource::Collection
|
543
|
+
end
|
544
|
+
|
504
545
|
# Sets the number of seconds after which requests to the REST API should time out.
|
505
546
|
def timeout=(timeout)
|
506
547
|
@connection = nil
|
@@ -527,9 +568,9 @@ module ActiveResource
|
|
527
568
|
# * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
|
528
569
|
# * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
|
529
570
|
# * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
|
530
|
-
def ssl_options=(
|
571
|
+
def ssl_options=(options)
|
531
572
|
@connection = nil
|
532
|
-
@ssl_options =
|
573
|
+
@ssl_options = options
|
533
574
|
end
|
534
575
|
|
535
576
|
# Returns the SSL options hash.
|
@@ -561,6 +602,12 @@ module ActiveResource
|
|
561
602
|
|
562
603
|
def headers
|
563
604
|
@headers ||= {}
|
605
|
+
|
606
|
+
if superclass != Object && superclass.headers
|
607
|
+
@headers = superclass.headers.merge(@headers)
|
608
|
+
else
|
609
|
+
@headers
|
610
|
+
end
|
564
611
|
end
|
565
612
|
|
566
613
|
attr_writer :element_name
|
@@ -578,7 +625,15 @@ module ActiveResource
|
|
578
625
|
attr_writer :primary_key
|
579
626
|
|
580
627
|
def primary_key
|
581
|
-
@primary_key
|
628
|
+
if defined?(@primary_key)
|
629
|
+
@primary_key
|
630
|
+
elsif superclass != Object && superclass.primary_key
|
631
|
+
primary_key = superclass.primary_key
|
632
|
+
return primary_key if primary_key.is_a?(Symbol)
|
633
|
+
primary_key.dup.freeze
|
634
|
+
else
|
635
|
+
'id'
|
636
|
+
end
|
582
637
|
end
|
583
638
|
|
584
639
|
# Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
|
@@ -591,7 +646,7 @@ module ActiveResource
|
|
591
646
|
prefix(options)
|
592
647
|
end
|
593
648
|
|
594
|
-
# An attribute reader for the source string for the resource path \prefix.
|
649
|
+
# An attribute reader for the source string for the resource path \prefix. This
|
595
650
|
# method is regenerated at runtime based on what the \prefix is set to.
|
596
651
|
def prefix_source
|
597
652
|
prefix # generate #prefix and #prefix_source methods first
|
@@ -624,12 +679,17 @@ module ActiveResource
|
|
624
679
|
alias_method :set_element_name, :element_name= #:nodoc:
|
625
680
|
alias_method :set_collection_name, :collection_name= #:nodoc:
|
626
681
|
|
627
|
-
|
682
|
+
def format_extension
|
683
|
+
include_format_in_path ? ".#{format.extension}" : ""
|
684
|
+
end
|
685
|
+
|
686
|
+
# Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
|
628
687
|
# will split from the \prefix options.
|
629
688
|
#
|
630
689
|
# ==== Options
|
631
690
|
# +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
|
632
|
-
#
|
691
|
+
# would yield a URL like <tt>/accounts/19/purchases.json</tt>).
|
692
|
+
#
|
633
693
|
# +query_options+ - A \hash to add items to the query string for the request.
|
634
694
|
#
|
635
695
|
# ==== Examples
|
@@ -637,7 +697,7 @@ module ActiveResource
|
|
637
697
|
# # => /posts/1.json
|
638
698
|
#
|
639
699
|
# class Comment < ActiveResource::Base
|
640
|
-
# self.site = "
|
700
|
+
# self.site = "https://37s.sunrise.com/posts/:post_id"
|
641
701
|
# end
|
642
702
|
#
|
643
703
|
# Comment.element_path(1, :post_id => 5)
|
@@ -653,30 +713,30 @@ module ActiveResource
|
|
653
713
|
check_prefix_options(prefix_options)
|
654
714
|
|
655
715
|
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
656
|
-
"#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}
|
716
|
+
"#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}#{format_extension}#{query_string(query_options)}"
|
657
717
|
end
|
658
718
|
|
659
719
|
# Gets the new element path for REST resources.
|
660
720
|
#
|
661
721
|
# ==== Options
|
662
722
|
# * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
|
663
|
-
#
|
723
|
+
# would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
|
664
724
|
#
|
665
725
|
# ==== Examples
|
666
726
|
# Post.new_element_path
|
667
727
|
# # => /posts/new.json
|
668
728
|
#
|
669
729
|
# class Comment < ActiveResource::Base
|
670
|
-
# self.site = "
|
730
|
+
# self.site = "https://37s.sunrise.com/posts/:post_id"
|
671
731
|
# end
|
672
732
|
#
|
673
733
|
# Comment.collection_path(:post_id => 5)
|
674
734
|
# # => /posts/5/comments/new.json
|
675
735
|
def new_element_path(prefix_options = {})
|
676
|
-
"#{prefix(prefix_options)}#{collection_name}/new
|
736
|
+
"#{prefix(prefix_options)}#{collection_name}/new#{format_extension}"
|
677
737
|
end
|
678
738
|
|
679
|
-
# Gets the collection path for the REST resources.
|
739
|
+
# Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
|
680
740
|
# will split from the +prefix_options+.
|
681
741
|
#
|
682
742
|
# ==== Options
|
@@ -700,7 +760,7 @@ module ActiveResource
|
|
700
760
|
def collection_path(prefix_options = {}, query_options = nil)
|
701
761
|
check_prefix_options(prefix_options)
|
702
762
|
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
703
|
-
"#{prefix(prefix_options)}#{collection_name}
|
763
|
+
"#{prefix(prefix_options)}#{collection_name}#{format_extension}#{query_string(query_options)}"
|
704
764
|
end
|
705
765
|
|
706
766
|
alias_method :set_primary_key, :primary_key= #:nodoc:
|
@@ -714,7 +774,7 @@ module ActiveResource
|
|
714
774
|
# Returns the new resource instance.
|
715
775
|
#
|
716
776
|
def build(attributes = {})
|
717
|
-
attrs = self.format.decode(connection.get("#{new_element_path}").body)
|
777
|
+
attrs = self.format.decode(connection.get("#{new_element_path(attributes)}", headers).body)
|
718
778
|
self.new(attrs)
|
719
779
|
end
|
720
780
|
|
@@ -724,8 +784,8 @@ module ActiveResource
|
|
724
784
|
# ryan = Person.new(:first => 'ryan')
|
725
785
|
# ryan.save
|
726
786
|
#
|
727
|
-
# Returns the newly created resource.
|
728
|
-
# exception will be raised (see <tt>save</tt>).
|
787
|
+
# Returns the newly created resource. If a failure has occurred an
|
788
|
+
# exception will be raised (see <tt>save</tt>). If the resource is invalid and
|
729
789
|
# has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
|
730
790
|
# while <tt>new?</tt> will still return <tt>true</tt>.
|
731
791
|
#
|
@@ -746,11 +806,11 @@ module ActiveResource
|
|
746
806
|
self.new(attributes).tap { |resource| resource.save }
|
747
807
|
end
|
748
808
|
|
749
|
-
# Core method for finding resources.
|
809
|
+
# Core method for finding resources. Used similarly to Active Record's +find+ method.
|
750
810
|
#
|
751
811
|
# ==== Arguments
|
752
|
-
# The first argument is considered to be the scope of the query.
|
753
|
-
# resources are returned from the request.
|
812
|
+
# The first argument is considered to be the scope of the query. That is, how many
|
813
|
+
# resources are returned from the request. It can be one of the following.
|
754
814
|
#
|
755
815
|
# * <tt>:one</tt> - Returns a single resource.
|
756
816
|
# * <tt>:first</tt> - Returns the first resource found.
|
@@ -794,9 +854,9 @@ module ActiveResource
|
|
794
854
|
# # => GET /people/1/street_addresses/1.json
|
795
855
|
#
|
796
856
|
# == Failure or missing data
|
797
|
-
#
|
798
|
-
#
|
799
|
-
#
|
857
|
+
# A failure to find the requested object raises a ResourceNotFound
|
858
|
+
# exception if the find was called with an id.
|
859
|
+
# With any other scope, find returns nil when no data is returned.
|
800
860
|
#
|
801
861
|
# Person.find(1)
|
802
862
|
# # => raises ResourceNotFound
|
@@ -833,12 +893,17 @@ module ActiveResource
|
|
833
893
|
find(:last, *args)
|
834
894
|
end
|
835
895
|
|
836
|
-
# This is an alias for find(:all).
|
896
|
+
# This is an alias for find(:all). You can pass in all the same
|
837
897
|
# arguments to this method as you can to <tt>find(:all)</tt>
|
838
898
|
def all(*args)
|
839
899
|
find(:all, *args)
|
840
900
|
end
|
841
901
|
|
902
|
+
def where(clauses = {})
|
903
|
+
raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
|
904
|
+
find(:all, :params => clauses)
|
905
|
+
end
|
906
|
+
|
842
907
|
|
843
908
|
# Deletes the resources with the ID in the +id+ parameter.
|
844
909
|
#
|
@@ -855,7 +920,7 @@ module ActiveResource
|
|
855
920
|
# # Let's assume a request to events/5/cancel.json
|
856
921
|
# Event.delete(params[:id]) # sends DELETE /events/5
|
857
922
|
def delete(id, options = {})
|
858
|
-
connection.delete(element_path(id, options))
|
923
|
+
connection.delete(element_path(id, options), headers)
|
859
924
|
end
|
860
925
|
|
861
926
|
# Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
|
@@ -891,14 +956,14 @@ module ActiveResource
|
|
891
956
|
begin
|
892
957
|
case from = options[:from]
|
893
958
|
when Symbol
|
894
|
-
instantiate_collection(get(from, options[:params]))
|
959
|
+
instantiate_collection(get(from, options[:params]), options[:params])
|
895
960
|
when String
|
896
961
|
path = "#{from}#{query_string(options[:params])}"
|
897
|
-
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
|
962
|
+
instantiate_collection(format.decode(connection.get(path, headers).body) || [], options[:params])
|
898
963
|
else
|
899
964
|
prefix_options, query_options = split_options(options[:params])
|
900
965
|
path = collection_path(prefix_options, query_options)
|
901
|
-
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
|
966
|
+
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), query_options, prefix_options )
|
902
967
|
end
|
903
968
|
rescue ActiveResource::ResourceNotFound
|
904
969
|
# Swallowing ResourceNotFound exceptions and return nil - as per
|
@@ -925,8 +990,11 @@ module ActiveResource
|
|
925
990
|
instantiate_record(format.decode(connection.get(path, headers).body), prefix_options)
|
926
991
|
end
|
927
992
|
|
928
|
-
def instantiate_collection(collection, prefix_options = {})
|
929
|
-
collection.
|
993
|
+
def instantiate_collection(collection, original_params = {}, prefix_options = {})
|
994
|
+
collection_parser.new(collection).tap do |parser|
|
995
|
+
parser.resource_class = self
|
996
|
+
parser.original_params = original_params
|
997
|
+
end.collect! { |record| instantiate_record(record, prefix_options) }
|
930
998
|
end
|
931
999
|
|
932
1000
|
def instantiate_record(record, prefix_options = {})
|
@@ -938,12 +1006,12 @@ module ActiveResource
|
|
938
1006
|
|
939
1007
|
# Accepts a URI and creates the site URI from that.
|
940
1008
|
def create_site_uri_from(site)
|
941
|
-
site.is_a?(URI) ? site.dup : URI.
|
1009
|
+
site.is_a?(URI) ? site.dup : URI.parse(site)
|
942
1010
|
end
|
943
1011
|
|
944
1012
|
# Accepts a URI and creates the proxy URI from that.
|
945
1013
|
def create_proxy_uri_from(proxy)
|
946
|
-
proxy.is_a?(URI) ? proxy.dup : URI.
|
1014
|
+
proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
|
947
1015
|
end
|
948
1016
|
|
949
1017
|
# contains a set of the current prefix parameters.
|
@@ -984,7 +1052,7 @@ module ActiveResource
|
|
984
1052
|
# gathered from the provided <tt>schema</tt>, or from the attributes
|
985
1053
|
# set on this instance after it has been fetched from the remote system.
|
986
1054
|
def known_attributes
|
987
|
-
self.class.known_attributes + self.attributes.keys.map(&:to_s)
|
1055
|
+
(self.class.known_attributes + self.attributes.keys.map(&:to_s)).uniq
|
988
1056
|
end
|
989
1057
|
|
990
1058
|
|
@@ -1003,7 +1071,7 @@ module ActiveResource
|
|
1003
1071
|
@attributes = {}.with_indifferent_access
|
1004
1072
|
@prefix_options = {}
|
1005
1073
|
@persisted = persisted
|
1006
|
-
load(attributes)
|
1074
|
+
load(attributes, false, persisted)
|
1007
1075
|
end
|
1008
1076
|
|
1009
1077
|
# Returns a \clone of the resource that hasn't been assigned an +id+ yet and
|
@@ -1014,7 +1082,7 @@ module ActiveResource
|
|
1014
1082
|
# not_ryan.new? # => true
|
1015
1083
|
#
|
1016
1084
|
# Any active resource member attributes will NOT be cloned, though all other
|
1017
|
-
# attributes are.
|
1085
|
+
# attributes are. This is to prevent the conflict between any +prefix_options+
|
1018
1086
|
# that refer to the original parent resource and the newly cloned parent
|
1019
1087
|
# resource that does not exist.
|
1020
1088
|
#
|
@@ -1030,7 +1098,7 @@ module ActiveResource
|
|
1030
1098
|
# Clone all attributes except the pk and any nested ARes
|
1031
1099
|
cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
|
1032
1100
|
# 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.
|
1101
|
+
# attempts to convert hashes into member objects and arrays into collections of objects. We want
|
1034
1102
|
# the raw objects to be cloned so we bypass load by directly setting the attributes hash.
|
1035
1103
|
resource = self.class.new({})
|
1036
1104
|
resource.prefix_options = self.prefix_options
|
@@ -1082,7 +1150,7 @@ module ActiveResource
|
|
1082
1150
|
attributes[self.class.primary_key] = id
|
1083
1151
|
end
|
1084
1152
|
|
1085
|
-
# Test for equality.
|
1153
|
+
# Test for equality. Resource are equal if and only if +other+ is the same object or
|
1086
1154
|
# is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
|
1087
1155
|
#
|
1088
1156
|
# ==== Examples
|
@@ -1138,7 +1206,7 @@ module ActiveResource
|
|
1138
1206
|
end
|
1139
1207
|
end
|
1140
1208
|
|
1141
|
-
# Saves (+POST+) or \updates (+PUT+) a resource.
|
1209
|
+
# Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
|
1142
1210
|
# +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
|
1143
1211
|
# is Json for the final object as it looked after the \save (which would include attributes like +created_at+
|
1144
1212
|
# that weren't part of the original submit).
|
@@ -1152,7 +1220,9 @@ module ActiveResource
|
|
1152
1220
|
# my_company.size = 10
|
1153
1221
|
# my_company.save # sends PUT /companies/1 (update)
|
1154
1222
|
def save
|
1155
|
-
|
1223
|
+
run_callbacks :save do
|
1224
|
+
new? ? create : update
|
1225
|
+
end
|
1156
1226
|
end
|
1157
1227
|
|
1158
1228
|
# Saves the resource.
|
@@ -1185,11 +1255,13 @@ module ActiveResource
|
|
1185
1255
|
# new_person.destroy
|
1186
1256
|
# Person.find(new_id) # 404 (Resource Not Found)
|
1187
1257
|
def destroy
|
1188
|
-
|
1258
|
+
run_callbacks :destroy do
|
1259
|
+
connection.delete(element_path, self.class.headers)
|
1260
|
+
end
|
1189
1261
|
end
|
1190
1262
|
|
1191
1263
|
# Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
|
1192
|
-
# found on the remote service.
|
1264
|
+
# found on the remote service. Using this method, you can check for
|
1193
1265
|
# resources that may have been deleted between the object's instantiation
|
1194
1266
|
# and actions on it.
|
1195
1267
|
#
|
@@ -1227,11 +1299,11 @@ module ActiveResource
|
|
1227
1299
|
# my_branch.reload
|
1228
1300
|
# my_branch.name # => "Wilson Road"
|
1229
1301
|
def reload
|
1230
|
-
self.load(self.class.find(to_param, :params => @prefix_options).attributes)
|
1302
|
+
self.load(self.class.find(to_param, :params => @prefix_options).attributes, false, true)
|
1231
1303
|
end
|
1232
1304
|
|
1233
1305
|
# A method to manually load attributes from a \hash. Recursively loads collections of
|
1234
|
-
# resources.
|
1306
|
+
# resources. This method is called in +initialize+ and +create+ when a \hash of attributes
|
1235
1307
|
# is provided.
|
1236
1308
|
#
|
1237
1309
|
# ==== Examples
|
@@ -1251,7 +1323,7 @@ module ActiveResource
|
|
1251
1323
|
# your_supplier = Supplier.new
|
1252
1324
|
# your_supplier.load(my_attrs)
|
1253
1325
|
# your_supplier.save
|
1254
|
-
def load(attributes, remove_root = false)
|
1326
|
+
def load(attributes, remove_root = false, persisted = false)
|
1255
1327
|
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
1256
1328
|
@prefix_options, attributes = split_options(attributes)
|
1257
1329
|
|
@@ -1269,14 +1341,14 @@ module ActiveResource
|
|
1269
1341
|
value.map do |attrs|
|
1270
1342
|
if attrs.is_a?(Hash)
|
1271
1343
|
resource ||= find_or_create_resource_for_collection(key)
|
1272
|
-
resource.new(attrs)
|
1344
|
+
resource.new(attrs, persisted)
|
1273
1345
|
else
|
1274
1346
|
attrs.duplicable? ? attrs.dup : attrs
|
1275
1347
|
end
|
1276
1348
|
end
|
1277
1349
|
when Hash
|
1278
1350
|
resource = find_or_create_resource_for(key)
|
1279
|
-
resource.new(value)
|
1351
|
+
resource.new(value, persisted)
|
1280
1352
|
else
|
1281
1353
|
value.duplicable? ? value.dup : value
|
1282
1354
|
end
|
@@ -1286,14 +1358,14 @@ module ActiveResource
|
|
1286
1358
|
|
1287
1359
|
# Updates a single attribute and then saves the object.
|
1288
1360
|
#
|
1289
|
-
# Note: Unlike ActiveRecord::Base.update_attribute
|
1361
|
+
# Note: <tt>Unlike ActiveRecord::Base.update_attribute</tt>, this method <b>is</b>
|
1290
1362
|
# subject to normal validation routines as an update sends the whole body
|
1291
|
-
# of the resource in the request.
|
1363
|
+
# of the resource in the request. (See Validations).
|
1292
1364
|
#
|
1293
1365
|
# As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
|
1294
1366
|
#
|
1295
1367
|
# If the saving fails because of a connection or remote service error, an
|
1296
|
-
# exception will be raised.
|
1368
|
+
# exception will be raised. If saving fails because the resource is
|
1297
1369
|
# invalid then <tt>false</tt> will be returned.
|
1298
1370
|
def update_attribute(name, value)
|
1299
1371
|
self.send("#{name}=".to_sym, value)
|
@@ -1304,7 +1376,7 @@ module ActiveResource
|
|
1304
1376
|
# and requests that the record be saved.
|
1305
1377
|
#
|
1306
1378
|
# If the saving fails because of a connection or remote service error, an
|
1307
|
-
# exception will be raised.
|
1379
|
+
# exception will be raised. If saving fails because the resource is
|
1308
1380
|
# invalid then <tt>false</tt> will be returned.
|
1309
1381
|
#
|
1310
1382
|
# Note: Though this request can be made with a partial set of the
|
@@ -1350,16 +1422,20 @@ module ActiveResource
|
|
1350
1422
|
|
1351
1423
|
# Update the resource on the remote service.
|
1352
1424
|
def update
|
1353
|
-
|
1354
|
-
|
1425
|
+
run_callbacks :update do
|
1426
|
+
connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
|
1427
|
+
load_attributes_from_response(response)
|
1428
|
+
end
|
1355
1429
|
end
|
1356
1430
|
end
|
1357
1431
|
|
1358
1432
|
# Create (i.e., \save to the remote service) the \new resource.
|
1359
1433
|
def create
|
1360
|
-
|
1361
|
-
self.
|
1362
|
-
|
1434
|
+
run_callbacks :create do
|
1435
|
+
connection.post(collection_path, encode, self.class.headers).tap do |response|
|
1436
|
+
self.id = id_from_response(response)
|
1437
|
+
load_attributes_from_response(response)
|
1438
|
+
end
|
1363
1439
|
end
|
1364
1440
|
end
|
1365
1441
|
|
@@ -1367,7 +1443,7 @@ module ActiveResource
|
|
1367
1443
|
if (response_code_allows_body?(response.code) &&
|
1368
1444
|
(response['Content-Length'].nil? || response['Content-Length'] != "0") &&
|
1369
1445
|
!response.body.nil? && response.body.strip.size > 0)
|
1370
|
-
load(self.class.format.decode(response.body), true)
|
1446
|
+
load(self.class.format.decode(response.body), true, true)
|
1371
1447
|
@persisted = true
|
1372
1448
|
end
|
1373
1449
|
end
|
@@ -1402,6 +1478,7 @@ module ActiveResource
|
|
1402
1478
|
|
1403
1479
|
# Tries to find a resource for a given collection name; if it fails, then the resource is created
|
1404
1480
|
def find_or_create_resource_for_collection(name)
|
1481
|
+
return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
|
1405
1482
|
find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
|
1406
1483
|
end
|
1407
1484
|
|
@@ -1412,7 +1489,7 @@ module ActiveResource
|
|
1412
1489
|
namespaces = module_names[0, module_names.size-1].map do |module_name|
|
1413
1490
|
receiver = receiver.const_get(module_name)
|
1414
1491
|
end
|
1415
|
-
const_args =
|
1492
|
+
const_args = [resource_name, false]
|
1416
1493
|
if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
|
1417
1494
|
namespace.const_get(*const_args)
|
1418
1495
|
else
|
@@ -1422,13 +1499,14 @@ module ActiveResource
|
|
1422
1499
|
|
1423
1500
|
# Tries to find a resource for a given name; if it fails, then the resource is created
|
1424
1501
|
def find_or_create_resource_for(name)
|
1502
|
+
return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
|
1425
1503
|
resource_name = name.to_s.camelize
|
1426
1504
|
|
1427
|
-
const_args =
|
1505
|
+
const_args = [resource_name, false]
|
1428
1506
|
if self.class.const_defined?(*const_args)
|
1429
1507
|
self.class.const_get(*const_args)
|
1430
1508
|
else
|
1431
|
-
ancestors = self.class.name.split("::")
|
1509
|
+
ancestors = self.class.name.to_s.split("::")
|
1432
1510
|
if ancestors.size > 1
|
1433
1511
|
find_or_create_resource_in_modules(resource_name, ancestors)
|
1434
1512
|
else
|
@@ -1474,9 +1552,12 @@ module ActiveResource
|
|
1474
1552
|
|
1475
1553
|
class Base
|
1476
1554
|
extend ActiveModel::Naming
|
1477
|
-
|
1555
|
+
extend ActiveResource::Associations
|
1556
|
+
|
1557
|
+
include Callbacks, CustomMethods, Observing, Validations
|
1478
1558
|
include ActiveModel::Conversion
|
1479
1559
|
include ActiveModel::Serializers::JSON
|
1480
1560
|
include ActiveModel::Serializers::Xml
|
1561
|
+
include ActiveResource::Reflection
|
1481
1562
|
end
|
1482
1563
|
end
|