accord 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +7 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +7 -0
  4. data/Gemfile.lock +43 -0
  5. data/accord.gemspec +20 -0
  6. data/lib/accord.rb +4 -0
  7. data/lib/accord/adapter_registry.rb +106 -0
  8. data/lib/accord/base_registry.rb +73 -0
  9. data/lib/accord/declarations.rb +131 -0
  10. data/lib/accord/exceptions.rb +22 -0
  11. data/lib/accord/extendor.rb +36 -0
  12. data/lib/accord/extendor_container.rb +43 -0
  13. data/lib/accord/interface.rb +189 -0
  14. data/lib/accord/interface_body.rb +73 -0
  15. data/lib/accord/interface_members.rb +31 -0
  16. data/lib/accord/interface_method.rb +27 -0
  17. data/lib/accord/interfaces.rb +471 -0
  18. data/lib/accord/nested_key_hash.rb +66 -0
  19. data/lib/accord/ro.rb +65 -0
  20. data/lib/accord/signature_info.rb +76 -0
  21. data/lib/accord/specification.rb +83 -0
  22. data/lib/accord/subscription_registry.rb +76 -0
  23. data/lib/accord/tags.rb +25 -0
  24. data/lib/accord/version.rb +3 -0
  25. data/spec/adapter_registry_spec.rb +296 -0
  26. data/spec/declarations_spec.rb +144 -0
  27. data/spec/extendor_container_spec.rb +101 -0
  28. data/spec/extendor_spec.rb +203 -0
  29. data/spec/integration/adaptation_spec.rb +86 -0
  30. data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
  31. data/spec/integration/adapter_hook_spec.rb +41 -0
  32. data/spec/integration/default_adapters_spec.rb +81 -0
  33. data/spec/integration/hash_adapters_spec.rb +20 -0
  34. data/spec/integration/interface_declaration_spec.rb +258 -0
  35. data/spec/integration/multi_adapters_spec.rb +83 -0
  36. data/spec/integration/named_adapters_spec.rb +54 -0
  37. data/spec/integration/single_adapters_spec.rb +93 -0
  38. data/spec/integration/subscriptions_spec.rb +245 -0
  39. data/spec/integration/verification_spec.rb +215 -0
  40. data/spec/interface_body_spec.rb +157 -0
  41. data/spec/interface_members_spec.rb +57 -0
  42. data/spec/interface_spec.rb +147 -0
  43. data/spec/nested_key_hash_spec.rb +140 -0
  44. data/spec/signature_info_spec.rb +65 -0
  45. data/spec/spec_helper.rb +2 -0
  46. data/spec/specification_spec.rb +246 -0
  47. data/spec/subscription_registry_spec.rb +206 -0
  48. data/spec/tags_spec.rb +38 -0
  49. metadata +134 -0
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ describe "Adapter for class declaration" do
5
+ let(:ip1) { Accord::InterfaceClass.new(:ip1, [Accord::Interface]) }
6
+ let(:registry) { Accord::AdapterRegistry.new }
7
+
8
+ context "given a class claiming to implement an interface" do
9
+ let(:cls) { Class.new }
10
+ before { Accord::Declarations.implements(cls, ip1) }
11
+
12
+ context "and a registration using the class declaration" do
13
+ let(:declaration) { Accord::Declarations.implemented_by(cls) }
14
+
15
+ before { registry.register([declaration], ip1, '') { 'value' } }
16
+
17
+ it "returns that when the claimed interface is used for lookup" do
18
+ expect(registry.lookup([declaration], ip1).call).to eq 'value'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ module Accord
5
+ describe "Adapter hook" do
6
+ before do
7
+ Accord.install_default_adapter_hook
8
+ end
9
+
10
+ after do
11
+ Accord.clear_adapter_hooks
12
+ Accord.clear_default_adapter_hook
13
+ end
14
+
15
+ let(:itarget) { InterfaceClass.new(:itarget, [Interface]) }
16
+ let(:registry) { Accord.default_adapter_registry }
17
+
18
+ it "makes interfaces adapt using the default adapter hook" do
19
+ ir = InterfaceClass.new(:ir, [Interface])
20
+ registry.register([ir], itarget) { 'adapted' }
21
+
22
+ obj = Object.new
23
+ Accord::Declarations.also_provides(obj, ir)
24
+
25
+ expect(itarget.adapt(obj)).to eq 'adapted'
26
+ end
27
+
28
+ it "also enables multi adaptation from interfaces" do
29
+ ir1 = InterfaceClass.new(:ir1, [Interface])
30
+ ir2 = InterfaceClass.new(:ir2, [Interface])
31
+ registry.register([ir1, ir2], itarget) { 'adapted' }
32
+
33
+ obj1 = Object.new
34
+ obj2 = Object.new
35
+ Declarations.also_provides(obj1, ir1)
36
+ Declarations.also_provides(obj2, ir2)
37
+
38
+ expect(itarget.adapt(obj1, obj2)).to eq 'adapted'
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ describe "Default adapters" do
5
+ let(:registry) { Accord::AdapterRegistry.new }
6
+ let(:ip1) { Accord::InterfaceClass.new(:ip1, [Accord::Interface]) }
7
+ let(:ir1) { Accord::InterfaceClass.new(:ir1, [Accord::Interface]) }
8
+
9
+ before do
10
+ stub_const('IP1', ip1)
11
+ stub_const('IR1', ir1)
12
+ end
13
+
14
+ context "single" do
15
+ before do
16
+ registry.register([nil], IP1, '') { 1 }
17
+ end
18
+
19
+ context "for interfaces we don't have specific adapters for" do
20
+ let(:iq) { Accord::InterfaceClass.new(:iq, [Accord::Interface]) }
21
+
22
+ it "finds the default adapter" do
23
+ expect(registry.lookup([iq], IP1, '').call).to eq 1
24
+ end
25
+ end
26
+
27
+ context "when a specific adapter is also registered" do
28
+ let(:ir1) { Accord::InterfaceClass.new(:ir1, [Accord::Interface]) }
29
+
30
+ before do
31
+ registry.register([ir1], IP1, '') { 2 }
32
+ end
33
+
34
+ it "overrides the default" do
35
+ expect(registry.lookup([ir1], IP1, '').call).to eq 2
36
+ end
37
+ end
38
+ end
39
+
40
+ context "multi" do
41
+ before do
42
+ registry.register([nil, IR1], IP1, '') { 1 }
43
+ end
44
+
45
+ context "for interfaces we don't have specific adapters for" do
46
+ let(:iq) { Accord::InterfaceClass.new(:iq, [Accord::Interface]) }
47
+
48
+ it "finds the default adapter" do
49
+ expect(registry.lookup([iq, IR1], IP1, '').call).to eq 1
50
+ end
51
+ end
52
+
53
+ context "when a specific adapter is also registered" do
54
+ let(:ir2) { Accord::InterfaceClass.new(:ir2, [Accord::Interface]) }
55
+
56
+ before do
57
+ registry.register([ir2, IR1], IP1, '') { 2 }
58
+ end
59
+
60
+ it "overrides the default" do
61
+ expect(registry.lookup([ir2, IR1], IP1, '').call).to eq 2
62
+ end
63
+ end
64
+ end
65
+
66
+ context "null" do
67
+ let(:ip2) { Accord::InterfaceClass.new(:ip2, [ip1]) }
68
+
69
+ before do
70
+ registry.register([], ip2, '') { 'null adapter' }
71
+ end
72
+
73
+ it "also can adapt no specification" do
74
+ expect(registry.lookup([], ip2, '').call).to eq 'null adapter'
75
+ end
76
+
77
+ it "will take into consideration any extending interface" do
78
+ expect(registry.lookup([], IP1, '').call).to eq 'null adapter'
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ describe "Hash adapters" do
5
+ let(:ip) { Accord::InterfaceClass.new(:ip, [Accord::Interface]) }
6
+ let(:registry) { Accord::AdapterRegistry.new }
7
+
8
+ context "given a hash" do
9
+ let(:hash) { Hash.new }
10
+
11
+ context "and a registration for it using no required interfaces" do
12
+ before { registry.register([], ip, '') { hash } }
13
+
14
+ it "returns that when the lookup is made without required interfaces "\
15
+ "for the right provided interface" do
16
+ expect(registry.lookup([], ip).call).to be hash
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ describe "Accord::Interface method" do
5
+ let(:mod) { Module.new }
6
+ let(:not_constant) do
7
+ Accord::Interface(:NotConstant) do
8
+ responds_to :method
9
+ end
10
+ end
11
+
12
+ before do
13
+ stub_const('TestModule', mod)
14
+
15
+ Accord::Interface(TestModule, :Base) do
16
+ responds_to :method1
17
+
18
+ invariant :invariant do |ob, errors|
19
+ ob.base_invariant_ran
20
+ errors << :base if ob.set_error
21
+ :ignore_return
22
+ end
23
+
24
+ tags[:tag] = :base
25
+ interface[:method1].tags[:tag] = :value
26
+ end
27
+
28
+ Accord::Interface(TestModule, :Extension) do
29
+ extends TestModule::Base
30
+
31
+ responds_to :method2, params: [{ param: :default }]
32
+
33
+ invariant :invariant do |ob, errors|
34
+ ob.extension_invariant_ran
35
+ errors << :extension if ob.set_error
36
+ :ignore_return
37
+ end
38
+
39
+ invariant :exclusive do |ob, errors|
40
+ ob.exclusive_invariant_ran
41
+ errors << :extension if ob.set_error
42
+ :ignore_return
43
+ end
44
+ end
45
+
46
+ Accord::Interface(TestModule, :OtherExtension) do
47
+ extends TestModule::Base
48
+
49
+ responds_to :method1 do
50
+ param :argument
51
+ end
52
+
53
+ responds_to :method2, params: { param: :default }
54
+ responds_to :method3, params: :argument
55
+ end
56
+ end
57
+
58
+ it "can be declared without a block" do
59
+ Accord::Interface(TestModule, :Marker)
60
+ expect(TestModule::Marker).to be_a(Accord::InterfaceClass)
61
+ end
62
+
63
+ describe "not constant" do
64
+ it "has a simpler name" do
65
+ expect(not_constant.name).to eq 'NotConstant'
66
+ end
67
+ end
68
+
69
+ describe "Base" do
70
+ it "has a name" do
71
+ expect(TestModule::Base.name).to eq 'TestModule::Base'
72
+ end
73
+
74
+ it "has :method1" do
75
+ expect(TestModule::Base).to be_defined(:method1)
76
+ expect(TestModule::Base).to be_owns(:method1)
77
+ end
78
+
79
+ specify "base's method can be obtained by calling #[]" do
80
+ expect(TestModule::Base[:method1].interface).to be TestModule::Base
81
+ end
82
+
83
+ it " has no method :method2" do
84
+ expect(TestModule::Base).to_not be_defined(:method2)
85
+ expect(TestModule::Base).to_not be_owns(:method2)
86
+ end
87
+
88
+ it "has a single method" do
89
+ expect(TestModule::Base.member_names).to eq [:method1]
90
+ expect(TestModule::Base.own_member_names).to eq [:method1]
91
+ end
92
+
93
+ it "iterates only over :method1" do
94
+ names = []
95
+ members = []
96
+
97
+ TestModule::Base.each do |name, member|
98
+ names << name
99
+ members << member
100
+ end
101
+
102
+ expect(names).to eq [:method1]
103
+ expect(members.map(&:interface)).to eq [TestModule::Base]
104
+ end
105
+
106
+ specify ":method1 doesn't require a parameter" do
107
+ expect(TestModule::Base[:method1].signature_info.arguments).to be_empty
108
+ end
109
+
110
+ it "has a tagged value" do
111
+ expect(TestModule::Base.tags[:tag]).to eq :base
112
+ end
113
+
114
+ it "can have tags on methods" do
115
+ expect(TestModule::Base[:method1].tags[:tag]).to eq :value
116
+ end
117
+
118
+ describe "invariants" do
119
+ let(:ob) { stub(base_invariant_ran: nil, set_error: false) }
120
+
121
+ it "runs only one invariant" do
122
+ ob.should_receive(:base_invariant_ran)
123
+ TestModule::Base.assert_invariants?(ob)
124
+ end
125
+
126
+ it "returns true if no error is set" do
127
+ result = TestModule::Base.assert_invariants?(ob)
128
+ expect(result).to be_true
129
+ end
130
+
131
+ it "returns false if an error is set" do
132
+ ob.stub(set_error: true)
133
+ result = TestModule::Base.assert_invariants?(ob)
134
+ expect(result).to be_false
135
+ end
136
+
137
+ it "collects errors" do
138
+ ob.stub(set_error: true)
139
+ errors = []
140
+ TestModule::Base.assert_invariants?(ob, errors)
141
+ expect(errors).to eq [:base]
142
+ end
143
+
144
+ describe "with exception" do
145
+ it "raise error if invariant set error" do
146
+ ob.stub(set_error: true)
147
+ expect {
148
+ TestModule::Base.assert_invariants(ob)
149
+ }.to raise_error(Accord::Invalid)
150
+ end
151
+
152
+ it "collects errors before raising" do
153
+ ob.stub(set_error: true)
154
+ errors = []
155
+ TestModule::Base.assert_invariants(ob, errors) rescue nil
156
+
157
+ expect(errors).to eq [:base]
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "Extension" do
164
+ it "has :method1" do
165
+ expect(TestModule::Extension.member_names).to include(:method1)
166
+ expect(TestModule::Extension).to be_defined(:method1)
167
+ end
168
+
169
+ it "doesn't owns :method1" do
170
+ expect(TestModule::Extension).to_not be_owns(:method1)
171
+ end
172
+
173
+ it "has :method2" do
174
+ expect(TestModule::Extension.member_names).to include(:method1)
175
+ expect(TestModule::Extension).to be_defined(:method2)
176
+ end
177
+
178
+ it "owns :method2" do
179
+ expect(TestModule::Extension).to be_owns(:method2)
180
+ end
181
+
182
+ it "has :method1 and :method2 defined in this order" do
183
+ expect(TestModule::Extension.member_names).to eq [:method1, :method2]
184
+ end
185
+
186
+ it "has :method2 defined in itself" do
187
+ expect(TestModule::Extension.own_member_names).to eq [:method2]
188
+ end
189
+
190
+ specify ":method2 requires a parameter with a default value" do
191
+ expect(TestModule::Extension[:method2].signature_info.arguments).to(
192
+ eq([{ name: :param, default: :default }])
193
+ )
194
+ end
195
+
196
+ specify "tags are not inherited" do
197
+ expect(TestModule::Extension.tags[:tag]).to be_nil
198
+ end
199
+
200
+ specify "tags on methods are inherited if the method is inherited" do
201
+ expect(TestModule::Extension[:method1].tags[:tag]).to eq :value
202
+ end
203
+
204
+ describe "invariants" do
205
+ let(:ob) do
206
+ stub(
207
+ set_error: false,
208
+ base_invariant_ran: nil,
209
+ extension_invariant_ran: nil,
210
+ exclusive_invariant_ran: nil,
211
+ )
212
+ end
213
+
214
+ it "runs invariants from base" do
215
+ ob.should_receive(:base_invariant_ran)
216
+ TestModule::Extension.assert_invariants?(ob)
217
+ end
218
+
219
+ it "runs invariants with the same name as the ones from base" do
220
+ ob.should_receive(:extension_invariant_ran)
221
+ TestModule::Extension.assert_invariants?(ob)
222
+ end
223
+
224
+ it "runs invariants included only in extension" do
225
+ ob.should_receive(:exclusive_invariant_ran)
226
+ TestModule::Extension.assert_invariants?(ob)
227
+ end
228
+ end
229
+ end
230
+
231
+ describe "OtherExtension" do
232
+ it "overrides :method1 to require an argument" do
233
+ expect(TestModule::OtherExtension[:method1].signature_info.arguments).to(
234
+ eq([{ name: :argument }])
235
+ )
236
+ end
237
+
238
+ it "keeps the original method as it was" do
239
+ expect(TestModule::Base[:method1].signature_info.arguments).to be_empty
240
+ end
241
+
242
+ specify "tags on methods are not inherited if the method is overridden" do
243
+ expect(TestModule::OtherExtension[:method1].tags[:tag]).to be_nil
244
+ end
245
+
246
+ specify ":method2 has an optional parameter" do
247
+ expect(TestModule::OtherExtension[:method2].signature_info.arguments).to(
248
+ eq([{ name: :param, default: :default }])
249
+ )
250
+ end
251
+
252
+ specify ":method3 has a single required parameter" do
253
+ expect(TestModule::OtherExtension[:method3].signature_info.arguments).to(
254
+ eq([{ name: :argument }])
255
+ )
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ describe "Multi adapters" do
5
+ let(:ip) { Accord::InterfaceClass.new(:ip, [Accord::Interface]) }
6
+ let(:ir1) { Accord::InterfaceClass.new(:ir1, [Accord::Interface]) }
7
+ let(:ir2) { Accord::InterfaceClass.new(:ir2, [Accord::Interface]) }
8
+
9
+ let(:registry) { Accord::AdapterRegistry.new }
10
+
11
+ before do
12
+ stub_const('IR1', ir1)
13
+ stub_const('IR2', ir2)
14
+ end
15
+
16
+ context "given a factory requiring more than one parameter" do
17
+ let(:factory) { Proc.new { |o1, o2| 'factory result' } }
18
+
19
+ context "and a registered adapter for those discriminators" do
20
+ before do
21
+ registry.register([ir1, ir2], ip, '') { 'adapter' }
22
+ end
23
+
24
+ context "when a query is made for objects providing those "\
25
+ "required interfaces" do
26
+ let(:cls1) { Class.new { Accord::Declarations.implements(self, IR1) } }
27
+ let(:cls2) { Class.new { Accord::Declarations.implements(self, IR2) } }
28
+
29
+ let(:r1) { cls1.new }
30
+ let(:r2) { cls2.new }
31
+
32
+ subject { registry.get([r1, r2], ip, '') }
33
+
34
+ it { should eq 'adapter' }
35
+ end
36
+
37
+ context "when a query is made for objects providing those "\
38
+ "required interfaces with the wrong name" do
39
+ let(:cls1) { Class.new { Accord::Declarations.implements(self, IR1) } }
40
+ let(:cls2) { Class.new { Accord::Declarations.implements(self, IR2) } }
41
+
42
+ let(:r1) { cls1.new }
43
+ let(:r2) { cls2.new }
44
+
45
+ subject { registry.get([r1, r2], ip, 'not unamed') }
46
+
47
+ it { should be_nil }
48
+ end
49
+ end
50
+
51
+ context "and a named registered adapter for those discriminators" do
52
+ before do
53
+ registry.register([ir1, ir2], ip, 'bob') { 'adapter' }
54
+ end
55
+
56
+ context "when a query is made for objects providing those "\
57
+ "required interfaces with the right name" do
58
+ let(:cls1) { Class.new { Accord::Declarations.implements(self, IR1) } }
59
+ let(:cls2) { Class.new { Accord::Declarations.implements(self, IR2) } }
60
+
61
+ let(:r1) { cls1.new }
62
+ let(:r2) { cls2.new }
63
+
64
+ subject { registry.get([r1, r2], ip, 'bob') }
65
+
66
+ it { should eq 'adapter' }
67
+ end
68
+
69
+ context "when a query is made for objects providing those "\
70
+ "required interfaces with the wrong name (unamed)" do
71
+ let(:cls1) { Class.new { Accord::Declarations.implements(self, IR1) } }
72
+ let(:cls2) { Class.new { Accord::Declarations.implements(self, IR2) } }
73
+
74
+ let(:r1) { cls1.new }
75
+ let(:r2) { cls2.new }
76
+
77
+ subject { registry.get([r1, r2], ip, '') }
78
+
79
+ it { should be_nil }
80
+ end
81
+ end
82
+ end
83
+ end