api_resource 0.4.3 → 0.5.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 (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