decorum 0.0.1

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.
@@ -0,0 +1,20 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorations
4
+ class ThirdDecorator < Decorum::Decorator
5
+ share :shared_attribute
6
+ attr_accessor :local_attribute
7
+
8
+ def third_decorator_method
9
+ "third"
10
+ end
11
+
12
+ alias_method :current_decorator_method, :third_decorator_method
13
+
14
+ def respect_previously_defined_methods?
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class BasicDecorator < Decorum::Decorator
5
+ default_attributes first_default: "default value", overridden_default: false
6
+
7
+ share :shared_attribute, :unused_shared_attribute
8
+ attr_accessor :name, :unused_personal_attribute
9
+
10
+ def basic_decorator_method
11
+ true
12
+ end
13
+
14
+ def respect_previously_defined_methods?
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class DecoratedObjectStub
5
+ include Decorum::Decorations
6
+
7
+ def respect_previously_defined_methods?
8
+ true
9
+ end
10
+
11
+ def decorated_state(*args)
12
+ @decorated_state ||= Decorum::Spec::Decorator::DecoratedStateStub.new
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class DecoratedStateStub < Decorum::DecoratedState
5
+ def reaching_its_destination?
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class DecoratorStub
5
+ def method_missing(*args)
6
+ throw :deferred, self
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Decorum::BareParticular do
4
+ let(:bp) { Decorum::BareParticular.new }
5
+
6
+ it 'is decoratable' do
7
+ expect(bp).to be_a(Decorum::Decorations)
8
+ end
9
+
10
+ it 'black holes undefined methods' do
11
+ expect(bp.nonexistent_method).to be_nil
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Decorum::ChainStop do
4
+ let(:chainstop) { Decorum::ChainStop.new }
5
+
6
+ it 'throws self via :chain_stop on undefined method' do
7
+ response = catch :chain_stop do
8
+ chainstop.nonexistent_method
9
+ end
10
+ expect(response).to be_equal(chainstop)
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Decorum::DecoratedState do
4
+ let(:ds) { Decorum::DecoratedState.new(foo: "bar") }
5
+
6
+ it 'assigns initialized options' do
7
+ expect(ds.foo).to eq("bar")
8
+ end
9
+
10
+ context 'when forwarding messages' do
11
+ before(:each) do
12
+ # need to pop it open first...
13
+ fake_shared_state = Decorum::Spec::DecoratedState::SharedStateStub.new
14
+ ds.instance_variable_set(:@shared_state, fake_shared_state)
15
+ end
16
+
17
+ it 'forwards getter methods' do
18
+ expect(ds.marker).to eq("retrieved")
19
+ end
20
+
21
+ it 'forwards setter methods' do
22
+ expect(ds.send(:"marker=", 'foo')).to eq('assigned')
23
+ end
24
+
25
+ describe '#respond_to?' do
26
+ it 'is false for forwarded messages' do
27
+ expect(ds.respond_to?(:marker)).to be_false
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,233 @@
1
+ require 'spec_helper'
2
+
3
+ describe Decorum::Decorations do
4
+ let(:decorated) { Decorum::Spec::Decorations::DecoratedObjectStub.new }
5
+ let(:deco_class_1) { Decorum::Spec::Decorations::FirstDecorator }
6
+ let(:deco_class_2) { Decorum::Spec::Decorations::SecondDecorator }
7
+ let(:deco_class_3) { Decorum::Spec::Decorations::ThirdDecorator }
8
+
9
+
10
+ context 'as-yet-undecorated' do
11
+ # assert some basic assumptions
12
+ it 'is decoratable' do
13
+ expect(decorated.is_a?(Decorum::Decorations)).to be_true
14
+ end
15
+
16
+ it 'does not respond to first decorator test method' do
17
+ expect(decorated.respond_to?(:first_decorator_method)).to be_false
18
+ end
19
+
20
+ it 'responds to its own methods' do
21
+ expect(decorated.undecorated_method).to be_true
22
+ end
23
+
24
+ describe '#decorated_state' do
25
+ it 'returns a hash' do
26
+ blank_state = decorated.decorated_state
27
+ expect(blank_state.is_a?(Hash)).to be_true
28
+ end
29
+
30
+ it 'returns an empty hash' do
31
+ blank_state = decorated.decorated_state
32
+ expect(blank_state.empty?).to be_true
33
+ end
34
+
35
+ it 'returns nil given an argument' do
36
+ expect(decorated.decorated_state(:not_gonna_work)).to be_nil
37
+ end
38
+ end
39
+
40
+ describe 'undecorate' do
41
+ it 'returns self on symbol' do
42
+ expect(decorated.undecorate(:symbol_arg)).to be_equal(decorated)
43
+ end
44
+
45
+ it 'returns self on spurious Decorator' do
46
+ expect(decorated.undecorate(deco_class_1)).to be_equal(decorated)
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'decorated' do
52
+ describe '#decorate' do
53
+ it 'returns self on decoration' do
54
+ real_decorated = decorated
55
+ expect(decorated.decorate(deco_class_1)).to be_equal(real_decorated)
56
+ end
57
+
58
+ it 'yields decorator if block_given?' do
59
+ decorator = nil
60
+ decorated.decorate(deco_class_1) { |dec| decorator = dec }
61
+ actual_decorator = decorated.instance_variable_get(:@_decorator_chain)
62
+ expect(decorator).to be(actual_decorator)
63
+ end
64
+
65
+ context 'success' do
66
+ before(:each) do
67
+ decorator_options = { decorator_handle: "first", shared_attribute: "shared 1", local_attribute: "local 1" }
68
+ decorated.decorate(deco_class_1, decorator_options)
69
+ end
70
+
71
+ it 'installs intercept' do
72
+ expect(decorated.is_a?(Decorum::Decorations::Intercept)).to be_true
73
+ end
74
+
75
+ it 'sets internal state' do
76
+ internal_state = decorated.instance_variable_get(:@_decorator_chain)
77
+ expect(internal_state.is_a?(deco_class_1)).to be_true
78
+ end
79
+
80
+ it 'does not lose its own methods' do
81
+ expect(decorated.respect_previously_defined_methods?).to be_true
82
+ end
83
+
84
+ it 'gains the methods of the decorator' do
85
+ expect(decorated.first_decorator_method).to eq('first')
86
+ end
87
+
88
+ it 'raises NoMethodError on nonexistent method' do
89
+ expect { decorated.nonexistent_method }.to raise_error(NoMethodError)
90
+ end
91
+
92
+ it 'is decorated by local attributes' do
93
+ expect(decorated.local_attribute).to eq("local 1")
94
+ end
95
+
96
+ it 'is decorated by shared attributes' do
97
+ expect(decorated.shared_attribute).to eq("shared 1")
98
+ end
99
+
100
+ it 'creates decorated state for decorator class' do
101
+ expect(decorated.decorated_state(deco_class_1).shared_attribute).to eq('shared 1')
102
+ end
103
+
104
+ context 'with multiple decorators' do
105
+ before(:each) do
106
+ 3.times do |i|
107
+ klass = send(:"deco_class_#{i + 1}")
108
+ decorated.decorate(klass)
109
+ end
110
+ end
111
+
112
+ it 'retains decorated methods' do
113
+ responses = [:first, :second, :third].map do |prefix|
114
+ decorated.send(:"#{prefix}_decorator_method")
115
+ end
116
+ expect(responses).to eq(["first", "second", "third"])
117
+ end
118
+
119
+ it 'prefers the most recent decorator' do
120
+ expect(decorated.current_decorator_method).to eq("third")
121
+ end
122
+ end
123
+ end
124
+
125
+ context 'failure' do
126
+ it 'rejects classes that are not Decorators' do
127
+ expect { decorated.decorate(Class,{}) }.to raise_error(RuntimeError)
128
+ end
129
+
130
+ it 'rejects non unique decorator handles' do
131
+ 3.times do |i|
132
+ klass = send(:"deco_class_#{i + 1}")
133
+ decorated.decorate(klass, decorator_handle: "deco-#{i + 1}")
134
+ end
135
+ expect { decorated.decorate(deco_class_1, decorator_handle: "deco-2") }.to raise_error(RuntimeError)
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#respond_to?' do
141
+ before(:each) { decorated.decorate(deco_class_1) }
142
+
143
+ it 'returns true for decorated methods' do
144
+ expect(decorated.respond_to?(:first_decorator_method)).to be_true
145
+ end
146
+
147
+ it 'returns true for undecorated methods' do
148
+ expect(decorated.respond_to?(:undecorated_method)).to be_true
149
+ end
150
+
151
+ it 'returns false for undefined method' do
152
+ expect(decorated.respond_to?(:nonexistent_method)).to be_false
153
+ end
154
+ end
155
+
156
+ context 'monitoring and unloading' do
157
+ before(:each) do
158
+ 3.times do |x|
159
+ klass = send(:"deco_class_#{x + 1}")
160
+ decorated.decorate(klass, decorator_handle: "deco-#{x + 1}")
161
+ end
162
+ end
163
+
164
+ describe '#decorators' do
165
+ it 'accurately reflects loaded decorators and in order' do
166
+ expected_map = ["deco-3", "deco-2", "deco-1"]
167
+ expect(decorated.decorators.map { |d| d.decorator_handle }).to eq(expected_map)
168
+ end
169
+
170
+ it 'memoizes decorators' do
171
+ decorated.decorators[0].next_link = decorated.decorators[2]
172
+ expect(decorated.decorators.length).to be(3)
173
+ end
174
+
175
+ it 'refreshes via #decorators!' do
176
+ decorated.decorators[0].next_link = decorated.decorators[2]
177
+ decorated.send(:decorators!)
178
+ expect(decorated.decorators.length).to be(2)
179
+ end
180
+ end
181
+
182
+ describe '#undecorate' do
183
+ before(:each) do
184
+ @undec = decorated.decorators.detect { |d| d.decorator_handle == "deco-2" }
185
+ # just to make sure...
186
+ unless @undec.is_a?(Decorum::Decorator)
187
+ raise "broken test---no such decorator deco-2; undec was #{@undec.inspect}"
188
+ end
189
+ end
190
+
191
+ it 'undecorates' do
192
+ decorated.undecorate(@undec)
193
+ expect(decorated.decorators.length).to be(2)
194
+ end
195
+
196
+ it 'undecorates the right one' do
197
+ decorated.undecorate(@undec)
198
+ expected_map = ["deco-3", "deco-1"]
199
+ expect(decorated.decorators.map { |d| d.decorator_handle }).to eq(expected_map)
200
+ end
201
+
202
+ it 'returns self on success' do
203
+ real_decorated = decorated
204
+ expect(decorated.undecorate(@undec)).to be_equal(real_decorated)
205
+ end
206
+
207
+ context 'once undecorated' do
208
+ before(:each) { decorated.undecorate(@undec) }
209
+
210
+ it 'no longer responds to removed decorated method' do
211
+ expect(decorated.respond_to?(:second_decorator_method)).to be_false
212
+ end
213
+
214
+ it 'still responds to other decorated methods' do
215
+ expect(decorated.respond_to?(:third_decorator_method)).to be_true
216
+ end
217
+
218
+ it 'doesn\'t mind if we check one just to be sure' do
219
+ expect(decorated.first_decorator_method).to eq('first')
220
+ end
221
+
222
+ it 'destroys shared state when last class member is gone' do
223
+ expect(decorated.decorated_state(deco_class_2)).to be_nil
224
+ end
225
+
226
+ it 'does not destroy other shared state' do
227
+ expect(decorated.decorated_state(deco_class_1)).to be_a(Decorum::DecoratedState)
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ describe Decorum::Decorator do
4
+ let(:decorator) do
5
+ @next_link = Decorum::ChainStop.new
6
+ @root = Decorum::Spec::Decorator::DecoratedObjectStub.new
7
+ @options_for_decorator = { name: 'bob', shared_attribute: 'shared', overridden_default: true }
8
+ Decorum::Spec::Decorator::BasicDecorator.new(@next_link, @root, @options_for_decorator)
9
+ end
10
+
11
+ it 'responds to .share' do
12
+ expect(Decorum::Decorator.respond_to?(:share)).to be_true
13
+ end
14
+
15
+ it 'responds to .accumulator' do
16
+ expect(Decorum::Decorator.respond_to?(:accumulator)).to be_true
17
+ end
18
+
19
+ describe '#decorated_state' do
20
+ it 'defers to the root object' do
21
+ expect(decorator.decorated_state.reaching_its_destination?).to be_true
22
+ end
23
+ end
24
+
25
+ describe '#next_link' do
26
+ it 'returns the next decorator' do
27
+ expect(decorator.next_link).to be_equal(@next_link)
28
+ end
29
+ end
30
+
31
+ describe '#next_link=' do
32
+ it 'sets the next decorator' do
33
+ @new_next_link = Decorum::Spec::Decorator::DecoratorStub.new
34
+ decorator.next_link = @new_next_link
35
+ response = catch :deferred do
36
+ decorator.send(:nonexistent_method)
37
+ end
38
+
39
+ expect(response).to be_equal(@new_next_link)
40
+ end
41
+
42
+ describe '#root' do
43
+ it 'returns the root object' do
44
+ expect(decorator.root).to be_equal(@root)
45
+ end
46
+
47
+ it 'is aliased as #object' do
48
+ expect(decorator.object).to be_equal(@root)
49
+ end
50
+ end
51
+
52
+ describe '#
53
+
54
+ describe '#decorated_tail' do
55
+ let(:value) { "howdy" }
56
+
57
+ it 'catches :chain_stop' do
58
+ response = decorator.decorated_tail(value) { throw :chain_stop, "caught" }
59
+ expect(response).to eq("caught")
60
+ end
61
+
62
+ it 'preserves return value' do
63
+ response = decorator.decorated_tail(value) { decorator.send(:nonexistent_method) }
64
+ expect(response).to eq(value)
65
+ end
66
+ end
67
+
68
+ context 'when attributes are declared default' do
69
+ describe '#getter' do
70
+ it 'gets default value' do
71
+ expect(decorator.first_default).to eq("default value")
72
+ end
73
+
74
+ it 'is overridden by options passed to initialize' do
75
+ expect(decorator.overridden_default).to equal(true)
76
+ end
77
+ end
78
+
79
+ describe '#setter' do
80
+ it 'gets defined' do
81
+ decorator.first_default = "new value"
82
+ expect(decorator.first_default).to eq("new value")
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'when attributes are declared via share' do
88
+ describe '#setter' do
89
+ it 'does not set attribute locally' do
90
+ expect(decorator.instance_variable_get(:@shared_attribute)).to be_nil
91
+ end
92
+
93
+ it 'sets attribute in shared state' do
94
+ expect(decorator.decorated_state.shared_attribute).to eq('shared')
95
+ end
96
+ end
97
+
98
+ describe '#getter' do
99
+ it 'sets attribute' do
100
+ expect(decorator.shared_attribute).to eq('shared')
101
+ end
102
+
103
+ it 'gets nil for unused attribute' do
104
+ expect(decorator.unused_shared_attribute).to be_nil
105
+ end
106
+ end
107
+
108
+ describe '#boolean' do
109
+ it 'returns true on set attribute' do
110
+ expect(decorator.shared_attribute?).to equal(true)
111
+ end
112
+
113
+ it 'returns false on unset attribute' do
114
+ expect(decorator.unused_shared_attribute?).to equal(false)
115
+ end
116
+ end
117
+
118
+ describe '#resetter' do
119
+ before(:each) { decorator.reset_shared_attribute }
120
+
121
+ it 'sets attribute to nil' do
122
+ expect(decorator.shared_attribute).to be_nil
123
+ end
124
+
125
+ it 'sets attribute to nil in shared state' do
126
+ decorator.instance_variable_set(:@shared_attribute, "for real not nil")
127
+ expect(decorator.shared_attribute).to be_nil
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'when attributes via declared personally' do
133
+ describe '#setter' do
134
+ it 'sets local attribute via initialize' do
135
+ expect(decorator.name).to eq('bob')
136
+ end
137
+
138
+ it 'sets local attribute locally by initialize' do
139
+ expect(decorator.instance_variable_get(:@name)).to eq('bob')
140
+ end
141
+
142
+ it 'does not set local attribute in shared state via initialize' do
143
+ expect(decorator.decorated_state.name).to be_nil
144
+ end
145
+ end
146
+
147
+ describe '#getter' do
148
+ it 'gets nil for unused attribute' do
149
+ expect(decorator.unused_personal_attribute).to be_nil
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'when calling decorator methods' do
155
+ it 'picks up methods it has' do
156
+ expect(decorator.basic_decorator_method).to be_true
157
+ end
158
+
159
+ it 'defers methods it doesn\'t have' do
160
+ response = catch :chain_stop do
161
+ decorator.send(:nonexistent_method)
162
+ end
163
+ expect(response).to be_a(Decorum::ChainStop)
164
+ end
165
+ end
166
+ end