accord 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 (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