activeresource 4.1.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +324 -0
  4. data/lib/active_resource/active_job_serializer.rb +26 -0
  5. data/lib/active_resource/associations/builder/association.rb +6 -6
  6. data/lib/active_resource/associations/builder/belongs_to.rb +5 -3
  7. data/lib/active_resource/associations/builder/has_many.rb +4 -2
  8. data/lib/active_resource/associations/builder/has_one.rb +5 -3
  9. data/lib/active_resource/associations.rb +23 -20
  10. data/lib/active_resource/base.rb +233 -113
  11. data/lib/active_resource/callbacks.rb +3 -1
  12. data/lib/active_resource/collection.rb +21 -12
  13. data/lib/active_resource/connection.rb +78 -81
  14. data/lib/active_resource/custom_methods.rb +8 -6
  15. data/lib/active_resource/exceptions.rb +17 -5
  16. data/lib/active_resource/formats/json_format.rb +4 -1
  17. data/lib/active_resource/formats/xml_format.rb +4 -2
  18. data/lib/active_resource/formats.rb +5 -3
  19. data/lib/active_resource/http_mock.rb +23 -27
  20. data/lib/active_resource/inheriting_hash.rb +15 -0
  21. data/lib/active_resource/log_subscriber.rb +14 -3
  22. data/lib/active_resource/railtie.rb +10 -10
  23. data/lib/active_resource/reflection.rb +11 -10
  24. data/lib/active_resource/schema.rb +6 -3
  25. data/lib/active_resource/singleton.rb +25 -28
  26. data/lib/active_resource/threadsafe_attributes.rb +35 -31
  27. data/lib/active_resource/validations.rb +18 -15
  28. data/lib/active_resource/version.rb +6 -4
  29. data/lib/active_resource.rb +8 -7
  30. data/lib/activeresource.rb +3 -1
  31. metadata +41 -24
  32. data/README.rdoc +0 -231
  33. data/lib/active_resource/observing.rb +0 -31
@@ -1,24 +1,27 @@
1
- require 'active_support'
2
- require 'active_support/core_ext/class/attribute_accessors'
3
- require 'active_support/core_ext/class/attribute'
4
- require 'active_support/core_ext/hash/indifferent_access'
5
- require 'active_support/core_ext/kernel/reporting'
6
- require 'active_support/core_ext/module/delegation'
7
- require 'active_support/core_ext/module/aliasing'
8
- require 'active_support/core_ext/object/blank'
9
- require 'active_support/core_ext/object/to_query'
10
- require 'active_support/core_ext/object/duplicable'
11
- require 'set'
12
- require 'uri'
13
-
14
- require 'active_support/core_ext/uri'
15
- require 'active_resource/connection'
16
- require 'active_resource/formats'
17
- require 'active_resource/schema'
18
- require 'active_resource/log_subscriber'
19
- require 'active_resource/associations'
20
- require 'active_resource/reflection'
21
- require 'active_resource/threadsafe_attributes'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/class/attribute_accessors"
5
+ require "active_support/core_ext/class/attribute"
6
+ require "active_support/core_ext/hash/indifferent_access"
7
+ require "active_support/core_ext/kernel/reporting"
8
+ require "active_support/core_ext/module/delegation"
9
+ require "active_support/core_ext/module/aliasing"
10
+ require "active_support/core_ext/object/blank"
11
+ require "active_support/core_ext/object/to_query"
12
+ require "active_support/core_ext/object/duplicable"
13
+ require "set"
14
+ require "uri"
15
+
16
+ require "active_resource/connection"
17
+ require "active_resource/formats"
18
+ require "active_resource/schema"
19
+ require "active_resource/log_subscriber"
20
+ require "active_resource/associations"
21
+ require "active_resource/reflection"
22
+ require "active_resource/threadsafe_attributes"
23
+
24
+ require "active_model/serializers/xml"
22
25
 
23
26
  module ActiveResource
24
27
  # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
@@ -123,18 +126,25 @@ module ActiveResource
123
126
  # requests. These sensitive credentials are sent unencrypted, visible to
124
127
  # any onlooker, so this scheme should only be used with SSL.
125
128
  #
126
- # Digest authentication sends a crytographic hash of the username, password,
129
+ # Digest authentication sends a cryptographic hash of the username, password,
127
130
  # HTTP method, URI, and a single-use secret key provided by the server.
128
131
  # Sensitive credentials aren't visible to onlookers, so digest authentication
129
132
  # doesn't require SSL. However, this doesn't mean the connection is secure!
130
133
  # Just the username and password.
131
134
  #
135
+ # Another common way to authenticate requests is via bearer tokens, a scheme
136
+ # originally created as part of the OAuth 2.0 protocol (see RFC 6750).
137
+ #
138
+ # Bearer authentication sends a token, that can maybe only be a short string
139
+ # of hexadecimal characters or even a JWT Token. Similarly to the Basic
140
+ # authentication, this scheme should only be used with SSL.
141
+ #
132
142
  # (You really, really want to use SSL. There's little reason not to.)
133
143
  #
134
144
  # === Picking an authentication scheme
135
145
  #
136
- # Basic authentication is the default. To switch to digest authentication,
137
- # set +auth_type+ to +:digest+:
146
+ # Basic authentication is the default. To switch to digest or bearer token authentication,
147
+ # set +auth_type+ to +:digest+ or +:bearer+:
138
148
  #
139
149
  # class Person < ActiveResource::Base
140
150
  # self.auth_type = :digest
@@ -153,6 +163,16 @@ module ActiveResource
153
163
  # self.site = "https://ryan:password@api.people.com"
154
164
  # end
155
165
  #
166
+ # === Setting the bearer token
167
+ #
168
+ # Set +bearer_token+ on the class:
169
+ #
170
+ # class Person < ActiveResource::Base
171
+ # # Set bearer token directly:
172
+ # self.auth_type = :bearer
173
+ # self.bearer_token = "my-bearer-token"
174
+ # end
175
+ #
156
176
  # === Certificate Authentication
157
177
  #
158
178
  # You can also authenticate using an X509 certificate. <tt>See ssl_options=</tt> for all options.
@@ -198,7 +218,9 @@ module ActiveResource
198
218
  # * 405 - ActiveResource::MethodNotAllowed
199
219
  # * 409 - ActiveResource::ResourceConflict
200
220
  # * 410 - ActiveResource::ResourceGone
221
+ # * 412 - ActiveResource::PreconditionFailed
201
222
  # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
223
+ # * 429 - ActiveResource::TooManyRequests
202
224
  # * 401..499 - ActiveResource::ClientError
203
225
  # * 500..599 - ActiveResource::ServerError
204
226
  # * Other - ActiveResource::ConnectionError
@@ -298,17 +320,24 @@ module ActiveResource
298
320
  ##
299
321
  # :singleton-method:
300
322
  # The logger for diagnosing and tracing Active Resource calls.
301
- cattr_accessor :logger
323
+ cattr_reader :logger
324
+
325
+ def self.logger=(logger)
326
+ self._connection = nil
327
+ @@logger = logger
328
+ end
302
329
 
303
330
  class_attribute :_format
304
331
  class_attribute :_collection_parser
305
332
  class_attribute :include_format_in_path
306
333
  self.include_format_in_path = true
307
334
 
335
+ class_attribute :connection_class
336
+ self.connection_class = Connection
308
337
 
309
338
  class << self
310
339
  include ThreadsafeAttributes
311
- threadsafe_attribute :_headers, :_connection, :_user, :_password, :_site, :_proxy
340
+ threadsafe_attribute :_headers, :_connection, :_user, :_password, :_bearer_token, :_site, :_proxy
312
341
 
313
342
  # Creates a schema for this resource - setting the attributes that are
314
343
  # known prior to fetching an instance from the remote system.
@@ -374,12 +403,12 @@ module ActiveResource
374
403
  @schema ||= {}.with_indifferent_access
375
404
  @known_attributes ||= []
376
405
 
377
- schema_definition.attrs.each do |k,v|
406
+ schema_definition.attrs.each do |k, v|
378
407
  @schema[k] = v
379
408
  @known_attributes << k
380
409
  end
381
410
 
382
- schema
411
+ @schema
383
412
  else
384
413
  @schema ||= nil
385
414
  end
@@ -396,7 +425,7 @@ module ActiveResource
396
425
  # example:
397
426
  #
398
427
  # class Person < ActiveResource::Base
399
- # schema = {'name' => :string, 'age' => :integer }
428
+ # self.schema = {'name' => :string, 'age' => :integer }
400
429
  # end
401
430
  #
402
431
  # The keys/values can be strings or symbols. They will be converted to
@@ -413,7 +442,7 @@ module ActiveResource
413
442
  raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
414
443
 
415
444
  schema do
416
- the_schema.each {|k,v| attribute(k,v) }
445
+ the_schema.each { |k, v| attribute(k, v) }
417
446
  end
418
447
  end
419
448
 
@@ -461,8 +490,8 @@ module ActiveResource
461
490
  self._site = nil
462
491
  else
463
492
  self._site = create_site_uri_from(site)
464
- self._user = URI.parser.unescape(_site.user) if _site.user
465
- self._password = URI.parser.unescape(_site.password) if _site.password
493
+ self._user = URI::DEFAULT_PARSER.unescape(_site.user) if _site.user
494
+ self._password = URI::DEFAULT_PARSER.unescape(_site.password) if _site.password
466
495
  end
467
496
  end
468
497
 
@@ -514,6 +543,22 @@ module ActiveResource
514
543
  self._password = password
515
544
  end
516
545
 
546
+ # Gets the \bearer_token for REST HTTP authentication.
547
+ def bearer_token
548
+ # Not using superclass_delegating_reader. See +site+ for explanation
549
+ if _bearer_token_defined?
550
+ _bearer_token
551
+ elsif superclass != Object && superclass.bearer_token
552
+ superclass.bearer_token.dup.freeze
553
+ end
554
+ end
555
+
556
+ # Sets the \bearer_token for REST HTTP authentication.
557
+ def bearer_token=(bearer_token)
558
+ self._connection = nil
559
+ self._bearer_token = bearer_token
560
+ end
561
+
517
562
  def auth_type
518
563
  if defined?(@auth_type)
519
564
  @auth_type
@@ -615,7 +660,7 @@ module ActiveResource
615
660
  # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
616
661
  def ssl_options=(options)
617
662
  self._connection = nil
618
- @ssl_options = options
663
+ @ssl_options = options
619
664
  end
620
665
 
621
666
  # Returns the SSL options hash.
@@ -632,10 +677,13 @@ module ActiveResource
632
677
  # or not (defaults to <tt>false</tt>).
633
678
  def connection(refresh = false)
634
679
  if _connection_defined? || superclass == Object
635
- self._connection = Connection.new(site, format) if refresh || _connection.nil?
680
+ self._connection = connection_class.new(
681
+ site, format, logger: logger
682
+ ) if refresh || _connection.nil?
636
683
  _connection.proxy = proxy if proxy
637
684
  _connection.user = user if user
638
685
  _connection.password = password if password
686
+ _connection.bearer_token = bearer_token if bearer_token
639
687
  _connection.auth_type = auth_type if auth_type
640
688
  _connection.timeout = timeout if timeout
641
689
  _connection.open_timeout = open_timeout if open_timeout
@@ -648,11 +696,10 @@ module ActiveResource
648
696
  end
649
697
 
650
698
  def headers
651
- self._headers ||= {}
652
- if superclass != Object && superclass.headers
653
- self._headers = superclass.headers.merge(_headers)
699
+ self._headers ||= if superclass != Object
700
+ InheritingHash.new(superclass.headers)
654
701
  else
655
- _headers
702
+ {}
656
703
  end
657
704
  end
658
705
 
@@ -678,15 +725,15 @@ module ActiveResource
678
725
  return primary_key if primary_key.is_a?(Symbol)
679
726
  primary_key.dup.freeze
680
727
  else
681
- 'id'
728
+ "id"
682
729
  end
683
730
  end
684
731
 
685
732
  # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
686
733
  # This method is regenerated at runtime based on what the \prefix is set to.
687
- def prefix(options={})
734
+ def prefix(options = {})
688
735
  default = site.path
689
- default << '/' unless default[-1..-1] == '/'
736
+ default << "/" unless default[-1..-1] == "/"
690
737
  # generate the actual method based on the current site path
691
738
  self.prefix = default
692
739
  prefix(options)
@@ -701,9 +748,9 @@ module ActiveResource
701
748
 
702
749
  # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
703
750
  # Default value is <tt>site.path</tt>.
704
- def prefix=(value = '/')
751
+ def prefix=(value = "/")
705
752
  # Replace :placeholders with '#{embedded options[:lookups]}'
706
- prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.parser.escape options[#{key}].to_s}" }
753
+ prefix_call = value.gsub(/:\w+/) { |key| "\#{URI::DEFAULT_PARSER.escape options[#{key}].to_s}" }
707
754
 
708
755
  # Clear prefix parameters in case they have been cached
709
756
  @prefix_parameters = nil
@@ -720,10 +767,10 @@ module ActiveResource
720
767
  raise
721
768
  end
722
769
 
723
- alias_method :set_prefix, :prefix= #:nodoc:
770
+ alias_method :set_prefix, :prefix= # :nodoc:
724
771
 
725
- alias_method :set_element_name, :element_name= #:nodoc:
726
- alias_method :set_collection_name, :collection_name= #:nodoc:
772
+ alias_method :set_element_name, :element_name= # :nodoc:
773
+ alias_method :set_collection_name, :collection_name= # :nodoc:
727
774
 
728
775
  def format_extension
729
776
  include_format_in_path ? ".#{format.extension}" : ""
@@ -759,7 +806,37 @@ module ActiveResource
759
806
  check_prefix_options(prefix_options)
760
807
 
761
808
  prefix_options, query_options = split_options(prefix_options) if query_options.nil?
762
- "#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}#{format_extension}#{query_string(query_options)}"
809
+ "#{prefix(prefix_options)}#{collection_name}/#{URI.encode_www_form_component(id.to_s)}#{format_extension}#{query_string(query_options)}"
810
+ end
811
+
812
+ # Gets the element url for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
813
+ # will split from the \prefix options.
814
+ #
815
+ # ==== Options
816
+ # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
817
+ # would yield a URL like <tt>https://37s.sunrise.com/accounts/19/purchases.json</tt>).
818
+ #
819
+ # +query_options+ - A \hash to add items to the query string for the request.
820
+ #
821
+ # ==== Examples
822
+ # Post.element_url(1)
823
+ # # => https://37s.sunrise.com/posts/1.json
824
+ #
825
+ # class Comment < ActiveResource::Base
826
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
827
+ # end
828
+ #
829
+ # Comment.element_url(1, :post_id => 5)
830
+ # # => https://37s.sunrise.com/posts/5/comments/1.json
831
+ #
832
+ # Comment.element_url(1, :post_id => 5, :active => 1)
833
+ # # => https://37s.sunrise.com/posts/5/comments/1.json?active=1
834
+ #
835
+ # Comment.element_url(1, {:post_id => 5}, {:active => 1})
836
+ # # => https://37s.sunrise.com/posts/5/comments/1.json?active=1
837
+ #
838
+ def element_url(id, prefix_options = {}, query_options = nil)
839
+ URI.join(site, element_path(id, prefix_options, query_options)).to_s
763
840
  end
764
841
 
765
842
  # Gets the new element path for REST resources.
@@ -809,7 +886,7 @@ module ActiveResource
809
886
  "#{prefix(prefix_options)}#{collection_name}#{format_extension}#{query_string(query_options)}"
810
887
  end
811
888
 
812
- alias_method :set_primary_key, :primary_key= #:nodoc:
889
+ alias_method :set_primary_key, :primary_key= # :nodoc:
813
890
 
814
891
  # Builds a new, unsaved record using the default values from the remote server so
815
892
  # that it can be used with RESTful forms.
@@ -852,6 +929,17 @@ module ActiveResource
852
929
  self.new(attributes).tap { |resource| resource.save }
853
930
  end
854
931
 
932
+ # Creates a new resource (just like <tt>create</tt>) and makes a request to the
933
+ # remote service that it be saved, but runs validations and raises
934
+ # <tt>ActiveResource::ResourceInvalid</tt>, making it equivalent to the following
935
+ # simultaneous calls:
936
+ #
937
+ # ryan = Person.new(:first => 'ryan')
938
+ # ryan.save!
939
+ def create!(attributes = {})
940
+ self.new(attributes).tap { |resource| resource.save! }
941
+ end
942
+
855
943
  # Core method for finding resources. Used similarly to Active Record's +find+ method.
856
944
  #
857
945
  # ==== Arguments
@@ -916,11 +1004,18 @@ module ActiveResource
916
1004
  options = arguments.slice!(0) || {}
917
1005
 
918
1006
  case scope
919
- when :all then find_every(options)
920
- when :first then find_every(options).to_a.first
921
- when :last then find_every(options).to_a.last
922
- when :one then find_one(options)
923
- else find_single(scope, options)
1007
+ when :all
1008
+ find_every(options)
1009
+ when :first
1010
+ collection = find_every(options)
1011
+ collection && collection.first
1012
+ when :last
1013
+ collection = find_every(options)
1014
+ collection && collection.last
1015
+ when :one
1016
+ find_one(options)
1017
+ else
1018
+ find_single(scope, options)
924
1019
  end
925
1020
  end
926
1021
 
@@ -947,7 +1042,7 @@ module ActiveResource
947
1042
 
948
1043
  def where(clauses = {})
949
1044
  raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
950
- find(:all, :params => clauses)
1045
+ find(:all, params: clauses)
951
1046
  end
952
1047
 
953
1048
 
@@ -981,7 +1076,7 @@ module ActiveResource
981
1076
  prefix_options, query_options = split_options(options[:params])
982
1077
  path = element_path(id, prefix_options, query_options)
983
1078
  response = connection.head(path, headers)
984
- response.code.to_i == 200
1079
+ (200..206).include? response.code.to_i
985
1080
  end
986
1081
  # id && !find_single(id, options).nil?
987
1082
  rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
@@ -989,7 +1084,6 @@ module ActiveResource
989
1084
  end
990
1085
 
991
1086
  private
992
-
993
1087
  def check_prefix_options(prefix_options)
994
1088
  p_options = HashWithIndifferentAccess.new(prefix_options)
995
1089
  prefix_parameters.each do |p|
@@ -999,23 +1093,26 @@ module ActiveResource
999
1093
 
1000
1094
  # Find every resource
1001
1095
  def find_every(options)
1002
- begin
1096
+ params = options[:params]
1097
+ prefix_options, query_options = split_options(params)
1098
+
1099
+ response =
1003
1100
  case from = options[:from]
1004
1101
  when Symbol
1005
- instantiate_collection(get(from, options[:params]), options[:params])
1102
+ get(from, params)
1006
1103
  when String
1007
- path = "#{from}#{query_string(options[:params])}"
1008
- instantiate_collection(format.decode(connection.get(path, headers).body) || [], options[:params])
1104
+ path = "#{from}#{query_string(query_options)}"
1105
+ format.decode(connection.get(path, headers).body)
1009
1106
  else
1010
- prefix_options, query_options = split_options(options[:params])
1011
1107
  path = collection_path(prefix_options, query_options)
1012
- instantiate_collection( (format.decode(connection.get(path, headers).body) || []), query_options, prefix_options )
1108
+ format.decode(connection.get(path, headers).body)
1013
1109
  end
1014
- rescue ActiveResource::ResourceNotFound
1015
- # Swallowing ResourceNotFound exceptions and return nil - as per
1016
- # ActiveRecord.
1017
- nil
1018
- end
1110
+
1111
+ instantiate_collection(response || [], query_options, prefix_options)
1112
+ rescue ActiveResource::ResourceNotFound
1113
+ # Swallowing ResourceNotFound exceptions and return nil - as per
1114
+ # ActiveRecord.
1115
+ nil
1019
1116
  end
1020
1117
 
1021
1118
  # Find a single resource from a one-off URL
@@ -1076,16 +1173,16 @@ module ActiveResource
1076
1173
  prefix_options, query_options = {}, {}
1077
1174
 
1078
1175
  (options || {}).each do |key, value|
1079
- next if key.blank? || !key.respond_to?(:to_sym)
1080
- (prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
1176
+ next if key.blank?
1177
+ (prefix_parameters.include?(key.to_s.to_sym) ? prefix_options : query_options)[key.to_s.to_sym] = value
1081
1178
  end
1082
1179
 
1083
1180
  [ prefix_options, query_options ]
1084
1181
  end
1085
1182
  end
1086
1183
 
1087
- attr_accessor :attributes #:nodoc:
1088
- attr_accessor :prefix_options #:nodoc:
1184
+ attr_accessor :attributes # :nodoc:
1185
+ attr_accessor :prefix_options # :nodoc:
1089
1186
 
1090
1187
  # If no schema has been defined for the class (see
1091
1188
  # <tt>ActiveResource::schema=</tt>), the default automatic schema is
@@ -1142,13 +1239,13 @@ module ActiveResource
1142
1239
  # not_ryan.hash # => {:not => "an ARes instance"}
1143
1240
  def clone
1144
1241
  # Clone all attributes except the pk and any nested ARes
1145
- cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
1242
+ cloned = attributes.reject { |k, v| k == self.class.primary_key || v.is_a?(ActiveResource::Base) }.transform_values { |v| v.clone }
1146
1243
  # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
1147
1244
  # attempts to convert hashes into member objects and arrays into collections of objects. We want
1148
1245
  # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
1149
1246
  resource = self.class.new({})
1150
1247
  resource.prefix_options = self.prefix_options
1151
- resource.send :instance_variable_set, '@attributes', cloned
1248
+ resource.send :instance_variable_set, "@attributes", cloned
1152
1249
  resource
1153
1250
  end
1154
1251
 
@@ -1323,13 +1420,13 @@ module ActiveResource
1323
1420
  # Person.delete(guys_id)
1324
1421
  # that_guy.exists? # => false
1325
1422
  def exists?
1326
- !new? && self.class.exists?(to_param, :params => prefix_options)
1423
+ !new? && self.class.exists?(to_param, params: prefix_options)
1327
1424
  end
1328
1425
 
1329
1426
  # Returns the serialized string representation of the resource in the configured
1330
1427
  # serialization format specified in ActiveResource::Base.format. The options
1331
1428
  # applicable depend on the configured encoding format.
1332
- def encode(options={})
1429
+ def encode(options = {})
1333
1430
  send("to_#{self.class.format.extension}", options)
1334
1431
  end
1335
1432
 
@@ -1345,7 +1442,7 @@ module ActiveResource
1345
1442
  # my_branch.reload
1346
1443
  # my_branch.name # => "Wilson Road"
1347
1444
  def reload
1348
- self.load(self.class.find(to_param, :params => @prefix_options).attributes, false, true)
1445
+ self.load(self.class.find(to_param, params: @prefix_options).attributes, false, true)
1349
1446
  end
1350
1447
 
1351
1448
  # A method to manually load attributes from a \hash. Recursively loads collections of
@@ -1370,7 +1467,11 @@ module ActiveResource
1370
1467
  # your_supplier.load(my_attrs)
1371
1468
  # your_supplier.save
1372
1469
  def load(attributes, remove_root = false, persisted = false)
1373
- raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
1470
+ unless attributes.respond_to?(:to_hash)
1471
+ raise ArgumentError, "expected attributes to be able to convert to Hash, got #{attributes.inspect}"
1472
+ end
1473
+
1474
+ attributes = attributes.to_hash
1374
1475
  @prefix_options, attributes = split_options(attributes)
1375
1476
 
1376
1477
  if attributes.keys.size == 1
@@ -1382,21 +1483,21 @@ module ActiveResource
1382
1483
  attributes.each do |key, value|
1383
1484
  @attributes[key.to_s] =
1384
1485
  case value
1385
- when Array
1386
- resource = nil
1387
- value.map do |attrs|
1388
- if attrs.is_a?(Hash)
1389
- resource ||= find_or_create_resource_for_collection(key)
1390
- resource.new(attrs, persisted)
1391
- else
1392
- attrs.duplicable? ? attrs.dup : attrs
1393
- end
1486
+ when Array
1487
+ resource = nil
1488
+ value.map do |attrs|
1489
+ if attrs.is_a?(Hash)
1490
+ resource ||= find_or_create_resource_for_collection(key)
1491
+ resource.new(attrs, persisted)
1492
+ else
1493
+ attrs.duplicable? ? attrs.dup : attrs
1394
1494
  end
1395
- when Hash
1396
- resource = find_or_create_resource_for(key)
1397
- resource.new(value, persisted)
1398
- else
1399
- value.duplicable? ? value.dup : value
1495
+ end
1496
+ when Hash
1497
+ resource = find_or_create_resource_for(key)
1498
+ resource.new(value, persisted)
1499
+ else
1500
+ value.duplicable? ? value.dup : value
1400
1501
  end
1401
1502
  end
1402
1503
  self
@@ -1438,7 +1539,7 @@ module ActiveResource
1438
1539
  # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
1439
1540
  # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
1440
1541
  # <tt>my_person.respond_to?(:name?)</tt>.
1441
- def respond_to?(method, include_priv = false)
1542
+ def respond_to_missing?(method, include_priv = false)
1442
1543
  method_name = method.to_s
1443
1544
  if attributes.nil?
1444
1545
  super
@@ -1453,12 +1554,20 @@ module ActiveResource
1453
1554
  end
1454
1555
  end
1455
1556
 
1456
- def to_json(options={})
1457
- super(include_root_in_json ? { :root => self.class.element_name }.merge(options) : options)
1557
+ def to_json(options = {})
1558
+ super(include_root_in_json ? { root: self.class.element_name }.merge(options) : options)
1559
+ end
1560
+
1561
+ def to_xml(options = {})
1562
+ super({ root: self.class.element_name }.merge(options))
1458
1563
  end
1459
1564
 
1460
- def to_xml(options={})
1461
- super({ :root => self.class.element_name }.merge(options))
1565
+ def read_attribute_for_serialization(n)
1566
+ if !attributes[n].nil?
1567
+ attributes[n]
1568
+ elsif respond_to?(n)
1569
+ send(n)
1570
+ end
1462
1571
  end
1463
1572
 
1464
1573
  protected
@@ -1486,9 +1595,9 @@ module ActiveResource
1486
1595
  end
1487
1596
 
1488
1597
  def load_attributes_from_response(response)
1489
- if (response_code_allows_body?(response.code) &&
1490
- (response['Content-Length'].nil? || response['Content-Length'] != "0") &&
1491
- !response.body.nil? && response.body.strip.size > 0)
1598
+ if response_code_allows_body?(response.code.to_i) &&
1599
+ (response["Content-Length"].nil? || response["Content-Length"] != "0") &&
1600
+ !response.body.nil? && response.body.strip.size > 0
1492
1601
  load(self.class.format.decode(response.body), true, true)
1493
1602
  @persisted = true
1494
1603
  end
@@ -1496,13 +1605,17 @@ module ActiveResource
1496
1605
 
1497
1606
  # Takes a response from a typical create post and pulls the ID out
1498
1607
  def id_from_response(response)
1499
- response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] if response['Location']
1608
+ response["Location"][/\/([^\/]*?)(\.\w+)?$/, 1] if response["Location"]
1500
1609
  end
1501
1610
 
1502
1611
  def element_path(options = nil)
1503
1612
  self.class.element_path(to_param, options || prefix_options)
1504
1613
  end
1505
1614
 
1615
+ def element_url(options = nil)
1616
+ self.class.element_url(to_param, options || prefix_options)
1617
+ end
1618
+
1506
1619
  def new_element_path
1507
1620
  self.class.new_element_path(prefix_options)
1508
1621
  end
@@ -1512,14 +1625,9 @@ module ActiveResource
1512
1625
  end
1513
1626
 
1514
1627
  private
1515
-
1516
- def read_attribute_for_serialization(n)
1517
- attributes[n]
1518
- end
1519
-
1520
1628
  # Determine whether the response is allowed to have a body per HTTP 1.1 spec section 4.4.1
1521
1629
  def response_code_allows_body?(c)
1522
- !((100..199).include?(c) || [204,304].include?(c))
1630
+ !((100..199).include?(c) || [204, 304].include?(c))
1523
1631
  end
1524
1632
 
1525
1633
  # Tries to find a resource for a given collection name; if it fails, then the resource is created
@@ -1532,7 +1640,7 @@ module ActiveResource
1532
1640
  # if it fails, then the resource is created
1533
1641
  def find_or_create_resource_in_modules(resource_name, module_names)
1534
1642
  receiver = Object
1535
- namespaces = module_names[0, module_names.size-1].map do |module_name|
1643
+ namespaces = module_names[0, module_names.size - 1].map do |module_name|
1536
1644
  receiver = receiver.const_get(module_name)
1537
1645
  end
1538
1646
  const_args = [resource_name, false]
@@ -1549,7 +1657,11 @@ module ActiveResource
1549
1657
  resource_name = name.to_s.camelize
1550
1658
 
1551
1659
  const_args = [resource_name, false]
1552
- if self.class.const_defined?(*const_args)
1660
+
1661
+ if !const_valid?(*const_args)
1662
+ # resource_name is not a valid ruby module name and cannot be created normally
1663
+ find_or_create_resource_for(:UnnamedResource)
1664
+ elsif self.class.const_defined?(*const_args)
1553
1665
  self.class.const_get(*const_args)
1554
1666
  else
1555
1667
  ancestors = self.class.name.to_s.split("::")
@@ -1565,11 +1677,20 @@ module ActiveResource
1565
1677
  end
1566
1678
  end
1567
1679
 
1680
+ def const_valid?(*const_args)
1681
+ self.class.const_defined?(*const_args)
1682
+ true
1683
+ rescue NameError
1684
+ false
1685
+ end
1686
+
1568
1687
  # Create and return a class definition for a resource inside the current resource
1569
1688
  def create_resource_for(resource_name)
1570
- resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
1689
+ resource = Class.new(ActiveResource::Base)
1571
1690
  resource.prefix = self.class.prefix
1572
- resource.site = self.class.site
1691
+ resource.site = self.class.site
1692
+ self.class.const_set(resource_name, resource)
1693
+
1573
1694
  resource
1574
1695
  end
1575
1696
 
@@ -1577,7 +1698,7 @@ module ActiveResource
1577
1698
  self.class.__send__(:split_options, options)
1578
1699
  end
1579
1700
 
1580
- def method_missing(method_symbol, *arguments) #:nodoc:
1701
+ def method_missing(method_symbol, *arguments) # :nodoc:
1581
1702
  method_name = method_symbol.to_s
1582
1703
 
1583
1704
  if method_name =~ /(=|\?)$/
@@ -1600,7 +1721,7 @@ module ActiveResource
1600
1721
  extend ActiveModel::Naming
1601
1722
  extend ActiveResource::Associations
1602
1723
 
1603
- include Callbacks, CustomMethods, Observing, Validations
1724
+ include Callbacks, CustomMethods, Validations
1604
1725
  include ActiveModel::Conversion
1605
1726
  include ActiveModel::Serializers::JSON
1606
1727
  include ActiveModel::Serializers::Xml
@@ -1609,4 +1730,3 @@ module ActiveResource
1609
1730
 
1610
1731
  ActiveSupport.run_load_hooks(:active_resource, Base)
1611
1732
  end
1612
-