resource 0.1.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 (39) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +26 -0
  4. data/Guardfile +22 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/lib/api_resource.rb +51 -0
  10. data/lib/api_resource/associations.rb +472 -0
  11. data/lib/api_resource/attributes.rb +154 -0
  12. data/lib/api_resource/base.rb +517 -0
  13. data/lib/api_resource/callbacks.rb +49 -0
  14. data/lib/api_resource/connection.rb +162 -0
  15. data/lib/api_resource/core_extensions.rb +7 -0
  16. data/lib/api_resource/custom_methods.rb +119 -0
  17. data/lib/api_resource/exceptions.rb +74 -0
  18. data/lib/api_resource/formats.rb +14 -0
  19. data/lib/api_resource/formats/json_format.rb +25 -0
  20. data/lib/api_resource/formats/xml_format.rb +36 -0
  21. data/lib/api_resource/log_subscriber.rb +15 -0
  22. data/lib/api_resource/mocks.rb +249 -0
  23. data/lib/api_resource/model_errors.rb +86 -0
  24. data/lib/api_resource/observing.rb +29 -0
  25. data/resource.gemspec +125 -0
  26. data/spec/lib/associations_spec.rb +412 -0
  27. data/spec/lib/attributes_spec.rb +109 -0
  28. data/spec/lib/base_spec.rb +454 -0
  29. data/spec/lib/callbacks_spec.rb +68 -0
  30. data/spec/lib/model_errors_spec.rb +29 -0
  31. data/spec/spec_helper.rb +32 -0
  32. data/spec/support/mocks/association_mocks.rb +18 -0
  33. data/spec/support/mocks/error_resource_mocks.rb +21 -0
  34. data/spec/support/mocks/test_resource_mocks.rb +23 -0
  35. data/spec/support/requests/association_requests.rb +14 -0
  36. data/spec/support/requests/error_resource_requests.rb +25 -0
  37. data/spec/support/requests/test_resource_requests.rb +31 -0
  38. data/spec/support/test_resource.rb +19 -0
  39. metadata +277 -0
@@ -0,0 +1,454 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'json'
3
+
4
+ include ApiResource
5
+
6
+ describe "Base" do
7
+
8
+ after(:all) do
9
+ TestResource.reload_class_attributes
10
+ end
11
+
12
+ describe "Loading data from a hash" do
13
+
14
+ describe "Determining Attributes, Scopes, and Associations from the server" do
15
+
16
+ it "should determine it's attributes when the class loads" do
17
+ tst = TestResource.new
18
+ tst.attribute?(:name).should be_true
19
+ tst.attribute?(:age).should be_true
20
+ end
21
+
22
+ it "should determine it's associations when the class loads" do
23
+ tst = TestResource.new
24
+ tst.association?(:has_many_objects).should be_true
25
+ tst.association?(:belongs_to_object).should be_true
26
+ end
27
+
28
+ it "should be able to determine scopes when the class loads" do
29
+ tst = TestResource.new
30
+ tst.scope?(:paginate).should be_true
31
+ tst.scope?(:active).should be_true
32
+ end
33
+
34
+ end
35
+ context "Attributes" do
36
+ before(:all) do
37
+ TestResource.define_attributes :attr1, :attr2
38
+ end
39
+
40
+ it "should set attributes for the data loaded from a hash" do
41
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2"})
42
+ tst.attr1?.should be_true
43
+ tst.attr1.should eql("attr1")
44
+ tst.attr1 = "test"
45
+ tst.attr1.should eql("test")
46
+ end
47
+
48
+ it "should create protected attributes for unknown attributes trying to be loaded" do
49
+ tst = TestResource.new({:attr1 => "attr1", :attr3 => "attr3"})
50
+ tst.attr3?.should be_true
51
+ tst.attr3.should eql("attr3")
52
+ lambda {
53
+ tst.attr3 = "test"
54
+ }.should raise_error
55
+ end
56
+ end
57
+
58
+ context "Associations" do
59
+ before(:all) do
60
+ TestResource.has_many :has_many_objects
61
+ TestResource.has_one :has_one_object
62
+ TestResource.belongs_to :belongs_to_object
63
+ end
64
+
65
+ after(:all) do
66
+ TestResource.related_objects.each do |key,val|
67
+ val.clear
68
+ end
69
+ end
70
+
71
+ context "MultiObjectProxy" do
72
+
73
+ it "should create a MultiObjectProxy for has_many associations" do
74
+ tst = TestResource.new({:has_many_objects => []})
75
+ tst.has_many_objects.should be_a(Associations::MultiObjectProxy)
76
+ end
77
+
78
+ it "should throw an error if a has many association is not nil or an array or a hash" do
79
+ TestResource.new({:has_many_objects => nil})
80
+ lambda {
81
+ TestResource.new({:has_many_objects => "invalid"})
82
+ }.should raise_error
83
+ end
84
+
85
+ it "should properly load the data from the provided array or hash" do
86
+ tst = TestResource.new({:has_many_objects => [{:service_uri => '/path'}]})
87
+ tst.has_many_objects.remote_path.should eql('/path')
88
+ tst = TestResource.new({:has_many_objects => {:service_uri => '/path'}})
89
+ tst.has_many_objects.remote_path.should eql('/path')
90
+ end
91
+
92
+ end
93
+
94
+ context "SingleObjectProxy" do
95
+
96
+ it "should create a SingleObjectProxy for belongs to and has_one associations" do
97
+ tst = TestResource.new(:belongs_to_object => {}, :has_one_object => {})
98
+ tst.belongs_to_object.should be_a(Associations::SingleObjectProxy)
99
+ tst.has_one_object.should be_a(Associations::SingleObjectProxy)
100
+ end
101
+
102
+ it "should throw an error if a belongs_to or has_many association is not a hash or nil" do
103
+ lambda {
104
+ TestResource.new(:belongs_to_object => [])
105
+ }.should raise_error
106
+ lambda {
107
+ TestResource.new(:has_one_object => [])
108
+ }.should raise_error
109
+ end
110
+
111
+ it "should properly load data from the provided hash" do
112
+ tst = TestResource.new(:has_one_object => {:service_uri => "/path"})
113
+ tst.has_one_object.remote_path.should eql('/path')
114
+ end
115
+
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "Request parameters and paths" do
121
+
122
+ after(:each) do
123
+ TestResource.element_name = TestResource.model_name.element
124
+ TestResource.collection_name = TestResource.element_name.to_s.pluralize
125
+ end
126
+
127
+ it "should set the element name and collection name by default" do
128
+ TestResource.element_name.should eql("test_resource")
129
+ TestResource.collection_name.should eql("test_resources")
130
+ end
131
+
132
+ it "should be able to set the element and collection names to anything" do
133
+ TestResource.element_name = "element"
134
+ TestResource.collection_name = "elements"
135
+ TestResource.element_name.should eql("element")
136
+ TestResource.collection_name.should eql("elements")
137
+ end
138
+
139
+ it "should propery generate collection paths and element paths with the new names and the default format json" do
140
+ TestResource.element_name = "element"
141
+ TestResource.collection_name = "elements"
142
+ TestResource.new_element_path.should eql("/elements/new.json")
143
+ TestResource.collection_path.should eql("/elements.json")
144
+ TestResource.element_path(1).should eql("/elements/1.json")
145
+ TestResource.element_path(1, :active => true).should eql("/elements/1.json?active=true")
146
+ end
147
+
148
+ it "should be able to set the format" do
149
+ TestResource.format.extension.to_sym.should eql(:json)
150
+ TestResource.format = :xml
151
+ TestResource.format.extension.to_sym.should eql(:xml)
152
+ TestResource.format = :json
153
+ end
154
+
155
+ it "should be able to set an http timeout" do
156
+ TestResource.timeout = 5
157
+ TestResource.timeout.should eql(5)
158
+ TestResource.connection.timeout.should eql(5)
159
+ end
160
+
161
+ end
162
+
163
+ describe "Serialization" do
164
+
165
+ before(:all) do
166
+ TestResource.has_many :has_many_objects
167
+ TestResource.define_attributes :attr1, :attr2
168
+ TestResource.include_root_in_json = true
169
+ end
170
+
171
+ before(:each) do
172
+ TestResource.include_root_in_json = false
173
+ end
174
+
175
+ context "JSON" do
176
+ it "should be able to serialize itself without the root" do
177
+ TestResource.include_root_in_json = false
178
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2"})
179
+ hash = JSON.parse(tst.to_json)
180
+ hash["attr1"].should eql("attr1")
181
+ hash["attr2"].should eql("attr2")
182
+ end
183
+
184
+ it "should be able to serialize itself with the root" do
185
+ TestResource.include_root_in_json = true
186
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2"})
187
+ hash = JSON.parse(tst.to_json)
188
+ hash["test_resource"].should_not be_nil
189
+ end
190
+
191
+ it "should not include associations by default" do
192
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2", :has_many_objects => []})
193
+ hash = JSON.parse(tst.to_json)
194
+ hash["has_many_objects"].should be_nil
195
+ end
196
+
197
+ it "should include associations passed given in the include_associations array" do
198
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2", :has_many_objects => []})
199
+ hash = JSON.parse(tst.to_json(:include_associations => [:has_many_objects]))
200
+ hash["has_many_objects"].should_not be_nil
201
+ end
202
+
203
+ it "should not include unknown attributes unless they are passed in via the include_extras array" do
204
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2", :attr3 => "attr3"})
205
+ hash = JSON.parse(tst.to_json)
206
+ hash["attr3"].should be_nil
207
+ hash = JSON.parse(tst.to_json(:include_extras => [:attr3]))
208
+ hash["attr3"].should_not be_nil
209
+ end
210
+
211
+ it "should ignore fields set under the except option" do
212
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2", :attr3 => "attr3"})
213
+ hash = JSON.parse(tst.to_json(:except => [:attr1]))
214
+ hash["attr1"].should be_nil
215
+ end
216
+
217
+ end
218
+
219
+ context "XML" do
220
+
221
+ it "should only be able to serialize itself with the root" do
222
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2"})
223
+ hash = Hash.from_xml(tst.to_xml)
224
+ hash["test_resource"].should_not be_nil
225
+ end
226
+
227
+ it "should properly serialize associations if they are included" do
228
+ tst = TestResource.new({:attr1 => "attr1", :attr2 => "attr2", :has_many_objects => []})
229
+ hash = Hash.from_xml(tst.to_xml(:include_associations => [:has_many_objects]))
230
+ hash["test_resource"]["has_many_objects"].should eql([])
231
+ end
232
+ end
233
+
234
+ end
235
+
236
+ describe "Finding Data" do
237
+
238
+ before(:all) do
239
+ TestResource.reload_class_attributes
240
+ end
241
+
242
+ it "should be able to find all" do
243
+ resources = TestResource.find(:all)
244
+ resources.size.should eql(5)
245
+ resources.each{|r| r.should be_a TestResource}
246
+ end
247
+
248
+ it "should be able to find first or last" do
249
+ res = TestResource.first
250
+ res.should be_a TestResource
251
+ res.name.should_not be_blank
252
+ res.age.should_not be_blank
253
+
254
+ res = TestResource.last
255
+ res.should be_a TestResource
256
+ res.name.should_not be_blank
257
+ res.age.should_not be_blank
258
+ end
259
+
260
+ it "should be able to find by id" do
261
+ res = TestResource.find(2)
262
+ res.should be_a TestResource
263
+ res.id.to_i.should eql(2)
264
+ end
265
+
266
+ end
267
+
268
+ describe "Saving Data" do
269
+
270
+ before(:all) do
271
+ TestResource.include_root_in_json = true
272
+ TestResource.reload_class_attributes
273
+ end
274
+
275
+ context "Creating new records" do
276
+
277
+ before(:all) do
278
+ TestResource.has_many :has_many_objects
279
+ end
280
+
281
+ it "should be able to post new data via the create method" do
282
+ tr = TestResource.create({:name => "Ethan", :age => 20})
283
+ tr.id.should_not be_blank
284
+ end
285
+
286
+ it "should be able to post new data via the save method" do
287
+ tr = TestResource.build({:name => "Ethan", :age => 20})
288
+ tr.save.should be_true
289
+ tr.id.should_not be_blank
290
+ end
291
+
292
+ context("Override create to return the json") do
293
+
294
+ before(:all) do
295
+ TestResource.send(:alias_method, :old_create, :create)
296
+ TestResource.send(:alias_method, :old_save, :save)
297
+
298
+ TestResource.send(:define_method, :create) do |*args|
299
+ opts = args.extract_options!
300
+ # When we create we should not include any blank attributes unless they are associations
301
+ except = self.class.include_blank_attributes_on_create ? {} : self.attributes.select{|k,v| v.blank?}
302
+ opts[:except] = opts[:except] ? opts[:except].concat(except.keys).uniq.symbolize_array : except.keys.symbolize_array
303
+ opts[:include_associations] = opts[:include_associations] ? opts[:include_associations].concat(args) : []
304
+ opts[:include_extras] ||= []
305
+ encode(opts)
306
+ end
307
+ TestResource.send(:define_method, :save) do |*args|
308
+ new? ? create(*args) : update(*args)
309
+ end
310
+ end
311
+
312
+ after(:all) do
313
+ TestResource.send(:alias_method, :create, :old_create)
314
+ TestResource.send(:alias_method, :save, :old_save)
315
+ end
316
+
317
+ it "should be able to include associations when saving if they are specified" do
318
+ tr = TestResource.build(:name => "Ethan", :age => 20)
319
+ hash = JSON.parse(tr.save)
320
+ hash['test_resource']['has_many_objects'].should be_nil
321
+ hash = JSON.parse(tr.save(:include_associations => [:has_many_objects]))
322
+ hash['test_resource']['has_many_objects'].should eql([])
323
+ end
324
+
325
+ it "should not include nil attributes when creating by default" do
326
+ tr = TestResource.build(:name => "Ethan")
327
+ hash = JSON.parse(tr.save)
328
+ hash['test_resource']['age'].should be_nil
329
+ hash['test_resource']['name'].should eql("Ethan")
330
+ end
331
+
332
+ it "should include nil attributes if they are passed in through the include_extras" do
333
+ tr = TestResource.build(:name => "Ethan")
334
+ hash = JSON.parse(tr.save(:include_extras => [:age]))
335
+ hash['test_resource'].key?('age').should be_true
336
+ end
337
+
338
+ it "should include nil attributes when creating if include_nil_attributes_on_create is true" do
339
+ TestResource.include_blank_attributes_on_create = true
340
+ tr = TestResource.build(:name => "Ethan")
341
+ hash = JSON.parse(tr.save)
342
+ hash['test_resource'].key?('age').should be_true
343
+ TestResource.include_blank_attributes_on_create = false
344
+ end
345
+ end
346
+ end
347
+
348
+ context "Updating old records" do
349
+ before(:all) do
350
+ TestResource.send(:alias_method, :old_update, :update)
351
+ TestResource.send(:alias_method, :old_save, :save)
352
+
353
+ TestResource.send(:define_method, :update) do |*args|
354
+ opts = args.extract_options!
355
+ # When we create we should not include any blank attributes
356
+ except = self.class.attribute_names - self.changed.symbolize_array
357
+ changed_associations = self.changed.symbolize_array.select{|item| self.association?(item)}
358
+ opts[:except] = opts[:except] ? opts[:except].concat(except).uniq.symbolize_array : except.symbolize_array
359
+ opts[:include_associations] = opts[:include_associations] ? opts[:include_associations].concat(args).concat(changed_associations).uniq : changed_associations.concat(args)
360
+ opts[:include_extras] ||= []
361
+ opts[:except] = [:id] if self.class.include_all_attributes_on_update
362
+ encode(opts)
363
+ end
364
+ TestResource.send(:define_method, :save) do |*args|
365
+ new? ? create(*args) : update(*args)
366
+ end
367
+ end
368
+
369
+ after(:all) do
370
+ TestResource.send(:alias_method, :update, :old_update)
371
+ TestResource.send(:alias_method, :save, :old_save)
372
+ end
373
+
374
+ it "should be able to put updated data via the update method" do
375
+ tr = TestResource.new(:id => 1, :name => "Ethan")
376
+ tr.should_not be_new
377
+ # Thus we know we are calling update
378
+ tr.age = 6
379
+ hash = JSON.parse(tr.update)
380
+ hash['test_resource']['age'].should eql(6)
381
+
382
+ hash = JSON.parse(tr.save)
383
+ hash['test_resource']['age'].should eql(6)
384
+ end
385
+
386
+ it "should only include changed attributes when updating" do
387
+ tr = TestResource.new(:id => 1, :name => "Ethan")
388
+ tr.should_not be_new
389
+ # Thus we know we are calling update
390
+ tr.age = 6
391
+ hash = JSON.parse(tr.save)
392
+ hash['test_resource']['name'].should be_nil
393
+ end
394
+
395
+ it "should include changed associations without specification" do
396
+ tr = TestResource.new(:id => 1, :name => "Ethan")
397
+ tr.has_many_objects = [TestResource.new]
398
+ hash = JSON.parse(tr.save)
399
+ hash['test_resource']['has_many_objects'].should_not be_blank
400
+ end
401
+
402
+ it "should include unchanged associations if they are specified" do
403
+ tr = TestResource.new(:id => 1, :name => "Ethan")
404
+ hash = JSON.parse(tr.save(:has_many_objects))
405
+ hash['test_resource']['has_many_objects'].should eql([])
406
+ end
407
+
408
+ it "should include all attributes if include_all_attributes_on_update is true" do
409
+ TestResource.include_all_attributes_on_update = true
410
+ tr = TestResource.new(:id => 1, :name => "Ethan")
411
+ hash = JSON.parse(tr.save)
412
+ hash['test_resource']['name'].should eql("Ethan")
413
+ hash['test_resource'].key?('age').should be_true
414
+ TestResource.include_all_attributes_on_update = false
415
+ end
416
+
417
+ end
418
+
419
+ end
420
+
421
+ describe "Deleting data" do
422
+ it "should be able to delete an id from the class method" do
423
+ TestResource.delete(1).should be_true
424
+ end
425
+
426
+ it "should be able to destroy itself as an instance" do
427
+ tr = TestResource.new(:id => 1, :name => "Ethan")
428
+ tr.destroy.should be_true
429
+ end
430
+ end
431
+
432
+ describe "Random methods" do
433
+
434
+ it "should know if it is persisted" do
435
+ tr = TestResource.new(:id => 1, :name => "Ethan")
436
+ tr.persisted?.should be_true
437
+ tr = TestResource.new(:name => "Ethan")
438
+ tr.persisted?.should be_false
439
+ end
440
+
441
+ end
442
+
443
+ describe "Inheritable Accessors" do
444
+
445
+ it "should copy the default values down to any level of subclass" do
446
+ class Child < TestResource; end
447
+
448
+ Child.site.should eql(TestResource.site)
449
+ Child.site.should_not be_blank
450
+ end
451
+
452
+ end
453
+
454
+ end