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.
- data/VERSION +1 -1
- data/api_resource.gemspec +4 -74
- data/coverage/assets/0.5.3/app.js +88 -0
- data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
- data/coverage/assets/0.5.3/favicon_green.png +0 -0
- data/coverage/assets/0.5.3/favicon_red.png +0 -0
- data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
- data/coverage/assets/0.5.3/highlight.css +129 -0
- data/coverage/assets/0.5.3/highlight.pack.js +1 -0
- data/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
- data/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
- data/coverage/assets/0.5.3/jquery.timeago.js +141 -0
- data/coverage/assets/0.5.3/jquery.url.js +174 -0
- data/coverage/assets/0.5.3/loading.gif +0 -0
- data/coverage/assets/0.5.3/magnify.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
- data/coverage/assets/0.5.3/stylesheet.css +383 -0
- data/coverage/index.html +3573 -0
- data/lib/api_resource/associations/abstract_scope.rb +191 -0
- data/lib/api_resource/associations/association_scope.rb +47 -0
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +5 -6
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +5 -8
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +12 -13
- data/lib/api_resource/associations/multi_object_proxy.rb +65 -39
- data/lib/api_resource/associations/resource_scope.rb +6 -17
- data/lib/api_resource/associations/scope.rb +23 -121
- data/lib/api_resource/associations/single_object_proxy.rb +41 -50
- data/lib/api_resource/associations.rb +32 -11
- data/lib/api_resource/attributes.rb +108 -69
- data/lib/api_resource/base.rb +114 -106
- data/lib/api_resource/local.rb +1 -1
- data/lib/api_resource/model_errors.rb +9 -6
- data/lib/api_resource/scopes.rb +53 -16
- data/lib/api_resource.rb +3 -1
- data/spec/lib/api_resource_spec.rb +3 -7
- data/spec/lib/associations/association_scope_spec.rb +19 -0
- data/spec/lib/associations_spec.rb +251 -162
- data/spec/lib/attributes_spec.rb +33 -15
- data/spec/lib/base_spec.rb +302 -64
- data/spec/lib/callbacks_spec.rb +4 -2
- data/spec/lib/local_spec.rb +5 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/mocks/association_mocks.rb +9 -1
- data/spec/support/requests/association_requests.rb +5 -5
- data/spec/support/requests/test_resource_requests.rb +16 -4
- data/spec/tmp/api_resource_test_db.sqlite +0 -0
- metadata +68 -22
- data/.document +0 -5
- data/.rspec +0 -5
- data/.travis.yml +0 -4
- data/lib/api_resource/associations/association_proxy.rb +0 -121
- data/lib/api_resource/associations/dynamic_resource_scope.rb +0 -23
- data/lib/api_resource/associations/generic_scope.rb +0 -68
- data/lib/api_resource/associations/multi_argument_resource_scope.rb +0 -15
- data/lib/api_resource/associations/relation_scope.rb +0 -25
data/lib/api_resource/base.rb
CHANGED
|
@@ -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
|
-
|
|
60
|
+
@resource_definition = self.connection.get(
|
|
52
61
|
self.new_element_path, self.headers
|
|
53
62
|
)
|
|
54
63
|
# Attributes go first
|
|
55
|
-
if
|
|
64
|
+
if resource_definition["attributes"]
|
|
56
65
|
|
|
57
66
|
define_attributes(
|
|
58
|
-
*(
|
|
67
|
+
*(resource_definition["attributes"]["public"] || [])
|
|
59
68
|
)
|
|
60
69
|
define_protected_attributes(
|
|
61
|
-
*(
|
|
70
|
+
*(resource_definition["attributes"]["protected"] || [])
|
|
62
71
|
)
|
|
63
72
|
|
|
64
73
|
end
|
|
65
74
|
# Then scopes
|
|
66
|
-
if
|
|
67
|
-
|
|
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
|
|
73
|
-
|
|
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
|
|
85
|
-
attrs +=
|
|
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
|
|
90
|
-
attrs +=
|
|
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.
|
|
125
|
+
self.load_resource_definition
|
|
118
126
|
super
|
|
119
127
|
end
|
|
120
128
|
|
|
121
|
-
def
|
|
122
|
-
unless instance_variable_defined?(:@
|
|
123
|
-
|
|
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
|
-
|
|
136
|
+
# we didn't do anything
|
|
137
|
+
false
|
|
127
138
|
end
|
|
128
139
|
|
|
129
|
-
def
|
|
140
|
+
def reload_resource_definition
|
|
130
141
|
# clear the public_attribute_names, protected_attribute_names
|
|
131
|
-
if instance_variable_defined?(:@
|
|
132
|
-
remove_instance_variable(:@
|
|
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.
|
|
147
|
+
self.load_resource_definition
|
|
137
148
|
end
|
|
138
149
|
# backwards compatibility
|
|
139
|
-
alias_method :reload_class_attributes, :
|
|
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.
|
|
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|
|
|
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={})
|
|
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
|
-
#
|
|
262
|
-
def new_element_path
|
|
263
|
-
|
|
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
|
-
#
|
|
270
|
-
#
|
|
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
|
-
|
|
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.
|
|
448
|
-
# Now we can make a call to setup the inheriting
|
|
449
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
511
|
-
self.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
data/lib/api_resource/local.rb
CHANGED
|
@@ -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({})
|
|
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
|
|
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
|
|
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
|
|
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
|
data/lib/api_resource/scopes.rb
CHANGED
|
@@ -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]
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|