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.
- data/.document +5 -0
- data/.gitignore +55 -0
- data/.rspec +8 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -36
- data/Gemfile.lock +37 -57
- data/Guardfile +7 -1
- data/{LICENSE.txt → LICENSE} +4 -2
- data/README.md +29 -0
- data/README.rdoc +5 -1
- data/Rakefile +5 -36
- data/api_resource.gemspec +40 -104
- data/lib/api_resource.rb +4 -1
- data/lib/api_resource/associations.rb +30 -14
- data/lib/api_resource/associations/association_proxy.rb +97 -0
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +0 -1
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +0 -1
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +7 -4
- data/lib/api_resource/associations/multi_object_proxy.rb +11 -16
- data/lib/api_resource/associations/single_object_proxy.rb +8 -6
- data/lib/api_resource/attributes.rb +29 -41
- data/lib/api_resource/base.rb +31 -102
- data/lib/api_resource/conditions.rb +36 -0
- data/lib/api_resource/conditions/abstract_condition.rb +112 -0
- data/lib/api_resource/conditions/association_condition.rb +18 -0
- data/lib/api_resource/conditions/include_condition.rb +16 -0
- data/lib/api_resource/conditions/multi_object_association_condition.rb +17 -0
- data/lib/api_resource/conditions/scope_condition.rb +11 -0
- data/lib/api_resource/conditions/single_object_association_condition.rb +19 -0
- data/lib/api_resource/connection.rb +18 -12
- data/lib/api_resource/finders.rb +122 -0
- data/lib/api_resource/finders/abstract_finder.rb +89 -0
- data/lib/api_resource/finders/multi_object_association_finder.rb +39 -0
- data/lib/api_resource/finders/resource_finder.rb +33 -0
- data/lib/api_resource/finders/single_object_association_finder.rb +40 -0
- data/lib/api_resource/observing.rb +19 -3
- data/lib/api_resource/scopes.rb +3 -3
- data/lib/api_resource/typecast.rb +85 -0
- data/lib/api_resource/typecasters/array_typecaster.rb +19 -0
- data/lib/api_resource/typecasters/boolean_typecaster.rb +22 -0
- data/lib/api_resource/typecasters/date_typecaster.rb +35 -0
- data/lib/api_resource/typecasters/float_typecaster.rb +22 -0
- data/lib/api_resource/typecasters/integer_typecaster.rb +22 -0
- data/lib/api_resource/typecasters/string_typecaster.rb +19 -0
- data/lib/api_resource/typecasters/time_typecaster.rb +44 -0
- data/lib/api_resource/version.rb +3 -0
- data/spec/lib/api_resource_spec.rb +6 -0
- data/spec/lib/associations/association_scope_spec.rb +1 -1
- data/spec/lib/associations_spec.rb +17 -50
- data/spec/lib/base_spec.rb +22 -0
- data/spec/lib/conditions/abstract_conditions_spec.rb +78 -0
- data/spec/lib/connection_spec.rb +19 -0
- data/spec/lib/finders/multi_object_association_finder_spec.rb +43 -0
- data/spec/lib/finders/resource_finder_spec.rb +89 -0
- data/spec/lib/finders/single_object_association_finder_spec.rb +43 -0
- data/spec/lib/observing_spec.rb +96 -0
- data/spec/lib/typecast_spec.rb +174 -0
- data/spec/lib/typecasters/boolean_typecaster_spec.rb +33 -0
- data/spec/lib/typecasters/date_typecaster_spec.rb +62 -0
- data/spec/lib/typecasters/float_typecaster_spec.rb +39 -0
- data/spec/lib/typecasters/integer_typecaster_spec.rb +40 -0
- data/spec/lib/typecasters/string_typecaster_spec.rb +28 -0
- data/spec/lib/typecasters/time_typecaster_spec.rb +65 -0
- data/spec/spec_helper.rb +0 -1
- metadata +134 -194
- data/VERSION +0 -1
- data/coverage/assets/0.5.3/app.js +0 -88
- 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 +0 -363
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +0 -44
- 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 +0 -129
- data/coverage/assets/0.5.3/highlight.pack.js +0 -1
- data/coverage/assets/0.5.3/jquery-1.6.2.min.js +0 -18
- data/coverage/assets/0.5.3/jquery.dataTables.min.js +0 -152
- data/coverage/assets/0.5.3/jquery.timeago.js +0 -141
- data/coverage/assets/0.5.3/jquery.url.js +0 -174
- 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 +0 -295
- data/coverage/assets/0.5.3/stylesheet.css +0 -383
- data/coverage/assets/0.7.1/application.css +0 -1110
- data/coverage/assets/0.7.1/application.js +0 -626
- data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.7.1/favicon_green.png +0 -0
- data/coverage/assets/0.7.1/favicon_red.png +0 -0
- data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
- data/coverage/assets/0.7.1/loading.gif +0 -0
- data/coverage/assets/0.7.1/magnify.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +0 -3564
- data/lib/api_resource/associations/abstract_scope.rb +0 -191
- data/lib/api_resource/associations/association_scope.rb +0 -47
- data/lib/api_resource/associations/resource_scope.rb +0 -21
- data/lib/api_resource/associations/scope.rb +0 -34
- data/spec/lib/associations/resource_scope_spec.rb +0 -24
- data/spec/tmp/api_resource_test_db.sqlite +0 -0
- data/tmp/rspec_guard_result +0 -1
data/spec/lib/connection_spec.rb
CHANGED
|
@@ -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
|