api_resource 0.5.1 → 0.6.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 (163) hide show
  1. data/.document +5 -0
  2. data/.gitignore +55 -0
  3. data/.rspec +8 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +3 -36
  6. data/Gemfile.lock +37 -57
  7. data/Guardfile +7 -1
  8. data/{LICENSE.txt → LICENSE} +4 -2
  9. data/README.md +29 -0
  10. data/README.rdoc +5 -1
  11. data/Rakefile +5 -36
  12. data/api_resource.gemspec +40 -104
  13. data/lib/api_resource.rb +4 -1
  14. data/lib/api_resource/associations.rb +30 -14
  15. data/lib/api_resource/associations/association_proxy.rb +97 -0
  16. data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +0 -1
  17. data/lib/api_resource/associations/has_many_remote_object_proxy.rb +0 -1
  18. data/lib/api_resource/associations/has_one_remote_object_proxy.rb +7 -4
  19. data/lib/api_resource/associations/multi_object_proxy.rb +11 -16
  20. data/lib/api_resource/associations/single_object_proxy.rb +8 -6
  21. data/lib/api_resource/attributes.rb +29 -41
  22. data/lib/api_resource/base.rb +31 -102
  23. data/lib/api_resource/conditions.rb +36 -0
  24. data/lib/api_resource/conditions/abstract_condition.rb +112 -0
  25. data/lib/api_resource/conditions/association_condition.rb +18 -0
  26. data/lib/api_resource/conditions/include_condition.rb +16 -0
  27. data/lib/api_resource/conditions/multi_object_association_condition.rb +17 -0
  28. data/lib/api_resource/conditions/scope_condition.rb +11 -0
  29. data/lib/api_resource/conditions/single_object_association_condition.rb +19 -0
  30. data/lib/api_resource/connection.rb +18 -12
  31. data/lib/api_resource/finders.rb +122 -0
  32. data/lib/api_resource/finders/abstract_finder.rb +89 -0
  33. data/lib/api_resource/finders/multi_object_association_finder.rb +39 -0
  34. data/lib/api_resource/finders/resource_finder.rb +33 -0
  35. data/lib/api_resource/finders/single_object_association_finder.rb +40 -0
  36. data/lib/api_resource/observing.rb +19 -3
  37. data/lib/api_resource/scopes.rb +3 -3
  38. data/lib/api_resource/typecast.rb +85 -0
  39. data/lib/api_resource/typecasters/array_typecaster.rb +19 -0
  40. data/lib/api_resource/typecasters/boolean_typecaster.rb +22 -0
  41. data/lib/api_resource/typecasters/date_typecaster.rb +35 -0
  42. data/lib/api_resource/typecasters/float_typecaster.rb +22 -0
  43. data/lib/api_resource/typecasters/integer_typecaster.rb +22 -0
  44. data/lib/api_resource/typecasters/string_typecaster.rb +19 -0
  45. data/lib/api_resource/typecasters/time_typecaster.rb +44 -0
  46. data/lib/api_resource/version.rb +3 -0
  47. data/spec/lib/api_resource_spec.rb +6 -0
  48. data/spec/lib/associations/association_scope_spec.rb +1 -1
  49. data/spec/lib/associations_spec.rb +17 -50
  50. data/spec/lib/base_spec.rb +22 -0
  51. data/spec/lib/conditions/abstract_conditions_spec.rb +78 -0
  52. data/spec/lib/connection_spec.rb +19 -0
  53. data/spec/lib/finders/multi_object_association_finder_spec.rb +43 -0
  54. data/spec/lib/finders/resource_finder_spec.rb +89 -0
  55. data/spec/lib/finders/single_object_association_finder_spec.rb +43 -0
  56. data/spec/lib/observing_spec.rb +96 -0
  57. data/spec/lib/typecast_spec.rb +174 -0
  58. data/spec/lib/typecasters/boolean_typecaster_spec.rb +33 -0
  59. data/spec/lib/typecasters/date_typecaster_spec.rb +62 -0
  60. data/spec/lib/typecasters/float_typecaster_spec.rb +39 -0
  61. data/spec/lib/typecasters/integer_typecaster_spec.rb +40 -0
  62. data/spec/lib/typecasters/string_typecaster_spec.rb +28 -0
  63. data/spec/lib/typecasters/time_typecaster_spec.rb +65 -0
  64. data/spec/spec_helper.rb +0 -1
  65. metadata +134 -194
  66. data/VERSION +0 -1
  67. data/coverage/assets/0.5.3/app.js +0 -88
  68. data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
  69. data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
  70. data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
  71. data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
  72. data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
  73. data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
  74. data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
  75. data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
  76. data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
  77. data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
  78. data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
  79. data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
  80. data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
  81. data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
  82. data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
  83. data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
  84. data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
  85. data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
  86. data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
  87. data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
  88. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +0 -363
  89. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +0 -44
  90. data/coverage/assets/0.5.3/favicon_green.png +0 -0
  91. data/coverage/assets/0.5.3/favicon_red.png +0 -0
  92. data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
  93. data/coverage/assets/0.5.3/highlight.css +0 -129
  94. data/coverage/assets/0.5.3/highlight.pack.js +0 -1
  95. data/coverage/assets/0.5.3/jquery-1.6.2.min.js +0 -18
  96. data/coverage/assets/0.5.3/jquery.dataTables.min.js +0 -152
  97. data/coverage/assets/0.5.3/jquery.timeago.js +0 -141
  98. data/coverage/assets/0.5.3/jquery.url.js +0 -174
  99. data/coverage/assets/0.5.3/loading.gif +0 -0
  100. data/coverage/assets/0.5.3/magnify.png +0 -0
  101. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  102. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  103. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  104. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  105. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  106. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  107. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  108. data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  109. data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
  110. data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  111. data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
  112. data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
  113. data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  114. data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +0 -295
  115. data/coverage/assets/0.5.3/stylesheet.css +0 -383
  116. data/coverage/assets/0.7.1/application.css +0 -1110
  117. data/coverage/assets/0.7.1/application.js +0 -626
  118. data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  119. data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  120. data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  121. data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  122. data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  123. data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  124. data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  125. data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  126. data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  127. data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  128. data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  129. data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  130. data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  131. data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  132. data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  133. data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  134. data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  135. data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  136. data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  137. data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  138. data/coverage/assets/0.7.1/favicon_green.png +0 -0
  139. data/coverage/assets/0.7.1/favicon_red.png +0 -0
  140. data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  141. data/coverage/assets/0.7.1/loading.gif +0 -0
  142. data/coverage/assets/0.7.1/magnify.png +0 -0
  143. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  144. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  145. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  146. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  147. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  148. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  149. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  150. data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  151. data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  152. data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  153. data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  154. data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  155. data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  156. data/coverage/index.html +0 -3564
  157. data/lib/api_resource/associations/abstract_scope.rb +0 -191
  158. data/lib/api_resource/associations/association_scope.rb +0 -47
  159. data/lib/api_resource/associations/resource_scope.rb +0 -21
  160. data/lib/api_resource/associations/scope.rb +0 -34
  161. data/spec/lib/associations/resource_scope_spec.rb +0 -24
  162. data/spec/tmp/api_resource_test_db.sqlite +0 -0
  163. data/tmp/rspec_guard_result +0 -1
@@ -131,6 +131,25 @@ describe Connection do
131
131
  end
132
132
 
133
133
  end
134
+
135
+ context "headers" do
136
+
137
+ it "should include the Lifebooker-Token in its headers" do
138
+
139
+ old_token = ApiResource.token
140
+
141
+ ApiResource.with_token("a") do
142
+ TestResource.connection.headers["Lifebooker-Token"].should eql("a")
143
+ ApiResource.with_token("b") do
144
+ TestResource.connection.headers["Lifebooker-Token"].should eql("b")
145
+ end
146
+ end
147
+
148
+ TestResource.connection.headers["Lifebooker-Token"].should eql(old_token)
149
+
150
+ end
151
+
152
+ end
134
153
 
135
154
 
136
155
 
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe "MultiObjectAssociationFinder" do
4
+
5
+ before(:each) do
6
+ TestResource.reload_resource_definition
7
+ end
8
+
9
+ it "should build a proper load path and call into the connection" do
10
+ TestResource.connection.expects(:get).with("test_resources.json?id[]=1&id[]=2").returns([])
11
+
12
+ ApiResource::Finders::MultiObjectAssociationFinder.new(
13
+ TestResource,
14
+ stub(:remote_path => "test_resources", :to_query => "id[]=1&id[]=2", :blank_conditions? => false)
15
+ ).find
16
+ end
17
+
18
+ it "should load a has many association properly" do
19
+ # much of this test already lies in resource_finder_spec.rb
20
+ # this just verifies that the data is passed in correctly
21
+ finder = ApiResource::Finders::MultiObjectAssociationFinder.new(
22
+ TestResource,
23
+ stub(
24
+ :remote_path => "test_resources",
25
+ :blank_conditions? => true,
26
+ :eager_load? => true,
27
+ :included_objects => [:has_many_objects]
28
+ )
29
+ )
30
+
31
+ tr = TestResource.new
32
+ tr.stubs(:id).returns(1)
33
+ tr.stubs(:has_many_object_ids).returns([1,2])
34
+ TestResource.connection.expects(:get).with("test_resources.json").returns([4])
35
+ TestResource.expects(:instantiate_collection).with([4]).returns([tr])
36
+
37
+ finder.expects(:load_includes).with(:has_many_objects => [1,2]).returns(5)
38
+ finder.expects(:apply_includes).with([tr], 5).returns(6)
39
+
40
+ finder.find.should eql([tr])
41
+ end
42
+
43
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ResourceFinder" do
4
+
5
+ before(:each) do
6
+ TestResource.reload_resource_definition
7
+ end
8
+
9
+ it "should call find on the class normally without includes" do
10
+ TestResource.expects(:find).with(:all, {})
11
+
12
+ ApiResource::Finders::ResourceFinder.new(
13
+ TestResource,
14
+ mock(:to_hash => {})
15
+ ).find
16
+ end
17
+
18
+ it "should pass conditions into the finder method" do
19
+ TestResource.expects(:find).with(:all, {:id => [1,2,3]})
20
+
21
+ ApiResource::Finders::ResourceFinder.new(
22
+ TestResource,
23
+ mock(:to_hash => {:id => [1,2,3]})
24
+ ).find
25
+ end
26
+
27
+ it "should try to load includes if it finds an object" do
28
+ obj_mock = mock(:id => 1)
29
+
30
+ TestResource.expects(:find).with(:all, {}).returns([obj_mock])
31
+
32
+ obj = ApiResource::Finders::ResourceFinder.new(
33
+ TestResource,
34
+ stub(:to_hash => {}, :eager_load? => false, :included_objects => [])
35
+ ).find
36
+
37
+ obj.first.id.should eql(1)
38
+ end
39
+
40
+ it "should load and distribute includes among the returned objects" do
41
+ inc_mock = stub(:id => 1)
42
+ inc_mock2 = stub(:id => 2)
43
+
44
+ tr = TestResource.new
45
+ tr.stubs(:id).returns(1)
46
+ tr.stubs(:has_many_object_ids).returns([1,2])
47
+ tr.expects(:has_many_objects=).with([inc_mock, inc_mock2])
48
+ tr.expects(:has_many_objects).returns([inc_mock, inc_mock2])
49
+
50
+ TestResource.expects(:find).with(:all, {}).returns([tr])
51
+
52
+ HasManyObject.expects(:find).with(:all, :id => [1,2]).returns([inc_mock, inc_mock2])
53
+
54
+ obj = ApiResource::Finders::ResourceFinder.new(
55
+ TestResource,
56
+ stub(:to_hash => {}, :eager_load? => true, :included_objects => [:has_many_objects])
57
+ ).find
58
+ obj.first.has_many_objects.collect(&:id).should eql([1,2])
59
+ end
60
+
61
+ it "should work with loading for multiple objects" do
62
+ inc_mock = stub(:id => 1)
63
+ inc_mock2 = stub(:id => 2)
64
+
65
+ tr = TestResource.new
66
+ tr.stubs(:id).returns(1)
67
+ tr.stubs(:has_many_object_ids).returns([1])
68
+ tr.expects(:has_many_objects=).with([inc_mock]).returns([inc_mock])
69
+ tr.expects(:has_many_objects).returns([inc_mock])
70
+
71
+ tr2 = TestResource.new
72
+ tr2.stubs(:id).returns(2)
73
+ tr2.stubs(:has_many_object_ids).returns([2])
74
+ tr2.expects(:has_many_objects=).with([inc_mock2]).returns([inc_mock2])
75
+ tr2.expects(:has_many_objects).returns([inc_mock2])
76
+
77
+ TestResource.expects(:find).with(:all, {}).returns([tr, tr2])
78
+ HasManyObject.expects(:find).with(:all, :id => [1,2]).returns([inc_mock2, inc_mock])
79
+
80
+ obj = ApiResource::Finders::ResourceFinder.new(
81
+ TestResource,
82
+ stub(:to_hash => {}, :eager_load? => true, :included_objects => [:has_many_objects])
83
+ ).find
84
+
85
+ obj.first.has_many_objects.collect(&:id).should eql([1])
86
+ obj.second.has_many_objects.collect(&:id).should eql([2])
87
+ end
88
+
89
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe "SingleObjectAssociationFinder" do
4
+
5
+ before(:each) do
6
+ TestResource.reload_resource_definition
7
+ end
8
+
9
+ it "should build a proper load path and call into the connection" do
10
+ TestResource.connection.expects(:get).with("test_resources.json?id[]=1&id[]=2").returns(nil)
11
+
12
+ ApiResource::Finders::SingleObjectAssociationFinder.new(
13
+ TestResource,
14
+ stub(:remote_path => "test_resources", :to_query => "id[]=1&id[]=2", :blank_conditions? => false)
15
+ ).find
16
+ end
17
+
18
+ it "should load a has many association properly" do
19
+ # much of this test already lies in resource_finder_spec.rb
20
+ # this just verifies that the data is passed in correctly
21
+ finder = ApiResource::Finders::SingleObjectAssociationFinder.new(
22
+ TestResource,
23
+ stub(
24
+ :remote_path => "test_resources",
25
+ :blank_conditions? => true,
26
+ :eager_load? => true,
27
+ :included_objects => [:has_many_objects]
28
+ )
29
+ )
30
+
31
+ tr = TestResource.new
32
+ tr.stubs(:id).returns(1)
33
+ tr.stubs(:has_many_object_ids).returns([1,2])
34
+ TestResource.connection.expects(:get).with("test_resources.json").returns(4)
35
+ TestResource.expects(:instantiate_record).returns(tr)
36
+
37
+ finder.expects(:load_includes).with(:has_many_objects => [1,2]).returns(5)
38
+ finder.expects(:apply_includes).with(tr, 5).returns(6)
39
+
40
+ finder.find.should eql(tr)
41
+ end
42
+
43
+ end
@@ -0,0 +1,96 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ include ApiResource
4
+
5
+ describe "Observing" do
6
+
7
+ before(:all) do
8
+ TestResource.reload_class_attributes
9
+ end
10
+
11
+ after(:all) do
12
+ TestResource.observers = []
13
+ TestResource.observer_instances.clear
14
+ TestResource.reload_class_attributes
15
+ end
16
+
17
+ it "should notify observers on create" do
18
+ tr = TestResource.new
19
+ tr.expects(:notify_observers).with(:before_save).returns(true)
20
+ tr.expects(:notify_observers).with(:before_create).returns(true)
21
+ tr.expects(:create_without_observers).returns(true)
22
+ tr.expects(:notify_observers).with(:after_create).returns(true)
23
+ tr.expects(:notify_observers).with(:after_save).returns(true)
24
+ tr.save
25
+ end
26
+
27
+ it "should notify observers on update" do
28
+ tr = TestResource.new
29
+ tr.stubs(:new?).returns(false)
30
+ tr.expects(:notify_observers).with(:before_save).returns(true)
31
+ tr.expects(:notify_observers).with(:before_update).returns(true)
32
+ tr.expects(:update_without_observers).returns(true)
33
+ tr.expects(:notify_observers).with(:after_update).returns(true)
34
+ tr.expects(:notify_observers).with(:after_save).returns(true)
35
+ tr.save
36
+ end
37
+
38
+ it "should notify observers on destroy" do
39
+ tr = TestResource.new
40
+ tr.stubs(:id).returns(1)
41
+ tr.expects(:notify_observers).with(:before_destroy).returns(true)
42
+ tr.expects(:destroy_without_observers).returns(true)
43
+ tr.expects(:notify_observers).with(:after_destroy).returns(true)
44
+ tr.destroy
45
+ end
46
+
47
+ it "should notify an observer for a given event" do
48
+ class TestResourceObserver < ApiResource::Observer
49
+ def before_save(elm)
50
+ return true
51
+ end
52
+ end
53
+
54
+ TestResourceObserver.any_instance.expects(:before_save).returns(true)
55
+ # A bit of a pain to set these up ex post facto
56
+ TestResource.observers = :test_resource_observer
57
+ TestResource.instantiate_observers
58
+ tr = TestResource.new
59
+ tr.expects(:save_without_observers).returns(true)
60
+ tr.save
61
+ end
62
+
63
+ it "should cancel the save if the observer returns false" do
64
+ class TestResourceObserver < ApiResource::Observer
65
+ def before_save(elm)
66
+ return true
67
+ end
68
+ end
69
+
70
+ TestResourceObserver.any_instance.expects(:before_save).returns(false)
71
+ # A bit of a pain to set these up ex post facto
72
+ TestResource.observers = :test_resource_observer
73
+ TestResource.instantiate_observers
74
+ tr = TestResource.new
75
+ tr.expects(:save_without_observers).never
76
+ tr.save
77
+ end
78
+
79
+ it "should run callbacks before observers" do
80
+ klass = Class.new(TestResource)
81
+ klass.class_eval <<-EOE, __FILE__, __LINE__ + 1
82
+ before_save :abort_save
83
+
84
+ def abort_save
85
+ return false
86
+ end
87
+ EOE
88
+
89
+ tr = klass.new
90
+ tr.expects(:abort_save).returns(false)
91
+ tr.expects(:notify_observers).never
92
+ tr.expects(:save_without_callbacks).never
93
+ tr.save
94
+ end
95
+
96
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiResource::Typecast do
4
+
5
+ let(:klass) { ApiResource::Base }
6
+
7
+ context ".register_typecaster" do
8
+
9
+ it "should be able to define a typecaster given a name and a module/class" do
10
+ caster = Module.new do
11
+ def self.from_api(value)
12
+ return "hello"
13
+ end
14
+ def self.to_api(value)
15
+ end
16
+ end
17
+ klass.register_typecaster(:String1, caster)
18
+ klass.typecasters[:string1].from_api(1).should eql("hello")
19
+ end
20
+
21
+ it "should be able to define a typecaster given a block" do
22
+ klass.register_typecaster(:String2) do
23
+ def self.from_api(value)
24
+ return "hello"
25
+ end
26
+ def self.to_api(value)
27
+ end
28
+ end
29
+
30
+ klass.typecasters[:string2].from_api(1).should eql("hello")
31
+ end
32
+
33
+ it "should raise an error if given both a class and a block" do
34
+ caster = Module.new do
35
+ def self.from_api(value)
36
+ return "hello"
37
+ end
38
+ def self.to_api(value)
39
+ end
40
+ end
41
+
42
+ lambda {
43
+ klass.register_typecaster(:String3, caster) do
44
+ def self.from_api(value)
45
+ return "hello"
46
+ end
47
+ def self.to_api(value)
48
+ end
49
+ end
50
+ }.should raise_error ArgumentError, "Cannot declare a typecaster with a class and a block"
51
+ end
52
+
53
+ it "should raise an error if given neither a class nor a block" do
54
+ lambda {
55
+ klass.register_typecaster(:String4)
56
+ }.should raise_error ArgumentError, "Must specify a typecaster with either a class or a block"
57
+ end
58
+
59
+ it "should raise an error if the typecaster already exists" do
60
+ klass.register_typecaster(:String5) do
61
+ def self.from_api(value)
62
+ return "hello"
63
+ end
64
+ def self.to_api(value)
65
+ end
66
+ end
67
+ lambda {
68
+ klass.register_typecaster(:String5) do
69
+ def self.from_api(value)
70
+ return "goodbye"
71
+ end
72
+ def self.to_api(value)
73
+ end
74
+ end
75
+ }.should raise_error ArgumentError, "Typecaster String5 already exists"
76
+ end
77
+
78
+ it "should raise an error if the klass does not respond_to? from_api" do
79
+ lambda {
80
+ klass.register_typecaster(:String6) do
81
+
82
+ end
83
+ }.should raise_error ArgumentError, "Typecaster must respond to from_api and to_api"
84
+ end
85
+ end
86
+
87
+ context "redefine_typecaster!" do
88
+ it "should be able to reregister a typecaster" do
89
+ klass.register_typecaster(:String7) do
90
+ def self.from_api(value)
91
+ return "hello"
92
+ end
93
+ def self.to_api(value)
94
+ end
95
+ end
96
+ klass.typecasters[:string7].from_api(1).should eql("hello")
97
+ klass.redefine_typecaster!(:String7) do
98
+ def self.from_api(value)
99
+ return "goodbye"
100
+ end
101
+ def self.to_api(value)
102
+ end
103
+ end
104
+
105
+ klass.typecasters[:string7].from_api(1).should eql("goodbye")
106
+ end
107
+
108
+ it "should redefine a typecaster only for subclasses of the class called on" do
109
+ sib1 = Class.new(ApiResource::Base)
110
+ sib2 = Class.new(ApiResource::Base)
111
+ child = Class.new(sib2)
112
+ # overriding a typecaster in sib2 should affect sib2 and child but not sib1
113
+ sib2.redefine_typecaster!(:string) do
114
+ def self.from_api(value)
115
+ return "hello"
116
+ end
117
+
118
+ def self.to_api(value)
119
+ return value
120
+ end
121
+ end
122
+
123
+ sib1.typecasters[:string].from_api(1).should eql("1")
124
+ ApiResource::Base.typecasters[:string].from_api(2).should eql("2")
125
+ sib2.typecasters[:string].from_api(1).should eql("hello")
126
+ child.typecasters[:string].from_api(1).should eql("hello")
127
+ end
128
+
129
+ it "should redefine a typecaster for all subclasses when called on ApiResource::Base" do
130
+ grandparent = Class.new(ApiResource::Base)
131
+ parent = Class.new(grandparent)
132
+ child = Class.new(parent)
133
+
134
+ grandparent.redefine_typecaster!(:string) do
135
+ def self.from_api(value)
136
+ return "goodbye"
137
+ end
138
+
139
+ def self.to_api(value)
140
+ return value
141
+ end
142
+ end
143
+
144
+ grandparent.typecasters[:string].from_api(2).should eql("goodbye")
145
+ parent.typecasters[:string].from_api(2).should eql("goodbye")
146
+ child.typecasters[:string].from_api(2).should eql("goodbye")
147
+ end
148
+
149
+ it "should be able to restore default typecasters" do
150
+ ApiResource::Base.redefine_typecaster!(:string) do
151
+ def self.from_api(value)
152
+ return "goodbye"
153
+ end
154
+
155
+ def self.to_api(value)
156
+ return value
157
+ end
158
+ end
159
+ ApiResource::Base.typecasters[:string].from_api(2).should eql("goodbye")
160
+ ApiResource::Base.redefine_typecaster!(:string, ApiResource::Base.default_typecasters[:string])
161
+ ApiResource::Base.typecasters[:string].from_api(2).should eql("2")
162
+ end
163
+ end
164
+
165
+ context ".typecasters" do
166
+ it "should have default typecasters" do
167
+ vals = [:boolean, :bool, :date, :decimal, :float, :integer,
168
+ :int, :string, :time, :datetime, :array]
169
+ vals.each do |val|
170
+ klass.typecasters.keys.should include val
171
+ end
172
+ end
173
+ end
174
+ end