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.

@@ -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
@@ -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. To map resources
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 = "http://api.people.com:3000/"
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>http://api.people.com:3000/people/</tt>, and
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 = "http://api.people.com:3000/"
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 = "http://api.people.com:3000/"
49
- # self.proxy = "http://user:password@proxy.people.com:8080"
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 = "http://api.people.com:3000/"
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 will require authentication, usually in the form of basic
117
- # HTTP authentication. Authentication can be specified by:
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
- # === HTTP Basic Authentication
120
- # * putting the credentials in the URL for the +site+ variable.
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.site = "http://ryan:password@api.people.com:3000/"
139
+ # self.auth_type = :digest
124
140
  # end
125
141
  #
126
- # * defining +user+ and/or +password+ variables
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
- # self.site = "http://api.people.com:3000/"
147
+ # # Set user and password directly:
130
148
  # self.user = "ryan"
131
149
  # self.password = "password"
132
- # end
133
150
  #
134
- # For obvious security reasons, it is probably best if such services are available
135
- # over HTTPS.
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
- # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
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
- # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file))
147
- # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),
148
- # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",
149
- # :verify_mode => OpenSSL::SSL::VERIFY_PEER}
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. Both the response code in the HTTP response and the body of the response are used to
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 http://api.people.com:3000/people/999.json
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. For example:
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 = "http://someip.com/posts/:post_id/"
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). These types of errors are denoted in the response by
211
- # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
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 http://api.people.com:3000/people/1.json
238
+ # # PUT https://api.people.com/people/1.xml
220
239
  # # or
221
- # # PUT http://api.people.com:3000/people/1.json
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":["First cannot be empty"]}
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 = "http://api.people.com:3000/"
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
- # class Person < ActiveResource::Base
280
- # schema do
281
- # # define each attribute separately
282
- # attribute 'name', :string
283
- #
284
- # # or use the convenience methods and pass >=1 attribute names
285
- # string 'eye_color', 'hair_color'
286
- # integer 'age'
287
- # float 'height', 'weight'
288
- #
289
- # # unsupported types should be left as strings
290
- # # overload the accessor methods if you need to convert them
291
- # attribute 'created_at', 'string'
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
- # p = Person.new
296
- # p.respond_to? :name # => true
297
- # p.respond_to? :age # => true
298
- # p.name # => nil
299
- # p.age # => nil
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
- # j = Person.find_by_name('John') # <person><name>John</name><age>34</age><num_children>3</num_children></person>
302
- # j.respond_to? :name # => true
303
- # j.respond_to? :age # => true
304
- # j.name # => 'John'
305
- # j.age # => '34' # note this is a string!
306
- # j.num_children # => '3' # note this is a string!
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
- # p.num_children # => NoMethodError
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
- # class Person < ActiveResource::Base
353
- # schema = {'name' => :string, 'age' => :integer }
354
- # end
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 <t>MethodNotFound</tt>); thus
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. The site variable is required for
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 = 'http://anonymous@test.com'
393
- # Subclass.site # => 'http://anonymous@test.com'
423
+ # Parent.site = 'https://anonymous@test.com'
424
+ # Subclass.site # => 'https://anonymous@test.com'
394
425
  # Subclass.site.user = 'david'
395
- # Parent.site # => 'http://david@test.com'
426
+ # Parent.site # => 'https://david@test.com'
396
427
  #
397
428
  # Without superclass_delegating_reader (expected behavior)
398
429
  #
399
- # Parent.site = 'http://anonymous@test.com'
400
- # Subclass.site # => 'http://anonymous@test.com'
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=(opts={})
571
+ def ssl_options=(options)
531
572
  @connection = nil
532
- @ssl_options = opts
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 ||= 'id'
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. This
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
- # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
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
- # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
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 = "http://37s.sunrise.i/posts/:post_id/"
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}.#{format.extension}#{query_string(query_options)}"
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
- # would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
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 = "http://37s.sunrise.i/posts/:post_id/"
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.#{format.extension}"
736
+ "#{prefix(prefix_options)}#{collection_name}/new#{format_extension}"
677
737
  end
678
738
 
679
- # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
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}.#{format.extension}#{query_string(query_options)}"
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).merge(attributes)
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. If a failure has occurred an
728
- # exception will be raised (see <tt>save</tt>). If the resource is invalid and
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. Used similarly to Active Record's +find+ method.
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. That is, how many
753
- # resources are returned from the request. It can be one of the following.
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
- # A failure to find the requested object raises a ResourceNotFound
798
- # exception if the find was called with an id.
799
- # With any other scope, find returns nil when no data is returned.
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). You can pass in all the same
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.collect! { |record| instantiate_record(record, prefix_options) }
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.parser.parse(site)
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.parser.parse(proxy)
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. This is to prevent the conflict between any +prefix_options+
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. We want
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. Resource are equal if and only if +other+ is the same object or
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. Delegates to +create+ if the object is \new,
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
- new? ? create : update
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
- connection.delete(element_path, self.class.headers)
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. Using this method, you can check for
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. This method is called in +initialize+ and +create+ when a \hash of attributes
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, this method <b>is</b>
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. (See Validations).
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. If saving fails because the resource is
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. If saving fails because the resource is
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
- connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
1354
- load_attributes_from_response(response)
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
- connection.post(collection_path, encode, self.class.headers).tap do |response|
1361
- self.id = id_from_response(response)
1362
- load_attributes_from_response(response)
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 = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
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 = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
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
- include CustomMethods, Observing, Validations
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