activeresource 2.3.18 → 3.0.0.beta

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.

data/CHANGELOG CHANGED
@@ -1,27 +1,4 @@
1
- *2.3.11 (February 9, 2011)*
2
- *2.3.10 (October 15, 2010)*
3
- *2.3.9 (September 4, 2010)*
4
- *2.3.8 (May 24, 2010)*
5
- *2.3.7 (May 24, 2010)*
6
-
7
- * Version bump.
8
-
9
-
10
- *2.3.6 (May 23, 2010)*
11
-
12
- * No changes, just a version bump.
13
-
14
-
15
- *2.3.5 (November 25, 2009)*
16
-
17
- * Minor Bug Fixes and deprecation warnings
18
-
19
- * More flexible content type handling when parsing responses.
20
-
21
- Ensures that ARes will handle responses like test/xml, or content types
22
- with charsets included.
23
-
24
- *2.3.4 (September 4, 2009)*
1
+ *Edge*
25
2
 
26
3
  * Add support for errors in JSON format. #1956 [Fabien Jakimowicz]
27
4
 
@@ -32,11 +9,6 @@
32
9
  * HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet]
33
10
 
34
11
 
35
- *2.3.3 (July 12, 2009)*
36
-
37
- * No changes, just a version bump.
38
-
39
-
40
12
  *2.3.2 [Final] (March 15, 2009)*
41
13
 
42
14
  * Nothing new, just included in 2.3.2
data/README CHANGED
@@ -1,7 +1,7 @@
1
1
  = Active Resource
2
2
 
3
3
  Active Resource (ARes) connects business objects and Representational State Transfer (REST)
4
- web services. It implements object-relational mapping for REST webservices to provide transparent
4
+ web services. It implements object-relational mapping for REST web services to provide transparent
5
5
  proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing
6
6
  in ActionController::Resources).
7
7
 
@@ -22,14 +22,14 @@ received and serialized into a usable Ruby object.
22
22
 
23
23
  === Configuration and Usage
24
24
 
25
- Putting ActiveResource to use is very similar to ActiveRecord. It's as simple as creating a model class
25
+ Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class
26
26
  that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
27
27
 
28
28
  class Person < ActiveResource::Base
29
29
  self.site = "http://api.people.com:3000/"
30
30
  end
31
31
 
32
- Now the Person class is REST enabled and can invoke REST services very similarly to how ActiveRecord invokes
32
+ Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
33
33
  lifecycle methods that operate against a persistent store.
34
34
 
35
35
  # Find a person with id = 1
@@ -42,7 +42,7 @@ records. But rather than dealing directly with a database record, you're dealin
42
42
  ==== Protocol
43
43
 
44
44
  Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
45
- built into ActionController but will also work with any other REST service that properly implements the protocol.
45
+ built into Action Controller but will also work with any other REST service that properly implements the protocol.
46
46
  REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification:
47
47
 
48
48
  * GET requests are used for finding and retrieving resources.
@@ -55,8 +55,8 @@ for more general information on REST web services, see the article here[http://e
55
55
 
56
56
  ==== Find
57
57
 
58
- GET Http requests expect the XML form of whatever resource/resources is/are being requested. So,
59
- for a request for a single element - the XML of that item is expected in response:
58
+ Find requests use the GET method and expect the XML form of whatever resource/resources is/are being requested. So,
59
+ for a request for a single element, the XML of that item is expected in response:
60
60
 
61
61
  # Expects a response of
62
62
  #
@@ -101,7 +101,7 @@ Collections can also be requested in a similar fashion
101
101
 
102
102
  ==== Create
103
103
 
104
- Creating a new resource submits the xml form of the resource as the body of the request and expects
104
+ Creating a new resource submits the XML form of the resource as the body of the request and expects
105
105
  a 'Location' header in the response with the RESTful URL location of the newly created resource. The
106
106
  id of the newly created resource is parsed out of the Location response header and automatically set
107
107
  as the id of the ARes object.
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
2
+ require 'active_resource'
3
+ require 'active_support/core_ext/hash/conversions'
4
+
5
+ ActiveSupport::XmlMini.backend = ENV['XMLMINI'] || 'REXML'
6
+ ActiveResource::HttpMock.respond_to do |mock|
7
+ mock.get '/people/1.xml', {}, { :id => 1, :name => 'bob' }.to_xml(:root => 'person')
8
+ end
9
+
10
+ class Person < ActiveResource::Base
11
+ self.site = 'http://localhost/'
12
+ end
13
+
14
+ bob = Person.find(1)
15
+ puts bob.inspect
@@ -21,24 +21,24 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- begin
25
- require 'active_support'
26
- rescue LoadError
27
- activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
28
- if File.directory?(activesupport_path)
29
- $:.unshift activesupport_path
30
- require 'active_support'
31
- end
32
- end
24
+ activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
25
+ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
26
+
27
+ activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
28
+ $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
33
29
 
34
- require 'active_resource/formats'
35
- require 'active_resource/base'
36
- require 'active_resource/validations'
37
- require 'active_resource/custom_methods'
30
+ require 'active_support'
31
+ require 'active_model'
38
32
 
39
33
  module ActiveResource
40
- Base.class_eval do
41
- include Validations
42
- include CustomMethods
43
- end
34
+ extend ActiveSupport::Autoload
35
+
36
+ autoload :Base
37
+ autoload :Connection
38
+ autoload :CustomMethods
39
+ autoload :Formats
40
+ autoload :HttpMock
41
+ autoload :Observing
42
+ autoload :Schema
43
+ autoload :Validations
44
44
  end
@@ -1,6 +1,21 @@
1
- require 'active_resource/connection'
2
- require 'cgi'
1
+ require 'active_support'
2
+ require 'active_support/core_ext/class/attribute_accessors'
3
+ require 'active_support/core_ext/class/inheritable_attributes'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'active_support/core_ext/kernel/reporting'
6
+ require 'active_support/core_ext/module/attr_accessor_with_default'
7
+ require 'active_support/core_ext/module/delegation'
8
+ require 'active_support/core_ext/module/aliasing'
9
+ require 'active_support/core_ext/object/blank'
10
+ require 'active_support/core_ext/object/misc'
11
+ require 'active_support/core_ext/object/to_query'
3
12
  require 'set'
13
+ require 'uri'
14
+
15
+ require 'active_resource/exceptions'
16
+ require 'active_resource/connection'
17
+ require 'active_resource/formats'
18
+ require 'active_resource/schema'
4
19
 
5
20
  module ActiveResource
6
21
  # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
@@ -19,7 +34,7 @@ module ActiveResource
19
34
  # end
20
35
  #
21
36
  # Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and
22
- # you can now use Active Resource's lifecycles methods to manipulate resources. In the case where you already have
37
+ # you can now use Active Resource's lifecycle methods to manipulate resources. In the case where you already have
23
38
  # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
24
39
  #
25
40
  # class PersonResource < ActiveResource::Base
@@ -27,6 +42,13 @@ module ActiveResource
27
42
  # self.element_name = "person"
28
43
  # end
29
44
  #
45
+ # If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
46
+ #
47
+ # class PersonResource < ActiveResource::Base
48
+ # self.site = "http://api.people.com:3000/"
49
+ # self.proxy = "http://user:password@proxy.people.com:8080"
50
+ # end
51
+ #
30
52
  #
31
53
  # == Lifecycle methods
32
54
  #
@@ -127,6 +149,7 @@ module ActiveResource
127
149
  # :verify_mode => OpenSSL::SSL::VERIFY_PEER}
128
150
  # end
129
151
  #
152
+ #
130
153
  # == Errors & Validation
131
154
  #
132
155
  # Error handling and validation is handled in much the same manner as you're used to seeing in
@@ -144,7 +167,7 @@ module ActiveResource
144
167
  #
145
168
  # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
146
169
  # following HTTP response codes will also result in these exceptions:
147
- #
170
+ #
148
171
  # * 200..399 - Valid response, no exception (other than 301, 302)
149
172
  # * 301, 302 - ActiveResource::Redirection
150
173
  # * 400 - ActiveResource::BadRequest
@@ -172,7 +195,7 @@ module ActiveResource
172
195
  #
173
196
  # === Validation errors
174
197
  #
175
- # Active Resource supports validations on resources and will return errors if any these validations fail
198
+ # Active Resource supports validations on resources and will return errors if any of these validations fail
176
199
  # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
177
200
  # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
178
201
  # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
@@ -227,10 +250,125 @@ module ActiveResource
227
250
  # The logger for diagnosing and tracing Active Resource calls.
228
251
  cattr_accessor :logger
229
252
 
230
- # Controls the top-level behavior of JSON serialization
231
- cattr_accessor :include_root_in_json, :instance_writer => false
232
-
233
253
  class << self
254
+ # Creates a schema for this resource - setting the attributes that are
255
+ # known prior to fetching an instance from the remote system.
256
+ #
257
+ # The schema helps define the set of <tt>known_attributes</tt> of the
258
+ # current resource.
259
+ #
260
+ # There is no need to specify a schema for your Active Resource. If
261
+ # you do not, the <tt>known_attributes</tt> will be guessed from the
262
+ # instance attributes returned when an instance is fetched from the
263
+ # remote system.
264
+ #
265
+ # example:
266
+ # class Person < ActiveResource::Base
267
+ # schema do
268
+ # # define each attribute separately
269
+ # attribute 'name', :string
270
+ #
271
+ # # or use the convenience methods and pass >=1 attribute names
272
+ # string 'eye_colour', 'hair_colour'
273
+ # integer 'age'
274
+ # float 'height', 'weight'
275
+ #
276
+ # # unsupported types should be left as strings
277
+ # # overload the accessor methods if you need to convert them
278
+ # attribute 'created_at', 'string'
279
+ # end
280
+ # end
281
+ #
282
+ # p = Person.new
283
+ # p.respond_to? :name # => true
284
+ # p.respond_to? :age # => true
285
+ # p.name # => nil
286
+ # p.age # => nil
287
+ #
288
+ # j = Person.find_by_name('John') # <person><name>John</name><age>34</age><num_children>3</num_children></person>
289
+ # j.respond_to? :name # => true
290
+ # j.respond_to? :age # => true
291
+ # j.name # => 'John'
292
+ # j.age # => '34' # note this is a string!
293
+ # j.num_children # => '3' # note this is a string!
294
+ #
295
+ # p.num_children # => NoMethodError
296
+ #
297
+ # Attribute-types must be one of:
298
+ # string, integer, float
299
+ #
300
+ # Note: at present the attribute-type doesn't do anything, but stay
301
+ # tuned...
302
+ # Shortly it will also *cast* the value of the returned attribute.
303
+ # ie:
304
+ # j.age # => 34 # cast to an integer
305
+ # j.weight # => '65' # still a string!
306
+ #
307
+ def schema(&block)
308
+ if block_given?
309
+ schema_definition = Schema.new
310
+ schema_definition.instance_eval(&block)
311
+
312
+ # skip out if we didn't define anything
313
+ return unless schema_definition.attrs.present?
314
+
315
+ @schema ||= {}.with_indifferent_access
316
+ @known_attributes ||= []
317
+
318
+ schema_definition.attrs.each do |k,v|
319
+ @schema[k] = v
320
+ @known_attributes << k
321
+ end
322
+
323
+ schema
324
+ else
325
+ @schema ||= nil
326
+ end
327
+ end
328
+
329
+ # Alternative, direct way to specify a <tt>schema</tt> for this
330
+ # Resource. <tt>schema</tt> is more flexible, but this is quick
331
+ # for a very simple schema.
332
+ #
333
+ # Pass the schema as a hash with the keys being the attribute-names
334
+ # and the value being one of the accepted attribute types (as defined
335
+ # in <tt>schema</tt>)
336
+ #
337
+ # example:
338
+ #
339
+ # class Person < ActiveResource::Base
340
+ # schema = {'name' => :string, 'age' => :integer }
341
+ # end
342
+ #
343
+ # The keys/values can be strings or symbols. They will be converted to
344
+ # strings.
345
+ #
346
+ def schema=(the_schema)
347
+ unless the_schema.present?
348
+ # purposefully nulling out the schema
349
+ @schema = nil
350
+ @known_attributes = []
351
+ return
352
+ end
353
+
354
+ raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
355
+
356
+ schema do
357
+ the_schema.each {|k,v| attribute(k,v) }
358
+ end
359
+ end
360
+
361
+ # Returns the list of known attributes for this resource, gathered
362
+ # from the provided <tt>schema</tt>
363
+ # Attributes that are known will cause your resource to return 'true'
364
+ # when <tt>respond_to?</tt> is called on them. A known attribute will
365
+ # return nil if not set (rather than <t>MethodNotFound</tt>); thus
366
+ # known attributes can be used with <tt>validates_presence_of</tt>
367
+ # without a getter-method.
368
+ def known_attributes
369
+ @known_attributes ||= []
370
+ end
371
+
234
372
  # Gets the URI of the REST resources to map for this class. The site variable is required for
235
373
  # Active Resource's mapping to work.
236
374
  def site
@@ -264,8 +402,8 @@ module ActiveResource
264
402
  @site = nil
265
403
  else
266
404
  @site = create_site_uri_from(site)
267
- @user = URI.decode(@site.user) if @site.user
268
- @password = URI.decode(@site.password) if @site.password
405
+ @user = uri_parser.unescape(@site.user) if @site.user
406
+ @password = uri_parser.unescape(@site.password) if @site.password
269
407
  end
270
408
  end
271
409
 
@@ -317,6 +455,17 @@ module ActiveResource
317
455
  @password = password
318
456
  end
319
457
 
458
+ def auth_type
459
+ if defined?(@auth_type)
460
+ @auth_type
461
+ end
462
+ end
463
+
464
+ def auth_type=(auth_type)
465
+ @connection = nil
466
+ @auth_type = auth_type
467
+ end
468
+
320
469
  # Sets the format that attributes are sent and received in from a mime type reference:
321
470
  #
322
471
  # Person.format = :json
@@ -336,7 +485,7 @@ module ActiveResource
336
485
 
337
486
  # Returns the current format, default is ActiveResource::Formats::XmlFormat.
338
487
  def format
339
- read_inheritable_attribute(:format) || ActiveResource::Formats[:xml]
488
+ read_inheritable_attribute(:format) || ActiveResource::Formats::XmlFormat
340
489
  end
341
490
 
342
491
  # Sets the number of seconds after which requests to the REST API should time out.
@@ -388,6 +537,7 @@ module ActiveResource
388
537
  @connection.proxy = proxy if proxy
389
538
  @connection.user = user if user
390
539
  @connection.password = password if password
540
+ @connection.auth_type = auth_type if auth_type
391
541
  @connection.timeout = timeout if timeout
392
542
  @connection.ssl_options = ssl_options if ssl_options
393
543
  @connection
@@ -402,11 +552,11 @@ module ActiveResource
402
552
 
403
553
  # Do not include any modules in the default element name. This makes it easier to seclude ARes objects
404
554
  # in a separate namespace without having to set element_name repeatedly.
405
- attr_accessor_with_default(:element_name) { to_s.split("::").last.underscore } #:nodoc:
555
+ attr_accessor_with_default(:element_name) { ActiveSupport::Inflector.underscore(to_s.split("::").last) } #:nodoc:
406
556
 
407
- attr_accessor_with_default(:collection_name) { element_name.pluralize } #:nodoc:
557
+ attr_accessor_with_default(:collection_name) { ActiveSupport::Inflector.pluralize(element_name) } #:nodoc:
408
558
  attr_accessor_with_default(:primary_key, 'id') #:nodoc:
409
-
559
+
410
560
  # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
411
561
  # This method is regenerated at runtime based on what the \prefix is set to.
412
562
  def prefix(options={})
@@ -434,13 +584,13 @@ module ActiveResource
434
584
  @prefix_parameters = nil
435
585
 
436
586
  # Redefine the new methods.
437
- code, line = <<-end_code, __LINE__ + 1
587
+ code = <<-end_code
438
588
  def prefix_source() "#{value}" end
439
589
  def prefix(options={}) "#{prefix_call}" end
440
590
  end_code
441
- silence_warnings { instance_eval code, __FILE__, line }
591
+ silence_warnings { instance_eval code, __FILE__, __LINE__ }
442
592
  rescue
443
- logger.error "Couldn't set prefix: #{$!}\n #{code}"
593
+ logger.error "Couldn't set prefix: #{$!}\n #{code}" if logger
444
594
  raise
445
595
  end
446
596
 
@@ -479,7 +629,7 @@ module ActiveResource
479
629
  # will split from the +prefix_options+.
480
630
  #
481
631
  # ==== Options
482
- # * +prefix_options+ - A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt>
632
+ # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
483
633
  # would yield a URL like <tt>/accounts/19/purchases.xml</tt>).
484
634
  # * +query_options+ - A hash to add items to the query string for the request.
485
635
  #
@@ -577,6 +727,19 @@ module ActiveResource
577
727
  #
578
728
  # StreetAddress.find(1, :params => { :person_id => 1 })
579
729
  # # => GET /people/1/street_addresses/1.xml
730
+ #
731
+ # == Failure or missing data
732
+ # A failure to find the requested object raises a ResourceNotFound
733
+ # exception if the find was called with an id.
734
+ # With any other scope, find returns nil when no data is returned.
735
+ #
736
+ # Person.find(1)
737
+ # # => raises ResourcenotFound
738
+ #
739
+ # Person.find(:all)
740
+ # Person.find(:first)
741
+ # Person.find(:last)
742
+ # # => nil
580
743
  def find(*arguments)
581
744
  scope = arguments.slice!(0)
582
745
  options = arguments.slice!(0) || {}
@@ -590,6 +753,28 @@ module ActiveResource
590
753
  end
591
754
  end
592
755
 
756
+
757
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
758
+ # in all the same arguments to this method as you can to
759
+ # <tt>find(:first)</tt>.
760
+ def first(*args)
761
+ find(:first, *args)
762
+ end
763
+
764
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
765
+ # in all the same arguments to this method as you can to
766
+ # <tt>find(:last)</tt>.
767
+ def last(*args)
768
+ find(:last, *args)
769
+ end
770
+
771
+ # This is an alias for find(:all). You can pass in all the same
772
+ # arguments to this method as you can to <tt>find(:all)</tt>
773
+ def all(*args)
774
+ find(:all, *args)
775
+ end
776
+
777
+
593
778
  # Deletes the resources with the ID in the +id+ parameter.
594
779
  #
595
780
  # ==== Options
@@ -630,16 +815,22 @@ module ActiveResource
630
815
  private
631
816
  # Find every resource
632
817
  def find_every(options)
633
- case from = options[:from]
634
- when Symbol
635
- instantiate_collection(get(from, options[:params]))
636
- when String
637
- path = "#{from}#{query_string(options[:params])}"
638
- instantiate_collection(connection.get(path, headers) || [])
639
- else
640
- prefix_options, query_options = split_options(options[:params])
641
- path = collection_path(prefix_options, query_options)
642
- instantiate_collection( (connection.get(path, headers) || []), prefix_options )
818
+ begin
819
+ case from = options[:from]
820
+ when Symbol
821
+ instantiate_collection(get(from, options[:params]))
822
+ when String
823
+ path = "#{from}#{query_string(options[:params])}"
824
+ instantiate_collection(connection.get(path, headers) || [])
825
+ else
826
+ prefix_options, query_options = split_options(options[:params])
827
+ path = collection_path(prefix_options, query_options)
828
+ instantiate_collection( (connection.get(path, headers) || []), prefix_options )
829
+ end
830
+ rescue ActiveResource::ResourceNotFound
831
+ # Swallowing ResourceNotFound exceptions and return nil - as per
832
+ # ActiveRecord.
833
+ nil
643
834
  end
644
835
  end
645
836
 
@@ -674,12 +865,12 @@ module ActiveResource
674
865
 
675
866
  # Accepts a URI and creates the site URI from that.
676
867
  def create_site_uri_from(site)
677
- site.is_a?(URI) ? site.dup : URI.parse(site)
868
+ site.is_a?(URI) ? site.dup : uri_parser.parse(site)
678
869
  end
679
870
 
680
871
  # Accepts a URI and creates the proxy URI from that.
681
872
  def create_proxy_uri_from(proxy)
682
- proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
873
+ proxy.is_a?(URI) ? proxy.dup : uri_parser.parse(proxy)
683
874
  end
684
875
 
685
876
  # contains a set of the current prefix parameters.
@@ -704,11 +895,30 @@ module ActiveResource
704
895
 
705
896
  [ prefix_options, query_options ]
706
897
  end
898
+
899
+ def uri_parser
900
+ @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
901
+ end
707
902
  end
708
903
 
709
904
  attr_accessor :attributes #:nodoc:
710
905
  attr_accessor :prefix_options #:nodoc:
711
906
 
907
+ # If no schema has been defined for the class (see
908
+ # <tt>ActiveResource::schema=</tt>), the default automatic schema is
909
+ # generated from the current instance's attributes
910
+ def schema
911
+ self.class.schema || self.attributes
912
+ end
913
+
914
+ # This is a list of known attributes for this resource. Either
915
+ # gathered fromthe provided <tt>schema</tt>, or from the attributes
916
+ # set on this instance after it has been fetched from the remote system.
917
+ def known_attributes
918
+ self.class.known_attributes + self.attributes.keys.map(&:to_s)
919
+ end
920
+
921
+
712
922
  # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
713
923
  # of attributes for the \new resource.
714
924
  #
@@ -721,7 +931,7 @@ module ActiveResource
721
931
  # my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
722
932
  # my_other_course.save
723
933
  def initialize(attributes = {})
724
- @attributes = {}
934
+ @attributes = {}.with_indifferent_access
725
935
  @prefix_options = {}
726
936
  load(attributes)
727
937
  end
@@ -762,7 +972,7 @@ module ActiveResource
762
972
  end
763
973
 
764
974
 
765
- # A method to determine if the resource a \new object (i.e., it has not been POSTed to the remote service yet).
975
+ # Returns +true+ if this object hasn't yet been saved, otherwise, returns +false+.
766
976
  #
767
977
  # ==== Examples
768
978
  # not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
@@ -831,7 +1041,7 @@ module ActiveResource
831
1041
  id.hash
832
1042
  end
833
1043
 
834
- # Duplicate the current resource without saving it.
1044
+ # Duplicates the current resource without saving it.
835
1045
  #
836
1046
  # ==== Examples
837
1047
  # my_invoice = Invoice.create(:customer => 'That Company')
@@ -850,8 +1060,8 @@ module ActiveResource
850
1060
  end
851
1061
  end
852
1062
 
853
- # A method to \save (+POST+) or \update (+PUT+) a resource. It delegates to +create+ if a \new object,
854
- # +update+ if it is existing. If the response to the \save includes a body, it will be assumed that this body
1063
+ # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
1064
+ # +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
855
1065
  # is XML for the final object as it looked after the \save (which would include attributes like +created_at+
856
1066
  # that weren't part of the original submit).
857
1067
  #
@@ -867,6 +1077,23 @@ module ActiveResource
867
1077
  new? ? create : update
868
1078
  end
869
1079
 
1080
+ # Saves the resource.
1081
+ #
1082
+ # If the resource is new, it is created via +POST+, otherwise the
1083
+ # existing resource is updated via +PUT+.
1084
+ #
1085
+ # With <tt>save!</tt> validations always run. If any of them fail
1086
+ # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to
1087
+ # the remote system.
1088
+ # See ActiveResource::Validations for more information.
1089
+ #
1090
+ # There's a series of callbacks associated with <tt>save!</tt>. If any
1091
+ # of the <tt>before_*</tt> callbacks return +false+ the action is
1092
+ # cancelled and <tt>save!</tt> raises ActiveResource::ResourceInvalid.
1093
+ def save!
1094
+ save || raise(ResourceInvalid.new(self))
1095
+ end
1096
+
870
1097
  # Deletes the resource from the remote service.
871
1098
  #
872
1099
  # ==== Examples
@@ -903,7 +1130,7 @@ module ActiveResource
903
1130
  !new? && self.class.exists?(to_param, :params => prefix_options)
904
1131
  end
905
1132
 
906
- # Converts the resource to an XML string representation.
1133
+ # Converts the resource to an XML string representation.
907
1134
  #
908
1135
  # ==== Options
909
1136
  # The +options+ parameter is handed off to the +to_xml+ method on each
@@ -912,14 +1139,7 @@ module ActiveResource
912
1139
  #
913
1140
  # * <tt>:indent</tt> - Set the indent level for the XML output (default is +2+).
914
1141
  # * <tt>:dasherize</tt> - Boolean option to determine whether or not element names should
915
- # replace underscores with dashes. Default is <tt>true</tt>. The default can be set to <tt>false</tt>
916
- # by setting the module attribute <tt>ActiveSupport.dasherize_xml = false</tt> in an initializer. Because save
917
- # uses this method, and there are no options on save, then you will have to set the default if you don't
918
- # want underscores in element names to become dashes when the resource is saved. This is important when
919
- # integrating with non-Rails applications.
920
- # * <tt>:camelize</tt> - Boolean option to determine whether or not element names should be converted
921
- # to camel case, e.g some_name to SomeName. Default is <tt>false</tt>. Like <tt>:dasherize</tt> you can
922
- # change the default by setting the module attribute <tt>ActiveSupport.camelise_xml = true</tt> in an initializer.
1142
+ # replace underscores with dashes (default is <tt>false</tt>).
923
1143
  # * <tt>:skip_instruct</tt> - Toggle skipping the +instruct!+ call on the XML builder
924
1144
  # that generates the XML declaration (default is <tt>false</tt>).
925
1145
  #
@@ -972,14 +1192,8 @@ module ActiveResource
972
1192
  # applicable depend on the configured encoding format.
973
1193
  def encode(options={})
974
1194
  case self.class.format
975
- when ActiveResource::Formats[:xml]
1195
+ when ActiveResource::Formats::XmlFormat
976
1196
  self.class.format.encode(attributes, {:root => self.class.element_name}.merge(options))
977
- when ActiveResource::Formats::JsonFormat
978
- if ActiveResource::Base.include_root_in_json
979
- self.class.format.encode({self.class.element_name => attributes}, options)
980
- else
981
- self.class.format.encode(attributes, options)
982
- end
983
1197
  else
984
1198
  self.class.format.encode(attributes, options)
985
1199
  end
@@ -1046,6 +1260,36 @@ module ActiveResource
1046
1260
  self
1047
1261
  end
1048
1262
 
1263
+ # Updates a single attribute and then saves the object.
1264
+ #
1265
+ # Note: Unlike ActiveRecord::Base.update_attribute, this method <b>is</b>
1266
+ # subject to normal validation routines as an update sends the whole body
1267
+ # of the resource in the request. (See Validations).
1268
+ #
1269
+ # As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
1270
+ #
1271
+ # If the saving fails because of a connection or remote service error, an
1272
+ # exception will be raised. If saving fails because the resource is
1273
+ # invalid then <tt>false</tt> will be returned.
1274
+ def update_attribute(name, value)
1275
+ self.send("#{name}=".to_sym, value)
1276
+ self.save
1277
+ end
1278
+
1279
+ # Updates this resource with all the attributes from the passed-in Hash
1280
+ # and requests that the record be saved.
1281
+ #
1282
+ # If the saving fails because of a connection or remote service error, an
1283
+ # exception will be raised. If saving fails because the resource is
1284
+ # invalid then <tt>false</tt> will be returned.
1285
+ #
1286
+ # Note: Though this request can be made with a partial set of the
1287
+ # resource's attributes, the full body of the request will still be sent
1288
+ # in the save request to the remote service.
1289
+ def update_attributes(attributes)
1290
+ load(attributes) && save
1291
+ end
1292
+
1049
1293
  # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
1050
1294
  alias_method :respond_to_without_attributes?, :respond_to?
1051
1295
 
@@ -1055,18 +1299,18 @@ module ActiveResource
1055
1299
  def respond_to?(method, include_priv = false)
1056
1300
  method_name = method.to_s
1057
1301
  if attributes.nil?
1058
- return super
1059
- elsif attributes.has_key?(method_name)
1060
- return true
1061
- elsif ['?','='].include?(method_name.last) && attributes.has_key?(method_name.first(-1))
1062
- return true
1302
+ super
1303
+ elsif known_attributes.include?(method_name)
1304
+ true
1305
+ elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
1306
+ true
1307
+ else
1308
+ # super must be called at the end of the method, because the inherited respond_to?
1309
+ # would return true for generated readers, even if the attribute wasn't present
1310
+ super
1063
1311
  end
1064
- # super must be called at the end of the method, because the inherited respond_to?
1065
- # would return true for generated readers, even if the attribute wasn't present
1066
- super
1067
1312
  end
1068
1313
 
1069
-
1070
1314
  protected
1071
1315
  def connection(refresh = false)
1072
1316
  self.class.connection(refresh)
@@ -1109,7 +1353,7 @@ module ActiveResource
1109
1353
  private
1110
1354
  # Tries to find a resource for a given collection name; if it fails, then the resource is created
1111
1355
  def find_or_create_resource_for_collection(name)
1112
- find_or_create_resource_for(name.to_s.singularize)
1356
+ find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
1113
1357
  end
1114
1358
 
1115
1359
  # Tries to find a resource in a non empty list of nested modules
@@ -1153,14 +1397,24 @@ module ActiveResource
1153
1397
  def method_missing(method_symbol, *arguments) #:nodoc:
1154
1398
  method_name = method_symbol.to_s
1155
1399
 
1156
- case method_name.last
1400
+ if method_name =~ /(=|\?)$/
1401
+ case $1
1157
1402
  when "="
1158
- attributes[method_name.first(-1)] = arguments.first
1403
+ attributes[$`] = arguments.first
1159
1404
  when "?"
1160
- attributes[method_name.first(-1)]
1161
- else
1162
- attributes.has_key?(method_name) ? attributes[method_name] : super
1405
+ attributes[$`]
1406
+ end
1407
+ else
1408
+ return attributes[method_name] if attributes.include?(method_name)
1409
+ # not set right now but we know about it
1410
+ return nil if known_attributes.include?(method_name)
1411
+ super
1163
1412
  end
1164
1413
  end
1165
1414
  end
1415
+
1416
+ class Base
1417
+ extend ActiveModel::Naming
1418
+ include CustomMethods, Observing, Validations
1419
+ end
1166
1420
  end