activeresource 3.0.20 → 3.1.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.

data/CHANGELOG CHANGED
@@ -1,70 +1,14 @@
1
- ## Rails 3.0.20 (unreleased)
1
+ *Rails 3.1.0 (unreleased)*
2
2
 
3
- ## Rails 3.0.19 (Jan 8, 2013)
3
+ * No changes
4
4
 
5
- * No changes.
6
-
7
- ## Rails 3.0.18 (Jan 2, 2013)
8
-
9
- * No changes.
10
-
11
- ## Rails 3.0.14 (Jun 12, 2012)
12
-
13
- * No changes.
14
-
15
- * Rails 3.0.13 (May 31, 2012)
16
-
17
- * No changes.
18
-
19
- *Rails 3.0.10 (August 16, 2011)*
20
-
21
- * No changes.
22
-
23
-
24
- *Rails 3.0.9 (June 16, 2011)*
25
-
26
- *No changes.
27
-
28
-
29
- *Rails 3.0.8 (June 7, 2011)*
30
-
31
- *No changes.
32
-
33
-
34
- *Rails 3.0.7 (April 18, 2011)*
35
-
36
- *No changes.
37
-
38
-
39
- *Rails 3.0.6 (April 5, 2011)
40
-
41
- * No changes.
42
-
43
-
44
- *Rails 3.0.5 (February 26, 2011)*
45
-
46
- * No changes.
47
-
48
-
49
- *Rails 3.0.4 (February 8, 2011)*
50
-
51
- * No changes.
52
-
53
-
54
- *Rails 3.0.3 (November 16, 2010)*
55
-
56
- * No changes.
57
-
58
-
59
- *Rails 3.0.2 (November 15, 2010)*
60
-
61
- * No changes.
5
+ *Rails 3.0.2 (unreleased)*
62
6
 
7
+ * No changes
63
8
 
64
9
  *Rails 3.0.1 (October 15, 2010)*
65
10
 
66
- * No changes.
67
-
11
+ * No Changes, just a version bump.
68
12
 
69
13
  *Rails 3.0.0 (August 29, 2010)*
70
14
 
data/README.rdoc CHANGED
@@ -95,7 +95,7 @@ Collections can also be requested in a similar fashion
95
95
  #
96
96
  # for GET http://api.people.com:3000/people.xml
97
97
  #
98
- people = Person.find(:all)
98
+ people = Person.all
99
99
  people.first # => <Person::xxx 'first' => 'Ryan' ...>
100
100
  people.last # => <Person::xxx 'first' => 'Jim' ...>
101
101
 
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+ require 'active_resource'
3
+ require 'benchmark'
4
+
5
+ TIMES = (ENV['N'] || 10_000).to_i
6
+
7
+ # deep nested resource
8
+ attrs = {
9
+ :id => 1,
10
+ :name => 'Luis',
11
+ :age => 21,
12
+ :friends => [
13
+ {
14
+ :name => 'JK',
15
+ :age => 24,
16
+ :colors => ['red', 'green', 'blue'],
17
+ :brothers => [
18
+ {
19
+ :name => 'Mateo',
20
+ :age => 35,
21
+ :children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }]
22
+ },
23
+ {
24
+ :name => 'Felipe',
25
+ :age => 33,
26
+ :children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }]
27
+ }
28
+ ]
29
+ },
30
+ {
31
+ :name => 'Eduardo',
32
+ :age => 20,
33
+ :colors => [],
34
+ :brothers => [
35
+ {
36
+ :name => 'Sebas',
37
+ :age => 23,
38
+ :children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }]
39
+ },
40
+ {
41
+ :name => 'Elsa',
42
+ :age => 19,
43
+ :children => [{ :name => 'Natacha', :age => 1 }]
44
+ },
45
+ {
46
+ :name => 'Milena',
47
+ :age => 16,
48
+ :children => []
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }
54
+
55
+ class Customer < ActiveResource::Base
56
+ self.site = "http://37s.sunrise.i:3000"
57
+ end
58
+
59
+ module Nested
60
+ class Customer < ActiveResource::Base
61
+ self.site = "http://37s.sunrise.i:3000"
62
+ end
63
+ end
64
+
65
+ Benchmark.bm(40) do |x|
66
+ x.report('Model.new (instantiation)') { TIMES.times { Customer.new } }
67
+ x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } }
68
+ x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } }
69
+ x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } }
70
+ end
@@ -1,6 +1,6 @@
1
1
  require 'active_support'
2
2
  require 'active_support/core_ext/class/attribute_accessors'
3
- require 'active_support/core_ext/class/inheritable_attributes'
3
+ require 'active_support/core_ext/class/attribute'
4
4
  require 'active_support/core_ext/hash/indifferent_access'
5
5
  require 'active_support/core_ext/kernel/reporting'
6
6
  require 'active_support/core_ext/module/attr_accessor_with_default'
@@ -12,6 +12,7 @@ require 'active_support/core_ext/object/duplicable'
12
12
  require 'set'
13
13
  require 'uri'
14
14
 
15
+ require 'active_support/core_ext/uri'
15
16
  require 'active_resource/exceptions'
16
17
  require 'active_resource/connection'
17
18
  require 'active_resource/formats'
@@ -166,6 +167,7 @@ module ActiveResource
166
167
  # # GET http://api.people.com:3000/people/999.xml
167
168
  # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
168
169
  #
170
+ #
169
171
  # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
170
172
  # following HTTP response codes will also result in these exceptions:
171
173
  #
@@ -194,6 +196,16 @@ module ActiveResource
194
196
  # redirect_to :action => 'new'
195
197
  # end
196
198
  #
199
+ # When a GET is requested for a nested resource and you don't provide the prefix_param
200
+ # an ActiveResource::MissingPrefixParam will be raised.
201
+ #
202
+ # class Comment < ActiveResource::Base
203
+ # self.site = "http://someip.com/posts/:post_id/"
204
+ # end
205
+ #
206
+ # Comment.find(1)
207
+ # # => ActiveResource::MissingPrefixParam: post_id prefix_option is missing
208
+ #
197
209
  # === Validation errors
198
210
  #
199
211
  # Active Resource supports validations on resources and will return errors if any of these validations fail
@@ -251,6 +263,8 @@ module ActiveResource
251
263
  # The logger for diagnosing and tracing Active Resource calls.
252
264
  cattr_accessor :logger
253
265
 
266
+ class_attribute :_format
267
+
254
268
  class << self
255
269
  # Creates a schema for this resource - setting the attributes that are
256
270
  # known prior to fetching an instance from the remote system.
@@ -403,8 +417,8 @@ module ActiveResource
403
417
  @site = nil
404
418
  else
405
419
  @site = create_site_uri_from(site)
406
- @user = uri_parser.unescape(@site.user) if @site.user
407
- @password = uri_parser.unescape(@site.password) if @site.password
420
+ @user = URI.parser.unescape(@site.user) if @site.user
421
+ @password = URI.parser.unescape(@site.password) if @site.password
408
422
  end
409
423
  end
410
424
 
@@ -480,13 +494,13 @@ module ActiveResource
480
494
  format = mime_type_reference_or_format.is_a?(Symbol) ?
481
495
  ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
482
496
 
483
- write_inheritable_attribute(:format, format)
497
+ self._format = format
484
498
  connection.format = format if site
485
499
  end
486
500
 
487
501
  # Returns the current format, default is ActiveResource::Formats::XmlFormat.
488
502
  def format
489
- read_inheritable_attribute(:format) || ActiveResource::Formats::XmlFormat
503
+ self._format || ActiveResource::Formats::XmlFormat
490
504
  end
491
505
 
492
506
  # Sets the number of seconds after which requests to the REST API should time out.
@@ -508,9 +522,9 @@ module ActiveResource
508
522
  #
509
523
  # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
510
524
  # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
511
- # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contrain several CA certificates.
525
+ # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contain several CA certificates.
512
526
  # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
513
- # * <tt>:verify_mode</tt> - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
527
+ # * <tt>:verify_mode</tt> - Flags for server the certification verification at beginning of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
514
528
  # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
515
529
  # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
516
530
  # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
@@ -577,7 +591,7 @@ module ActiveResource
577
591
  # Default value is <tt>site.path</tt>.
578
592
  def prefix=(value = '/')
579
593
  # Replace :placeholders with '#{embedded options[:lookups]}'
580
- prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.escape options[#{key}].to_s}" }
594
+ prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.parser.escape options[#{key}].to_s}" }
581
595
 
582
596
  # Clear prefix parameters in case they have been cached
583
597
  @prefix_parameters = nil
@@ -621,8 +635,10 @@ module ActiveResource
621
635
  # # => /posts/5/comments/1.xml?active=1
622
636
  #
623
637
  def element_path(id, prefix_options = {}, query_options = nil)
638
+ check_prefix_options(prefix_options)
639
+
624
640
  prefix_options, query_options = split_options(prefix_options) if query_options.nil?
625
- "#{prefix(prefix_options)}#{collection_name}/#{URI.escape id.to_s}.#{format.extension}#{query_string(query_options)}"
641
+ "#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}.#{format.extension}#{query_string(query_options)}"
626
642
  end
627
643
 
628
644
  # Gets the new element path for REST resources.
@@ -663,6 +679,7 @@ module ActiveResource
663
679
  # # => /posts/5/comments.xml?active=1
664
680
  #
665
681
  def collection_path(prefix_options = {}, query_options = nil)
682
+ check_prefix_options(prefix_options)
666
683
  prefix_options, query_options = split_options(prefix_options) if query_options.nil?
667
684
  "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
668
685
  end
@@ -678,7 +695,7 @@ module ActiveResource
678
695
  # Returns the new resource instance.
679
696
  #
680
697
  def build(attributes = {})
681
- attrs = connection.get("#{new_element_path}").merge(attributes)
698
+ attrs = self.format.decode(connection.get("#{new_element_path}").body).merge(attributes)
682
699
  self.new(attrs)
683
700
  end
684
701
 
@@ -842,6 +859,14 @@ module ActiveResource
842
859
  end
843
860
 
844
861
  private
862
+
863
+ def check_prefix_options(prefix_options)
864
+ p_options = HashWithIndifferentAccess.new(prefix_options)
865
+ prefix_parameters.each do |p|
866
+ raise(MissingPrefixParam, "#{p} prefix_option is missing") if p_options[p].blank?
867
+ end
868
+ end
869
+
845
870
  # Find every resource
846
871
  def find_every(options)
847
872
  begin
@@ -850,11 +875,11 @@ module ActiveResource
850
875
  instantiate_collection(get(from, options[:params]))
851
876
  when String
852
877
  path = "#{from}#{query_string(options[:params])}"
853
- instantiate_collection(connection.get(path, headers) || [])
878
+ instantiate_collection(format.decode(connection.get(path, headers).body) || [])
854
879
  else
855
880
  prefix_options, query_options = split_options(options[:params])
856
881
  path = collection_path(prefix_options, query_options)
857
- instantiate_collection( (connection.get(path, headers) || []), prefix_options )
882
+ instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
858
883
  end
859
884
  rescue ActiveResource::ResourceNotFound
860
885
  # Swallowing ResourceNotFound exceptions and return nil - as per
@@ -870,7 +895,7 @@ module ActiveResource
870
895
  instantiate_record(get(from, options[:params]))
871
896
  when String
872
897
  path = "#{from}#{query_string(options[:params])}"
873
- instantiate_record(connection.get(path, headers))
898
+ instantiate_record(format.decode(connection.get(path, headers).body))
874
899
  end
875
900
  end
876
901
 
@@ -878,7 +903,7 @@ module ActiveResource
878
903
  def find_single(scope, options)
879
904
  prefix_options, query_options = split_options(options[:params])
880
905
  path = element_path(scope, prefix_options, query_options)
881
- instantiate_record(connection.get(path, headers), prefix_options)
906
+ instantiate_record(format.decode(connection.get(path, headers).body), prefix_options)
882
907
  end
883
908
 
884
909
  def instantiate_collection(collection, prefix_options = {})
@@ -886,7 +911,7 @@ module ActiveResource
886
911
  end
887
912
 
888
913
  def instantiate_record(record, prefix_options = {})
889
- new(record).tap do |resource|
914
+ new(record, true).tap do |resource|
890
915
  resource.prefix_options = prefix_options
891
916
  end
892
917
  end
@@ -894,12 +919,12 @@ module ActiveResource
894
919
 
895
920
  # Accepts a URI and creates the site URI from that.
896
921
  def create_site_uri_from(site)
897
- site.is_a?(URI) ? site.dup : uri_parser.parse(site)
922
+ site.is_a?(URI) ? site.dup : URI.parser.parse(site)
898
923
  end
899
924
 
900
925
  # Accepts a URI and creates the proxy URI from that.
901
926
  def create_proxy_uri_from(proxy)
902
- proxy.is_a?(URI) ? proxy.dup : uri_parser.parse(proxy)
927
+ proxy.is_a?(URI) ? proxy.dup : URI.parser.parse(proxy)
903
928
  end
904
929
 
905
930
  # contains a set of the current prefix parameters.
@@ -924,10 +949,6 @@ module ActiveResource
924
949
 
925
950
  [ prefix_options, query_options ]
926
951
  end
927
-
928
- def uri_parser
929
- @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
930
- end
931
952
  end
932
953
 
933
954
  attr_accessor :attributes #:nodoc:
@@ -959,9 +980,10 @@ module ActiveResource
959
980
  #
960
981
  # my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
961
982
  # my_other_course.save
962
- def initialize(attributes = {})
983
+ def initialize(attributes = {}, persisted = false)
963
984
  @attributes = {}.with_indifferent_access
964
985
  @prefix_options = {}
986
+ @persisted = persisted
965
987
  load(attributes)
966
988
  end
967
989
 
@@ -987,10 +1009,7 @@ module ActiveResource
987
1009
  # not_ryan.hash # => {:not => "an ARes instance"}
988
1010
  def clone
989
1011
  # Clone all attributes except the pk and any nested ARes
990
- cloned = attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.inject({}) do |attrs, (k, v)|
991
- attrs[k] = v.clone
992
- attrs
993
- end
1012
+ cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
994
1013
  # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
995
1014
  # attempts to convert hashes into member objects and arrays into collections of objects. We want
996
1015
  # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
@@ -1014,7 +1033,7 @@ module ActiveResource
1014
1033
  # is_new.new? # => false
1015
1034
  #
1016
1035
  def new?
1017
- id.nil?
1036
+ !persisted?
1018
1037
  end
1019
1038
  alias :new_record? :new?
1020
1039
 
@@ -1031,7 +1050,7 @@ module ActiveResource
1031
1050
  # not_persisted.persisted? # => true
1032
1051
  #
1033
1052
  def persisted?
1034
- !new?
1053
+ @persisted
1035
1054
  end
1036
1055
 
1037
1056
  # Gets the <tt>\id</tt> attribute of the resource.
@@ -1076,7 +1095,7 @@ module ActiveResource
1076
1095
  end
1077
1096
 
1078
1097
  # Delegates to id in order to allow two resources of the same type and \id to work with something like:
1079
- # [Person.find(1), Person.find(2)] & [Person.find(1), Person.find(4)] # => [Person.find(1)]
1098
+ # [(a = Person.find 1), (b = Person.find 2)] & [(c = Person.find 1), (d = Person.find 4)] # => [a]
1080
1099
  def hash
1081
1100
  id.hash
1082
1101
  end
@@ -1220,9 +1239,10 @@ module ActiveResource
1220
1239
  @attributes[key.to_s] =
1221
1240
  case value
1222
1241
  when Array
1223
- resource = find_or_create_resource_for_collection(key)
1242
+ resource = nil
1224
1243
  value.map do |attrs|
1225
1244
  if attrs.is_a?(Hash)
1245
+ resource ||= find_or_create_resource_for_collection(key)
1226
1246
  resource.new(attrs)
1227
1247
  else
1228
1248
  attrs.duplicable? ? attrs.dup : attrs
@@ -1232,7 +1252,7 @@ module ActiveResource
1232
1252
  resource = find_or_create_resource_for(key)
1233
1253
  resource.new(value)
1234
1254
  else
1235
- value.dup rescue value
1255
+ value.duplicable? ? value.dup : value
1236
1256
  end
1237
1257
  end
1238
1258
  self
@@ -1318,8 +1338,9 @@ module ActiveResource
1318
1338
  end
1319
1339
 
1320
1340
  def load_attributes_from_response(response)
1321
- if response['Content-Length'] != "0" && response.body.strip.size > 0
1341
+ if !response['Content-Length'].blank? && response['Content-Length'] != "0" && !response.body.nil? && response.body.strip.size > 0
1322
1342
  load(self.class.format.decode(response.body))
1343
+ @persisted = true
1323
1344
  end
1324
1345
  end
1325
1346
 
@@ -1347,34 +1368,44 @@ module ActiveResource
1347
1368
  end
1348
1369
 
1349
1370
  # Tries to find a resource in a non empty list of nested modules
1350
- # Raises a NameError if it was not found in any of the given nested modules
1351
- def find_resource_in_modules(resource_name, module_names)
1371
+ # if it fails, then the resource is created
1372
+ def find_or_create_resource_in_modules(resource_name, module_names)
1352
1373
  receiver = Object
1353
1374
  namespaces = module_names[0, module_names.size-1].map do |module_name|
1354
1375
  receiver = receiver.const_get(module_name)
1355
1376
  end
1356
- if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(resource_name) }
1357
- return namespace.const_get(resource_name)
1377
+ const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
1378
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
1379
+ namespace.const_get(*const_args)
1358
1380
  else
1359
- raise NameError
1381
+ create_resource_for(resource_name)
1360
1382
  end
1361
1383
  end
1362
1384
 
1363
1385
  # Tries to find a resource for a given name; if it fails, then the resource is created
1364
1386
  def find_or_create_resource_for(name)
1365
1387
  resource_name = name.to_s.camelize
1366
- ancestors = self.class.name.split("::")
1367
- if ancestors.size > 1
1368
- find_resource_in_modules(resource_name, ancestors)
1369
- else
1370
- self.class.const_get(resource_name)
1371
- end
1372
- rescue NameError
1373
- if self.class.const_defined?(resource_name)
1374
- resource = self.class.const_get(resource_name)
1388
+
1389
+ const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
1390
+ if self.class.const_defined?(*const_args)
1391
+ self.class.const_get(*const_args)
1375
1392
  else
1376
- resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
1393
+ ancestors = self.class.name.split("::")
1394
+ if ancestors.size > 1
1395
+ find_or_create_resource_in_modules(resource_name, ancestors)
1396
+ else
1397
+ if Object.const_defined?(*const_args)
1398
+ Object.const_get(*const_args)
1399
+ else
1400
+ create_resource_for(resource_name)
1401
+ end
1402
+ end
1377
1403
  end
1404
+ end
1405
+
1406
+ # Create and return a class definition for a resource inside the current resource
1407
+ def create_resource_for(resource_name)
1408
+ resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
1378
1409
  resource.prefix = self.class.prefix
1379
1410
  resource.site = self.class.site
1380
1411
  resource
@@ -1,4 +1,6 @@
1
1
  require 'active_support/core_ext/benchmark'
2
+ require 'active_support/core_ext/uri'
3
+ require 'active_support/core_ext/object/inclusion'
2
4
  require 'net/https'
3
5
  require 'date'
4
6
  require 'time'
@@ -31,21 +33,20 @@ module ActiveResource
31
33
  def initialize(site, format = ActiveResource::Formats::XmlFormat)
32
34
  raise ArgumentError, 'Missing site URI' unless site
33
35
  @user = @password = nil
34
- @uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
35
36
  self.site = site
36
37
  self.format = format
37
38
  end
38
39
 
39
40
  # Set URI for remote service.
40
41
  def site=(site)
41
- @site = site.is_a?(URI) ? site : @uri_parser.parse(site)
42
- @user = @uri_parser.unescape(@site.user) if @site.user
43
- @password = @uri_parser.unescape(@site.password) if @site.password
42
+ @site = site.is_a?(URI) ? site : URI.parser.parse(site)
43
+ @user = URI.parser.unescape(@site.user) if @site.user
44
+ @password = URI.parser.unescape(@site.password) if @site.password
44
45
  end
45
46
 
46
47
  # Set the proxy for remote service.
47
48
  def proxy=(proxy)
48
- @proxy = proxy.is_a?(URI) ? proxy : @uri_parser.parse(proxy)
49
+ @proxy = proxy.is_a?(URI) ? proxy : URI.parser.parse(proxy)
49
50
  end
50
51
 
51
52
  # Sets the user for remote service.
@@ -76,7 +77,7 @@ module ActiveResource
76
77
  # Executes a GET request.
77
78
  # Used to get (find) resources.
78
79
  def get(path, headers = {})
79
- with_auth { format.decode(request(:get, path, build_request_headers(headers, :get, self.site.merge(path))).body) }
80
+ with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
80
81
  end
81
82
 
82
83
  # Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
@@ -277,7 +278,7 @@ module ActiveResource
277
278
  def legitimize_auth_type(auth_type)
278
279
  return :basic if auth_type.nil?
279
280
  auth_type = auth_type.to_sym
280
- [:basic, :digest].include?(auth_type) ? auth_type : :basic
281
+ auth_type.in?([:basic, :digest]) ? auth_type : :basic
281
282
  end
282
283
  end
283
284
  end
@@ -54,7 +54,7 @@ module ActiveResource
54
54
  #
55
55
  # Person.find(:all, :from => :active)
56
56
  def get(custom_method_name, options = {})
57
- connection.get(custom_method_collection_url(custom_method_name, options), headers)
57
+ format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body)
58
58
  end
59
59
 
60
60
  def post(custom_method_name, options = {}, body = '')
@@ -85,7 +85,7 @@ module ActiveResource
85
85
 
86
86
  module InstanceMethods
87
87
  def get(method_name, options = {})
88
- connection.get(custom_method_element_url(method_name, options), self.class.headers)
88
+ self.class.format.decode(connection.get(custom_method_element_url(method_name, options), self.class.headers).body)
89
89
  end
90
90
 
91
91
  def post(method_name, options = {}, body = nil)
@@ -36,6 +36,9 @@ module ActiveResource
36
36
  def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
37
37
  end
38
38
 
39
+ # Raised when ...
40
+ class MissingPrefixParam < ArgumentError; end # :nodoc:
41
+
39
42
  # 4xx Client Error
40
43
  class ClientError < ConnectionError; end # :nodoc:
41
44
 
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/kernel/reporting'
2
+ require 'active_support/core_ext/object/inclusion'
2
3
 
3
4
  module ActiveResource
4
5
  class InvalidRequestError < StandardError; end #:nodoc:
@@ -8,8 +9,8 @@ module ActiveResource
8
9
  # requests.
9
10
  #
10
11
  # To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
11
- # method with an attached block. The block declares a set of URIs with expected input, and the output
12
- # each request should return. The passed in block has any number of entries in the following generalized
12
+ # method with an attached block. The block declares a set of URIs with expected input, and the output
13
+ # each request should return. The passed in block has any number of entries in the following generalized
13
14
  # format:
14
15
  #
15
16
  # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
@@ -28,7 +29,7 @@ module ActiveResource
28
29
  # <tt>request_headers</tt> listed above.
29
30
  #
30
31
  # In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
31
- # +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
32
+ # +path+ and <tt>request_headers</tt>. If no match is found an +InvalidRequestError+ exception
32
33
  # will be raised showing you what request it could not find a response for and also what requests and response
33
34
  # pairs have been recorded so you can create a new mock for that request.
34
35
  #
@@ -60,15 +61,26 @@ module ActiveResource
60
61
  # end
61
62
  module_eval <<-EOE, __FILE__, __LINE__ + 1
62
63
  def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
63
- @responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
64
+ request = Request.new(:#{method}, path, nil, request_headers)
65
+ response = Response.new(body || "", status, response_headers)
66
+
67
+ delete_duplicate_responses(request)
68
+
69
+ @responses << [request, response]
64
70
  end
65
71
  EOE
66
72
  end
73
+
74
+ private
75
+
76
+ def delete_duplicate_responses(request)
77
+ @responses.delete_if {|r| r[0] == request }
78
+ end
67
79
  end
68
80
 
69
81
  class << self
70
82
 
71
- # Returns an array of all request objects that have been sent to the mock. You can use this to check
83
+ # Returns an array of all request objects that have been sent to the mock. You can use this to check
72
84
  # if your model actually sent an HTTP request.
73
85
  #
74
86
  # ==== Example
@@ -93,18 +105,18 @@ module ActiveResource
93
105
  end
94
106
 
95
107
  # Returns the list of requests and their mocked responses. Look up a
96
- # response for a request using responses.assoc(request).
108
+ # response for a request using <tt>responses.assoc(request)</tt>.
97
109
  def responses
98
110
  @@responses ||= []
99
111
  end
100
112
 
101
113
  # Accepts a block which declares a set of requests and responses for the HttpMock to respond to in
102
114
  # the following format:
103
- #
115
+ #
104
116
  # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
105
- #
117
+ #
106
118
  # === Example
107
- #
119
+ #
108
120
  # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
109
121
  # ActiveResource::HttpMock.respond_to do |mock|
110
122
  # mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
@@ -112,73 +124,91 @@ module ActiveResource
112
124
  # mock.put "/people/1.xml", {}, nil, 204
113
125
  # mock.delete "/people/1.xml", {}, nil, 200
114
126
  # end
115
- #
127
+ #
116
128
  # Alternatively, accepts a hash of <tt>{Request => Response}</tt> pairs allowing you to generate
117
129
  # these the following format:
118
- #
130
+ #
119
131
  # ActiveResource::Request.new(method, path, body, request_headers)
120
132
  # ActiveResource::Response.new(body, status, response_headers)
121
- #
133
+ #
122
134
  # === Example
123
- #
135
+ #
124
136
  # Request.new(:#{method}, path, nil, request_headers)
125
- #
137
+ #
126
138
  # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
127
139
  #
128
140
  # create_matz = ActiveResource::Request.new(:post, '/people.xml', @matz, {})
129
141
  # created_response = ActiveResource::Response.new("", 201, {"Location" => "/people/1.xml"})
130
142
  # get_matz = ActiveResource::Request.new(:get, '/people/1.xml', nil)
131
143
  # ok_response = ActiveResource::Response.new("", 200, {})
132
- #
144
+ #
133
145
  # pairs = {create_matz => created_response, get_matz => ok_response}
134
- #
146
+ #
135
147
  # ActiveResource::HttpMock.respond_to(pairs)
136
148
  #
137
149
  # Note, by default, every time you call +respond_to+, any previous request and response pairs stored
138
150
  # in HttpMock will be deleted giving you a clean slate to work on.
139
- #
151
+ #
140
152
  # If you want to override this behaviour, pass in +false+ as the last argument to +respond_to+
141
- #
153
+ #
142
154
  # === Example
143
- #
155
+ #
144
156
  # ActiveResource::HttpMock.respond_to do |mock|
145
157
  # mock.send(:get, "/people/1", {}, "XML1")
146
158
  # end
147
159
  # ActiveResource::HttpMock.responses.length #=> 1
148
- #
160
+ #
149
161
  # ActiveResource::HttpMock.respond_to(false) do |mock|
150
162
  # mock.send(:get, "/people/2", {}, "XML2")
151
163
  # end
152
164
  # ActiveResource::HttpMock.responses.length #=> 2
153
- #
165
+ #
154
166
  # This also works with passing in generated pairs of requests and responses, again, just pass in false
155
167
  # as the last argument:
156
- #
168
+ #
157
169
  # === Example
158
- #
170
+ #
159
171
  # ActiveResource::HttpMock.respond_to do |mock|
160
172
  # mock.send(:get, "/people/1", {}, "XML1")
161
173
  # end
162
174
  # ActiveResource::HttpMock.responses.length #=> 1
163
- #
175
+ #
164
176
  # get_matz = ActiveResource::Request.new(:get, '/people/1.xml', nil)
165
177
  # ok_response = ActiveResource::Response.new("", 200, {})
166
- #
178
+ #
167
179
  # pairs = {get_matz => ok_response}
168
180
  #
169
181
  # ActiveResource::HttpMock.respond_to(pairs, false)
170
182
  # ActiveResource::HttpMock.responses.length #=> 2
183
+ #
184
+ # # If you add a response with an existing request, it will be replaced
185
+ #
186
+ # fail_response = ActiveResource::Response.new("", 404, {})
187
+ # pairs = {get_matz => fail_response}
188
+ #
189
+ # ActiveResource::HttpMock.respond_to(pairs, false)
190
+ # ActiveResource::HttpMock.responses.length #=> 2
191
+ #
171
192
  def respond_to(*args) #:yields: mock
172
193
  pairs = args.first || {}
173
194
  reset! if args.last.class != FalseClass
174
- responses.concat pairs.to_a
195
+
175
196
  if block_given?
176
197
  yield Responder.new(responses)
177
198
  else
199
+ delete_responses_to_replace pairs.to_a
200
+ responses.concat pairs.to_a
178
201
  Responder.new(responses)
179
202
  end
180
203
  end
181
204
 
205
+ def delete_responses_to_replace(new_responses)
206
+ new_responses.each{|nr|
207
+ request_to_remove = nr[0]
208
+ @@responses = responses.delete_if{|r| r[0] == request_to_remove}
209
+ }
210
+ end
211
+
182
212
  # Deletes all logged requests and responses.
183
213
  def reset!
184
214
  requests.clear
@@ -269,8 +299,10 @@ module ActiveResource
269
299
  end
270
300
  end
271
301
 
302
+ # Returns true if code is 2xx,
303
+ # false otherwise.
272
304
  def success?
273
- (200..299).include?(code)
305
+ code.in?(200..299)
274
306
  end
275
307
 
276
308
  def [](key)
@@ -281,6 +313,8 @@ module ActiveResource
281
313
  headers[key] = value
282
314
  end
283
315
 
316
+ # Returns true if the other is a Response with an equal body, equal message
317
+ # and equal headers. Otherwise it returns false.
284
318
  def ==(other)
285
319
  if (other.is_a?(Response))
286
320
  other.body == body && other.message == message && other.headers == headers
@@ -5,6 +5,14 @@ module ActiveResource
5
5
 
6
6
  included do
7
7
  %w( create save update destroy ).each do |method|
8
+ # def create_with_notifications(*args, &block)
9
+ # notify_observers(:before_create)
10
+ # if result = create_without_notifications(*args, &block)
11
+ # notify_observers(:after_create)
12
+ # end
13
+ # result
14
+ # end
15
+ # alias_method_chain(create, :notifications)
8
16
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
9
17
  def #{method}_with_notifications(*args, &block)
10
18
  notify_observers(:before_#{method})
@@ -4,7 +4,7 @@ module ActiveResource # :nodoc:
4
4
  class Schema # :nodoc:
5
5
  # attributes can be known to be one of these types. They are easy to
6
6
  # cast to/from.
7
- KNOWN_ATTRIBUTE_TYPES = %w( string integer float )
7
+ KNOWN_ATTRIBUTE_TYPES = %w( string text integer float decimal datetime timestamp time date binary boolean )
8
8
 
9
9
  # An array of attribute definitions, representing the attributes that
10
10
  # have been defined.
@@ -39,8 +39,6 @@ module ActiveResource # :nodoc:
39
39
 
40
40
  # The following are the attribute types supported by Active Resource
41
41
  # migrations.
42
- # TODO: We should eventually support all of these:
43
- # %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |attr_type|
44
42
  KNOWN_ATTRIBUTE_TYPES.each do |attr_type|
45
43
  class_eval <<-EOV, __FILE__, __LINE__ + 1
46
44
  def #{attr_type.to_s}(*args)
@@ -8,12 +8,12 @@ module ActiveResource
8
8
  # Active Resource validation is reported to and from this object, which is used by Base#save
9
9
  # to determine whether the object in a valid state to be saved. See usage example in Validations.
10
10
  class Errors < ActiveModel::Errors
11
- # Grabs errors from an array of messages (like ActiveRecord::Validations)
11
+ # Grabs errors from an array of messages (like ActiveRecord::Validations).
12
12
  # The second parameter directs the errors cache to be cleared (default)
13
- # or not (by passing true)
13
+ # or not (by passing true).
14
14
  def from_array(messages, save_cache = false)
15
15
  clear unless save_cache
16
- humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
16
+ humanized_attributes = Hash[@base.attributes.keys.map { |attr_name| [attr_name.humanize, attr_name] }]
17
17
  messages.each do |message|
18
18
  attr_message = humanized_attributes.keys.detect do |attr_name|
19
19
  if message[0, attr_name.size + 1] == "#{attr_name} "
@@ -68,20 +68,12 @@ module ActiveResource
68
68
 
69
69
  # Validate a resource and save (POST) it to the remote web service.
70
70
  # If any local validations fail - the save (POST) will not be attempted.
71
- def save_with_validation(options=nil)
72
- perform_validation = case options
73
- when Hash
74
- options[:validate] != false
75
- when NilClass
76
- true
77
- else
78
- ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
79
- options
80
- end
71
+ def save_with_validation(options={})
72
+ perform_validation = options[:validate] != false
81
73
 
82
74
  # clear the remote validations so they don't interfere with the local
83
75
  # ones. Otherwise we get an endless loop and can never change the
84
- # fields so as to make the resource valid
76
+ # fields so as to make the resource valid.
85
77
  @remote_errors = nil
86
78
  if perform_validation && valid? || !perform_validation
87
79
  save_without_validation
@@ -92,7 +84,7 @@ module ActiveResource
92
84
  rescue ResourceInvalid => error
93
85
  # cache the remote errors because every call to <tt>valid?</tt> clears
94
86
  # all errors. We must keep a copy to add these back after local
95
- # validations
87
+ # validations.
96
88
  @remote_errors = error
97
89
  load_remote_errors(@remote_errors, true)
98
90
  false
@@ -100,7 +92,7 @@ module ActiveResource
100
92
 
101
93
 
102
94
  # Loads the set of remote errors into the object's Errors based on the
103
- # content-type of the error-block received
95
+ # content-type of the error-block received.
104
96
  def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
105
97
  case self.class.format
106
98
  when ActiveResource::Formats[:xml]
@@ -1,9 +1,9 @@
1
1
  module ActiveResource
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 3
4
- MINOR = 0
5
- TINY = 20
6
- PRE = nil
4
+ MINOR = 1
5
+ TINY = 0
6
+ PRE = "beta1"
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
9
9
  end
metadata CHANGED
@@ -1,53 +1,52 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: activeresource
3
- version: !ruby/object:Gem::Version
4
- version: 3.0.20
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: 6
5
+ version: 3.1.0.beta1
5
6
  platform: ruby
6
- authors:
7
+ authors:
7
8
  - David Heinemeier Hansson
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-01-28 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
12
+
13
+ date: 2011-05-04 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
14
17
  name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '='
18
- - !ruby/object:Gem::Version
19
- version: 3.0.20
20
- type: :runtime
21
18
  prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - '='
25
- - !ruby/object:Gem::Version
26
- version: 3.0.20
27
- - !ruby/object:Gem::Dependency
28
- name: activemodel
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '='
32
- - !ruby/object:Gem::Version
33
- version: 3.0.20
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 3.1.0.beta1
34
25
  type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: activemodel
35
29
  prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '='
39
- - !ruby/object:Gem::Version
40
- version: 3.0.20
41
- description: REST on Rails. Wrap your RESTful web app with Ruby classes and work with
42
- them like Active Record models.
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - "="
34
+ - !ruby/object:Gem::Version
35
+ version: 3.1.0.beta1
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ description: REST on Rails. Wrap your RESTful web app with Ruby classes and work with them like Active Record models.
43
39
  email: david@loudthinking.com
44
40
  executables: []
41
+
45
42
  extensions: []
46
- extra_rdoc_files:
43
+
44
+ extra_rdoc_files:
47
45
  - README.rdoc
48
- files:
46
+ files:
49
47
  - CHANGELOG
50
48
  - README.rdoc
49
+ - examples/performance.rb
51
50
  - examples/simple.rb
52
51
  - lib/active_resource/base.rb
53
52
  - lib/active_resource/connection.rb
@@ -64,29 +63,34 @@ files:
64
63
  - lib/active_resource/validations.rb
65
64
  - lib/active_resource/version.rb
66
65
  - lib/active_resource.rb
66
+ has_rdoc: true
67
67
  homepage: http://www.rubyonrails.org
68
68
  licenses: []
69
- metadata: {}
69
+
70
70
  post_install_message:
71
- rdoc_options:
72
- - "--main"
71
+ rdoc_options:
72
+ - --main
73
73
  - README.rdoc
74
- require_paths:
74
+ require_paths:
75
75
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
77
- requirements:
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
78
79
  - - ">="
79
- - !ruby/object:Gem::Version
80
+ - !ruby/object:Gem::Version
80
81
  version: 1.8.7
81
- required_rubygems_version: !ruby/object:Gem::Requirement
82
- requirements:
83
- - - ">="
84
- - !ruby/object:Gem::Version
85
- version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">"
86
+ - !ruby/object:Gem::Version
87
+ version: 1.3.1
86
88
  requirements: []
89
+
87
90
  rubyforge_project: activeresource
88
- rubygems_version: 2.0.0.preview3.1
91
+ rubygems_version: 1.6.2
89
92
  signing_key:
90
- specification_version: 4
93
+ specification_version: 3
91
94
  summary: REST modeling framework (part of Rails).
92
95
  test_files: []
96
+
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 12c4fd12a01df590f853606ab4b5f6e5661e8d8f
4
- data.tar.gz: 5ee8a3bfb4aaf107fe9a7341dce4a39badca74e5
5
- SHA512:
6
- metadata.gz: 7e7719bc289dcce220968d1f44d89c22046df8b1934dbd484c6334b4c21eb336a6eec629b62e5dd8f08d7c7f3d032366a300a854ca3613f95908066b013a9a80
7
- data.tar.gz: 1cc60aa0ab2e1629d4853b734c1dd8fabf7ff34b88523f6d7e43756cf8ed054d17144fe37cd8d46f84e7ad1e54909e76ba5db449d85c1e99b5a8d32f374d567f