api_resource 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/VERSION +1 -1
  2. data/api_resource.gemspec +4 -74
  3. data/coverage/assets/0.5.3/app.js +88 -0
  4. data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
  5. data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
  6. data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
  7. data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
  8. data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
  9. data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
  10. data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
  11. data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
  12. data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
  13. data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
  14. data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
  15. data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
  16. data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
  17. data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
  18. data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
  19. data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
  20. data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
  21. data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
  22. data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
  23. data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
  24. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
  25. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
  26. data/coverage/assets/0.5.3/favicon_green.png +0 -0
  27. data/coverage/assets/0.5.3/favicon_red.png +0 -0
  28. data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
  29. data/coverage/assets/0.5.3/highlight.css +129 -0
  30. data/coverage/assets/0.5.3/highlight.pack.js +1 -0
  31. data/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
  32. data/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
  33. data/coverage/assets/0.5.3/jquery.timeago.js +141 -0
  34. data/coverage/assets/0.5.3/jquery.url.js +174 -0
  35. data/coverage/assets/0.5.3/loading.gif +0 -0
  36. data/coverage/assets/0.5.3/magnify.png +0 -0
  37. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  38. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  39. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  40. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  41. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  42. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  43. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  44. data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  45. data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
  46. data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  47. data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
  48. data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
  49. data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  50. data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
  51. data/coverage/assets/0.5.3/stylesheet.css +383 -0
  52. data/coverage/index.html +3573 -0
  53. data/lib/api_resource/associations/abstract_scope.rb +191 -0
  54. data/lib/api_resource/associations/association_scope.rb +47 -0
  55. data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +5 -6
  56. data/lib/api_resource/associations/has_many_remote_object_proxy.rb +5 -8
  57. data/lib/api_resource/associations/has_one_remote_object_proxy.rb +12 -13
  58. data/lib/api_resource/associations/multi_object_proxy.rb +65 -39
  59. data/lib/api_resource/associations/resource_scope.rb +6 -17
  60. data/lib/api_resource/associations/scope.rb +23 -121
  61. data/lib/api_resource/associations/single_object_proxy.rb +41 -50
  62. data/lib/api_resource/associations.rb +32 -11
  63. data/lib/api_resource/attributes.rb +108 -69
  64. data/lib/api_resource/base.rb +114 -106
  65. data/lib/api_resource/local.rb +1 -1
  66. data/lib/api_resource/model_errors.rb +9 -6
  67. data/lib/api_resource/scopes.rb +53 -16
  68. data/lib/api_resource.rb +3 -1
  69. data/spec/lib/api_resource_spec.rb +3 -7
  70. data/spec/lib/associations/association_scope_spec.rb +19 -0
  71. data/spec/lib/associations_spec.rb +251 -162
  72. data/spec/lib/attributes_spec.rb +33 -15
  73. data/spec/lib/base_spec.rb +302 -64
  74. data/spec/lib/callbacks_spec.rb +4 -2
  75. data/spec/lib/local_spec.rb +5 -1
  76. data/spec/spec_helper.rb +2 -3
  77. data/spec/support/mocks/association_mocks.rb +9 -1
  78. data/spec/support/requests/association_requests.rb +5 -5
  79. data/spec/support/requests/test_resource_requests.rb +16 -4
  80. data/spec/tmp/api_resource_test_db.sqlite +0 -0
  81. metadata +68 -22
  82. data/.document +0 -5
  83. data/.rspec +0 -5
  84. data/.travis.yml +0 -4
  85. data/lib/api_resource/associations/association_proxy.rb +0 -121
  86. data/lib/api_resource/associations/dynamic_resource_scope.rb +0 -23
  87. data/lib/api_resource/associations/generic_scope.rb +0 -68
  88. data/lib/api_resource/associations/multi_argument_resource_scope.rb +0 -15
  89. data/lib/api_resource/associations/relation_scope.rb +0 -25
@@ -25,11 +25,15 @@ module ApiResource
25
25
 
26
26
  class_attribute :primary_key
27
27
  self.primary_key = "id"
28
+
29
+ delegate :logger, :to => ApiResource
28
30
 
29
31
  class << self
30
32
 
31
33
  # writers - accessors with defaults were not working
32
34
  attr_writer :element_name, :collection_name
35
+
36
+ delegate :logger, :to => ApiResource
33
37
 
34
38
  def inherited(klass)
35
39
  # Call the methods of the superclass to make sure inheritable accessors and the like have been inherited
@@ -44,33 +48,38 @@ module ApiResource
44
48
  EOE
45
49
  true
46
50
  end
51
+
52
+ def resource_definition
53
+ @resource_definition
54
+ end
55
+
47
56
  # This makes a request to new_element_path
48
57
  def set_class_attributes_upon_load
49
58
  return true if self == ApiResource::Base
50
59
  begin
51
- class_data = self.connection.get(
60
+ @resource_definition = self.connection.get(
52
61
  self.new_element_path, self.headers
53
62
  )
54
63
  # Attributes go first
55
- if class_data["attributes"]
64
+ if resource_definition["attributes"]
56
65
 
57
66
  define_attributes(
58
- *(class_data["attributes"]["public"] || [])
67
+ *(resource_definition["attributes"]["public"] || [])
59
68
  )
60
69
  define_protected_attributes(
61
- *(class_data["attributes"]["protected"] || [])
70
+ *(resource_definition["attributes"]["protected"] || [])
62
71
  )
63
72
 
64
73
  end
65
74
  # Then scopes
66
- if class_data["scopes"]
67
- class_data["scopes"].each_pair do |scope_name, opts|
75
+ if resource_definition["scopes"]
76
+ resource_definition["scopes"].each_pair do |scope_name, opts|
68
77
  self.scope(scope_name, opts)
69
78
  end
70
79
  end
71
80
  # Then associations
72
- if class_data["associations"]
73
- class_data["associations"].each_pair do |key, hash|
81
+ if resource_definition["associations"]
82
+ resource_definition["associations"].each_pair do |key, hash|
74
83
  hash.each_pair do |assoc_name, assoc_options|
75
84
  self.send(key, assoc_name, assoc_options)
76
85
  end
@@ -81,15 +90,14 @@ module ApiResource
81
90
  # define the basic methods but we need to override all the setters
82
91
  # so we do dirty tracking
83
92
  attrs = []
84
- if class_data["attributes"] && class_data["attributes"]["public"]
85
- attrs += class_data["attributes"]["public"].collect{|v|
93
+ if resource_definition["attributes"] && resource_definition["attributes"]["public"]
94
+ attrs += resource_definition["attributes"]["public"].collect{|v|
86
95
  v.is_a?(Array) ? v.first : v
87
96
  }.flatten
88
97
  end
89
- if class_data["associations"]
90
- attrs += class_data["associations"].values.collect(&:keys).flatten
98
+ if resource_definition["associations"]
99
+ attrs += resource_definition["associations"].values.collect(&:keys).flatten
91
100
  end
92
- define_attribute_methods(attrs)
93
101
 
94
102
  # Swallow up any loading errors because the site may be incorrect
95
103
  rescue Exception => e
@@ -114,29 +122,32 @@ module ApiResource
114
122
  # load our resource definition to make sure we know what this class
115
123
  # responds to
116
124
  def respond_to?(*args)
117
- self.load_class_data
125
+ self.load_resource_definition
118
126
  super
119
127
  end
120
128
 
121
- def load_class_data
122
- unless instance_variable_defined?(:@class_data)
123
- @class_data = true
129
+ def load_resource_definition
130
+ unless instance_variable_defined?(:@resource_definition)
131
+ # set to not nil so we don't get an infinite loop
132
+ @resource_definition = true
124
133
  self.set_class_attributes_upon_load
134
+ return true
125
135
  end
126
- true
136
+ # we didn't do anything
137
+ false
127
138
  end
128
139
 
129
- def reload_class_data
140
+ def reload_resource_definition
130
141
  # clear the public_attribute_names, protected_attribute_names
131
- if instance_variable_defined?(:@class_data)
132
- remove_instance_variable(:@class_data)
142
+ if instance_variable_defined?(:@resource_definition)
143
+ remove_instance_variable(:@resource_definition)
133
144
  self.clear_attributes
134
145
  self.clear_related_objects
135
146
  end
136
- self.load_class_data
147
+ self.load_resource_definition
137
148
  end
138
149
  # backwards compatibility
139
- alias_method :reload_class_attributes, :reload_class_data
150
+ alias_method :reload_class_attributes, :reload_resource_definition
140
151
 
141
152
  def token_with_new_token_set=(new_token)
142
153
  self.token_without_new_token_set = new_token
@@ -163,7 +174,7 @@ module ApiResource
163
174
 
164
175
  # reset class attributes and try to reload them if the site changed
165
176
  unless self.site.to_s == old_site
166
- self.reload_class_data
177
+ self.reload_resource_definition
167
178
  end
168
179
 
169
180
  return site
@@ -219,12 +230,17 @@ module ApiResource
219
230
  end
220
231
 
221
232
  def prefix=(value = '/')
222
- prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.escape options[#{key}].to_s}"}
233
+ prefix_call = value.gsub(/:\w+/) { |key|
234
+ "\#{URI.escape options[#{key}].to_s}"
235
+ }
223
236
  @prefix_parameters = nil
224
237
  silence_warnings do
225
238
  instance_eval <<-EOE, __FILE__, __LINE__ + 1
226
239
  def prefix_source() "#{value}" end
227
- def prefix(options={}) "#{prefix_call}" end
240
+ def prefix(options={})
241
+ ret = "#{prefix_call}"
242
+ ret =~ Regexp.new(Regexp.escape("//")) ? "/" : ret
243
+ end
228
244
  EOE
229
245
  end
230
246
  rescue Exception => e
@@ -258,22 +274,20 @@ module ApiResource
258
274
  end
259
275
  end
260
276
 
261
- # TODO: Add back in support for non-dynamic prefix paths (e.g. /subdir/resources/new.json)
262
- def new_element_path
263
- "/#{collection_name}/new.#{format.extension}"
277
+ # path to find
278
+ def new_element_path(prefix_options = {})
279
+ File.join(
280
+ self.prefix(prefix_options),
281
+ self.collection_name,
282
+ "new.#{format.extension}"
283
+ )
264
284
  end
265
285
 
266
286
  def collection_path(prefix_options = {}, query_options = nil)
267
287
  prefix_options, query_options = split_options(prefix_options) if query_options.nil?
268
288
 
269
- # If we have a prefix, we need a foreign key id
270
- # This regex detects '//', which means no foreign key id is present.
271
- if prefix(prefix_options) =~ /\/\/$/
272
- "/#{collection_name}.#{format.extension}#{query_string(query_options)}"
273
- else
274
- # Fall back on this rather than search without the id
275
- "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
276
- end
289
+ # Fall back on this rather than search without the id
290
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
277
291
  end
278
292
 
279
293
  def build(attributes = {})
@@ -289,6 +303,10 @@ module ApiResource
289
303
  # where options can be standard rails options or :expires_in.
290
304
  # If :expires_in is set, it caches it for expires_in seconds.
291
305
  def find(*arguments)
306
+
307
+ # make sure we have class data before loading
308
+ self.load_resource_definition
309
+
292
310
  scope = arguments.slice!(0)
293
311
  options = arguments.slice!(0) || {}
294
312
 
@@ -343,13 +361,29 @@ module ApiResource
343
361
  def delete(id, options = {})
344
362
  connection.delete(element_path(id, options))
345
363
  end
346
-
364
+
365
+ def instantiate_collection(collection)
366
+ collection.collect{|record|
367
+ instantiate_record(record)
368
+ }
369
+ end
370
+
371
+ def instantiate_record(record)
372
+ self.load_resource_definition
373
+ ret = self.allocate
374
+ ret.instance_variable_set(
375
+ :@attributes, record.with_indifferent_access
376
+ )
377
+ ret.instance_variable_set(
378
+ :@attributes_cache, HashWithIndifferentAccess.new
379
+ )
380
+ ret
381
+ end
382
+
347
383
  protected
348
384
  def method_missing(meth, *args, &block)
349
385
  # make one attempt to load remote attrs
350
- unless self.instance_variable_defined?(:@class_data)
351
- self.set_class_attributes_upon_load
352
- self.instance_variable_set(:@class_data, true)
386
+ if self.load_resource_definition
353
387
  return self.send(meth, *args, &block)
354
388
  end
355
389
  super
@@ -395,15 +429,6 @@ module ApiResource
395
429
  instantiate_record(connection.get(path, headers))
396
430
  end
397
431
 
398
- def instantiate_collection(collection)
399
- collection.collect! { |record| instantiate_record(record) }
400
- end
401
-
402
- def instantiate_record(record)
403
- new(record)
404
- end
405
-
406
-
407
432
  # Accepts a URI and creates the site URI from that.
408
433
  def create_site_uri_from(site)
409
434
  site.is_a?(URI) ? site.dup : uri_parser.parse(site)
@@ -443,10 +468,13 @@ module ApiResource
443
468
  end
444
469
 
445
470
  def initialize(attributes = {})
471
+ # call super's initialize to set up any variables that we need
472
+ super(attributes)
446
473
  # if we initialize this class, load the attributes
447
- self.class.load_class_data
448
- # Now we can make a call to setup the inheriting klass with its attributes
449
- load(attributes)
474
+ self.class.load_resource_definition
475
+ # Now we can make a call to setup the inheriting
476
+ # klass with its attributes
477
+ self.attributes = attributes
450
478
  end
451
479
 
452
480
  def new?
@@ -459,12 +487,12 @@ module ApiResource
459
487
  end
460
488
 
461
489
  def id
462
- self.attributes[self.class.primary_key]
490
+ self.read_attribute(self.class.primary_key)
463
491
  end
464
492
 
465
493
  # Bypass dirty tracking for this field
466
494
  def id=(id)
467
- attributes[self.class.primary_key] = id
495
+ @attributes[self.class.primary_key] = id
468
496
  end
469
497
 
470
498
  def ==(other)
@@ -480,9 +508,7 @@ module ApiResource
480
508
  end
481
509
 
482
510
  def dup
483
- self.class.new.tap do |resource|
484
- resource.attributes = self.attributes
485
- end
511
+ self.class.instantiate_record(self.attributes)
486
512
  end
487
513
 
488
514
  def update_attributes(attrs)
@@ -507,8 +533,15 @@ module ApiResource
507
533
  end
508
534
 
509
535
  def reload
510
- remove_instance_variable(:@assoc_attributes) if instance_variable_defined?(:@assoc_attributes)
511
- self.load(self.class.find(to_param, :params => @prefix_options).attributes_without_proxies)
536
+ # find the record from the remote service
537
+ reloaded = self.class.find(self.id)
538
+
539
+ # clear out the attributes cache
540
+ @attributes_cache = HashWithIndifferentAccess.new
541
+ # set up our attributes cache on our record
542
+ @attributes = reloaded.instance_variable_get(:@attributes)
543
+
544
+ reloaded
512
545
  end
513
546
 
514
547
  def to_param
@@ -531,56 +564,13 @@ module ApiResource
531
564
  self.class.prefix_source.scan(/\:(\w+)/).collect{|match| match.first.to_sym}
532
565
  end
533
566
 
534
- def load(attributes)
535
- return if attributes.nil?
536
- raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
537
-
538
- attributes.symbolize_keys.each do |key, value|
539
- # If this attribute doesn't exist define it as a protected attribute
540
- self.class.define_protected_attributes(key) unless self.respond_to?(key)
541
- #self.send("#{key}_will_change!") if self.respond_to?("#{key}_will_change!")
542
- self.attributes[key] =
543
- case value
544
- when Array
545
- if self.has_many?(key)
546
- MultiObjectProxy.new(self.has_many_class_name(key), value)
547
- elsif self.association?(key)
548
- raise ArgumentError, "Expected a hash value or nil, got: #{value.inspect}"
549
- else
550
- typecast_attribute(key, value)
551
- end
552
- when Hash
553
- if self.has_many?(key)
554
- MultiObjectProxy.new(self.has_many_class_name(key), value)
555
- elsif self.association?(key)
556
- #binding.pry
557
- SingleObjectProxy.new(self.association_class_name(key), value)
558
- else
559
- typecast_attribute(key, value)
560
- end
561
- when NilClass
562
- # If it's nil and an association then create a blank object
563
- if self.has_many?(key)
564
- return MultiObjectProxy.new(self.has_many_class_name(key), [])
565
- elsif self.association?(key)
566
- SingleObjectProxy.new(self.association_class_name(key), value)
567
- end
568
- else
569
- raise ArgumentError, "expected an array or a hash for the association #{key}, got: #{value.inspect}" if self.association?(key)
570
- typecast_attribute(key, value)
571
- end
572
- end
573
- return self
574
- end
575
-
576
567
  # Override to_s and inspect so they only show attributes
577
568
  # and not associations, this prevents force loading of associations
578
569
  # when we call to_s or inspect on a descendent of base but allows it if we
579
570
  # try to evaluate an association directly
580
571
  def to_s
581
- return "#<#{self.class}:#{(self.object_id * 2).to_s(16)} @attributes=#{self.attributes.inject({}){|accum,(k,v)| self.association?(k) ? accum : accum.merge(k => v)}}"
572
+ return "#<#{self.class}:#{(self.object_id * 2).to_s(16)} @attributes=#{self.attributes}"
582
573
  end
583
-
584
574
  alias_method :inspect, :to_s
585
575
 
586
576
  # Methods for serialization as json or xml, relying on the serializable_hash method
@@ -593,11 +583,17 @@ module ApiResource
593
583
  end
594
584
 
595
585
  def serializable_hash(options = {})
586
+
596
587
  action = options[:action]
588
+
597
589
  include_nil_attributes = options[:include_nil_attributes]
590
+
598
591
  options[:include_associations] = options[:include_associations] ? options[:include_associations].symbolize_array : self.changes.keys.symbolize_array.select{|k| self.association?(k)}
592
+
599
593
  options[:include_extras] = options[:include_extras] ? options[:include_extras].symbolize_array : []
594
+
600
595
  options[:except] ||= []
596
+
601
597
  ret = self.attributes.inject({}) do |accum, (key,val)|
602
598
  # If this is an association and it's in include_associations then include it
603
599
  if options[:include_extras].include?(key.to_sym)
@@ -614,7 +610,13 @@ module ApiResource
614
610
  end
615
611
  end
616
612
  options[:include_associations].each do |assoc|
617
- ret[assoc] = self.send(assoc).serializable_hash({:include_id => true, :include_nil_attributes => include_nil_attributes, :action => action}) if self.association?(assoc)
613
+ if self.association?(assoc)
614
+ ret[assoc] = self.send(assoc).serializable_hash({
615
+ :include_id => true,
616
+ :include_nil_attributes => include_nil_attributes,
617
+ :action => action
618
+ })
619
+ end
618
620
  end
619
621
  # include id - this is for nested updates
620
622
  ret[:id] = self.id if options[:include_id] && !self.new?
@@ -627,7 +629,13 @@ module ApiResource
627
629
  end
628
630
 
629
631
  def load_attributes_from_response(response)
630
- load(response)
632
+ if response.present?
633
+ @attributes_cache = {}
634
+ @attributes = @attributes.merge(
635
+ response.with_indifferent_access
636
+ )
637
+ end
638
+ response
631
639
  end
632
640
 
633
641
  def element_path(id, prefix_override_options = {}, query_options = nil)
@@ -5,7 +5,7 @@ module ApiResource
5
5
  true
6
6
  end
7
7
  # shouldn't do anything here either -
8
- def self.reload_class_attributes
8
+ def self.reload_resource_definition
9
9
  true
10
10
  end
11
11
  end
@@ -4,11 +4,16 @@ module ApiResource
4
4
 
5
5
  def from_array(messages, save_cache = false)
6
6
  clear unless save_cache
7
- humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
7
+ humanized_attributes = @base.attributes.keys.inject({}){|h, attr_name|
8
+ h.update(attr_name.to_s.humanize => attr_name)
9
+ }
8
10
  messages.each do |message|
9
11
  attr_message = humanized_attributes.keys.detect do |attr_name|
10
12
  if message[0,attr_name.size + 1] == "#{attr_name} "
11
- add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
13
+ self.add(
14
+ humanized_attributes[attr_name],
15
+ message[(attr_name.size + 1)..-1]
16
+ )
12
17
  end
13
18
  end
14
19
  end
@@ -18,7 +23,7 @@ module ApiResource
18
23
  clear unless save_cache
19
24
  messages.each do |attr, message_array|
20
25
  message_array.each do |message|
21
- add attr, message
26
+ self.add(attr, message)
22
27
  end
23
28
  end
24
29
  end
@@ -61,10 +66,8 @@ module ApiResource
61
66
  elsif error_data.is_a?(Array)
62
67
  self.errors.from_array(error_data)
63
68
  else
64
- raise Exception.new
69
+ raise "Invalid response for invalid object: expected an array or hash got #{remote_errors}"
65
70
  end
66
- rescue Exception
67
- raise "Invalid response for invalid object: expected an array or hash got #{remote_errors}"
68
71
  end
69
72
 
70
73
  # This method runs any local validations but not remote ones
@@ -1,35 +1,72 @@
1
1
  module ApiResource
2
2
  module Scopes
3
+
3
4
  extend ActiveSupport::Concern
4
5
 
5
-
6
6
  module ClassMethods
7
7
  def scopes
8
8
  return self.related_objects[:scopes]
9
9
  end
10
10
 
11
- # Called by base.rb
12
- # @param name is the name of the scope from the json
13
- # @param hsh is always a hash with the arguments for the scope
14
- def scope(name, hsh)
15
- raise ArgumentError, "Expecting an attributes hash given #{hsh.inspect}" unless hsh.is_a?(Hash)
16
- self.related_objects[:scopes][name.to_sym] = hsh
17
- # we also need to define a class method for each scope
18
- self.instance_eval <<-EOE, __FILE__, __LINE__ + 1
19
- def #{name}(*args)
20
- return #{ApiResource::Associations::ResourceScope.class_factory(hsh)}.new(self, :#{name}, *args)
21
- end
22
- EOE
23
- end
24
-
25
11
  def scope?(name)
26
- self.related_objects[:scopes][name.to_sym].present?
12
+ self.related_objects[:scopes].has_key?(name.to_sym)
27
13
  end
28
14
 
29
15
  def scope_attributes(name)
30
16
  raise "No such scope #{name}" unless self.scope?(name)
31
17
  self.related_objects[:scopes][name.to_sym]
32
18
  end
19
+
20
+ # Called by base.rb
21
+ # @param scope_name is the scope_name of the scope from the json
22
+ # e.g. paged
23
+ #
24
+ # @param scope_definition is always a hash with the arguments for the scope
25
+ # e.g. {:page => "req", "per_page" => "opt"}
26
+ def scope(scope_name, scope_definition)
27
+
28
+ unless scope_definition.is_a?(Hash)
29
+ raise ArgumentError, "Expecting an attributes hash given #{scope_definition.inspect}"
30
+ end
31
+
32
+ self.related_objects[:scopes][scope_name.to_sym] = scope_definition
33
+
34
+ self.class_eval do
35
+
36
+ define_singleton_method(scope_name) do |*args|
37
+
38
+ arg_names = scope_definition.keys
39
+ arg_types = scope_definition.values
40
+
41
+ finder_opts = {
42
+ scope_name => {}
43
+ }
44
+
45
+ arg_names.each_with_index do |arg_name, i|
46
+
47
+ # If we are dealing with a scope with multiple args
48
+ if arg_types[i] == :rest
49
+ finder_opts[scope_name][arg_name] =
50
+ args.slice(i, args.count)
51
+ # Else we are only dealing with a single argument
52
+ else
53
+ if arg_types[i] == :req || args[i].present?
54
+ finder_opts[scope_name][arg_name] = args[i]
55
+ end
56
+ end
57
+ end
58
+
59
+ # if we have nothing at this point we should just pass 'true'
60
+ if finder_opts[scope_name] == {}
61
+ finder_opts[scope_name] = true
62
+ end
63
+
64
+ ApiResource::Associations::ResourceScope.new(
65
+ self, finder_opts
66
+ )
67
+ end
68
+ end
69
+ end
33
70
  end
34
71
 
35
72
  def scopes
data/lib/api_resource.rb CHANGED
@@ -72,7 +72,9 @@ module ApiResource
72
72
  def self.cache(reset = false)
73
73
  @cache = nil if reset
74
74
  @cache ||= begin
75
- defined?(Rails) ? Rails.cache : ActiveSupport::Cache::MemoryStore.new
75
+ defined?(Rails) ? Rails.cache : ActiveSupport::Cache::MemoryStore.new
76
+ rescue
77
+ ActiveSupport::Cache::MemoryStore.new
76
78
  end
77
79
  end
78
80
 
@@ -4,13 +4,9 @@ describe ApiResource do
4
4
 
5
5
  context ".cache" do
6
6
 
7
- around(:each) do |example|
8
- begin
9
- old_rails = Object.send(:remove_const, :Rails)
10
- example.run
11
- ensure
12
- Rails = old_rails
13
- ApiResource
7
+ after(:each) do
8
+ if defined?(Rails)
9
+ Object.send(:remove_const, :Rails)
14
10
  end
15
11
  end
16
12
 
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiResource::Associations::AssociationScope do
4
+
5
+ context "#remote_path" do
6
+
7
+ it "should provide access to its remote path when
8
+ instantiated through a parent" do
9
+
10
+ test_resource = TestResource.find(1)
11
+ test_resource.has_many_service_uri.remote_path.should eql(
12
+ "/test_resource/1/has_many"
13
+ )
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end