decorum 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/README.md +39 -6
- data/lib/decorum/decorations.rb +49 -4
- data/lib/decorum/version.rb +1 -1
- data/spec/support/decorations/class_specified_decorator_one.rb +21 -0
- data/spec/unit/decorations_spec.rb +68 -2
- metadata +4 -2
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -24,10 +24,16 @@ end
|
|
24
24
|
|
25
25
|
bp = BirthdayParty.new
|
26
26
|
bp.respond_to?(:shoot_confetti) # ==> false
|
27
|
+
bp.is_decorated? # ==> false
|
27
28
|
bp.decorate(Confetti)
|
29
|
+
bp.is_decorated? # ==> true
|
28
30
|
bp.shoot_confetti # ==> "boom, yay"
|
29
31
|
```
|
30
32
|
|
33
|
+
## New in 0.4.0
|
34
|
+
- Decorators and their attributes may now be specified in class definitions, and loaded with `#load_decorators_from_class`
|
35
|
+
- `#is_decorated?`
|
36
|
+
|
31
37
|
## New in 0.3.0
|
32
38
|
- Methods may be called directly on decorators
|
33
39
|
- Decorator namespaces
|
@@ -196,7 +202,7 @@ original definition is found.)
|
|
196
202
|
A sizeable amount of the world's total Ruby functionality is implemented
|
197
203
|
by overriding `#method_missing`, so decorators shouldn't get in the way.
|
198
204
|
Because Decorum intercepts messages in the objects eigenclass, it also
|
199
|
-
respects existing overrides. If the decorator chain doesn't claim the
|
205
|
+
respects existing class-level overrides. If the decorator chain doesn't claim the
|
200
206
|
message, `super` is called and lookup proceeds normally.
|
201
207
|
|
202
208
|
#### Unloadable decorators
|
@@ -205,10 +211,10 @@ Decorators can be unloaded, if necessary:
|
|
205
211
|
|
206
212
|
```ruby
|
207
213
|
@bob.decorate(IsTheMole)
|
208
|
-
@bob.revoke_security_clearances
|
209
214
|
|
210
|
-
|
211
|
-
|
215
|
+
# meanwhile:
|
216
|
+
class IsTheMole < Decorum::Decorator
|
217
|
+
# hook method
|
212
218
|
def post_decorate
|
213
219
|
object.revoke_security_clearances
|
214
220
|
end
|
@@ -219,6 +225,8 @@ class IsTheMole < Decorum::Decorators
|
|
219
225
|
end
|
220
226
|
end
|
221
227
|
```
|
228
|
+
This example also shows the use of the `#post_decorate` hook.
|
229
|
+
|
222
230
|
|
223
231
|
### Implementation
|
224
232
|
|
@@ -243,7 +251,7 @@ by including Decorum::Decorations, or for a single object, by extending it.
|
|
243
251
|
Decorum::Decorations at whatever point(s) of the class hierarchy you
|
244
252
|
feel appropriate.
|
245
253
|
|
246
|
-
###
|
254
|
+
### Using Decorators
|
247
255
|
The decorated object is accessible as either `#root` or `#object`. A helper method:
|
248
256
|
|
249
257
|
```ruby
|
@@ -299,6 +307,31 @@ object; this shared state can be used for a number of purposes. Finally,
|
|
299
307
|
`default_attributes` lets you set class-level defaults; these will be
|
300
308
|
preempted by options passed to the constructor.
|
301
309
|
|
310
|
+
#### Specifying decorators in classes
|
311
|
+
|
312
|
+
Decoration may be performed by the instance method `#decorate` above,
|
313
|
+
or you can include default decorators and arguments in your class
|
314
|
+
definition:
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
class Coffee
|
318
|
+
include Decorum::Decorations
|
319
|
+
# two bovine dairy no sugar by default, please:
|
320
|
+
decorators Milk, { animal: "cow" }, Milk, { animal: "cow" }
|
321
|
+
...
|
322
|
+
end
|
323
|
+
|
324
|
+
c = Coffee.new
|
325
|
+
c.load_decorators_from_class
|
326
|
+
c.add_milk
|
327
|
+
c.milk_level # ==> 2
|
328
|
+
```
|
329
|
+
|
330
|
+
Note that this usage does _not_ automatically include decorators on new
|
331
|
+
objects. That would require invasive procedures on your object initialization.
|
332
|
+
Instead, Decorum provides `#load_decorators_from_class`, which you can call
|
333
|
+
in your initializations, or later.
|
334
|
+
|
302
335
|
As a side note, you can disable another decorators methods thus:
|
303
336
|
|
304
337
|
```ruby
|
@@ -334,7 +367,7 @@ other things:
|
|
334
367
|
|
335
368
|
And so on.
|
336
369
|
|
337
|
-
###
|
370
|
+
### #decorated_tail
|
338
371
|
|
339
372
|
How exactly did the first MilkDecorator pass `#add_milk`
|
340
373
|
down the chain instead of returning? In general, the decision
|
data/lib/decorum/decorations.rb
CHANGED
@@ -1,5 +1,36 @@
|
|
1
1
|
module Decorum
|
2
2
|
module Decorations
|
3
|
+
def self.included(modyool)
|
4
|
+
# class method to declare default decorators
|
5
|
+
def modyool.decorators(*args)
|
6
|
+
# set first-listed priority
|
7
|
+
if args[0] == :reverse
|
8
|
+
return @_decorum_stack_reverse = true
|
9
|
+
end
|
10
|
+
|
11
|
+
@_decorum_stack ||= []
|
12
|
+
|
13
|
+
if !args.empty?
|
14
|
+
args.each do |arg|
|
15
|
+
if (arg.is_a?(Class) && arg.ancestors.include?(Decorum::Decorator)) || arg.is_a?(Hash)
|
16
|
+
next if arg.is_a?(Hash)
|
17
|
+
klass = arg
|
18
|
+
next_arg = args[args.index(arg) + 1]
|
19
|
+
options = next_arg.is_a?(Hash) ? next_arg : {}
|
20
|
+
@_decorum_stack << [klass, options]
|
21
|
+
else
|
22
|
+
raise ArgumentError, "invalid argument to #{self.to_s}.decorate_with: #{arg.to_s}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
@_decorum_stack_reverse ? @_decorum_stack.reverse : @_decorum_stack
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# public instance methods
|
32
|
+
|
33
|
+
|
3
34
|
def decorate(klass, options={})
|
4
35
|
if namespace_method = options.delete(:namespace)
|
5
36
|
decorator = nil
|
@@ -11,7 +42,7 @@ module Decorum
|
|
11
42
|
end
|
12
43
|
namespace.decorate(klass, options) { |d| decorator = d }
|
13
44
|
else
|
14
|
-
namespace
|
45
|
+
namespace = Decorum::DecoratorNamespace.new(self)
|
15
46
|
namespace.decorate(klass, options) { |d| decorator = d }
|
16
47
|
instance_variable_set(:"@_decorum_#{namespace_method}", namespace)
|
17
48
|
m = Module.new do
|
@@ -20,6 +51,7 @@ module Decorum
|
|
20
51
|
end
|
21
52
|
end
|
22
53
|
extend m
|
54
|
+
_decorator_namespaces << namespace_method
|
23
55
|
end
|
24
56
|
yield CallableDecorator.new(decorator) if block_given?
|
25
57
|
else
|
@@ -35,13 +67,22 @@ module Decorum
|
|
35
67
|
remove_from_decorator_chain(target)
|
36
68
|
self
|
37
69
|
end
|
38
|
-
|
39
70
|
|
71
|
+
def is_decorated?
|
72
|
+
![ decorators, _decorator_namespaces.map { |ns| send(ns).decorators } ].flatten.empty?
|
73
|
+
end
|
74
|
+
|
40
75
|
# returns callable decorators---use this
|
41
76
|
def decorators
|
42
77
|
_decorators.map { |d| CallableDecorator.new(d) }
|
43
78
|
end
|
44
79
|
|
80
|
+
# leaving it to you to, say, call this from #initialize
|
81
|
+
def load_decorators_from_class
|
82
|
+
self.class.decorators.each { |decorator_class, options| decorate(decorator_class, options) }
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
45
86
|
# returns raw decorators---don't use this unless
|
46
87
|
# you know what you're doing
|
47
88
|
def _decorators
|
@@ -75,6 +116,10 @@ module Decorum
|
|
75
116
|
@_decorated_state
|
76
117
|
end
|
77
118
|
end
|
119
|
+
|
120
|
+
def _decorator_namespaces
|
121
|
+
@_decorator_namespaces ||= []
|
122
|
+
end
|
78
123
|
|
79
124
|
module Decorum::Decorations::Intercept
|
80
125
|
def method_missing(message, *args, &block)
|
@@ -90,11 +135,11 @@ module Decorum
|
|
90
135
|
end
|
91
136
|
end
|
92
137
|
|
93
|
-
|
138
|
+
private
|
94
139
|
|
95
140
|
def add_to_decorator_chain(klass, options)
|
96
141
|
unless klass.ancestors.include?(Decorum::Decorator)
|
97
|
-
raise
|
142
|
+
raise TypeError, "decorator chain needs a Decorator"
|
98
143
|
end
|
99
144
|
|
100
145
|
if options[:decorator_handle]
|
data/lib/decorum/version.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Decorum
|
2
|
+
module Spec
|
3
|
+
module Decorations
|
4
|
+
class ClassSpecifiedDecoratorOne < Decorum::Decorator
|
5
|
+
attr_accessor :passed_option
|
6
|
+
|
7
|
+
def one
|
8
|
+
"one"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ClassSpecifiedDecoratorTwo < ClassSpecifiedDecoratorOne
|
13
|
+
attr_accessor :passed_option
|
14
|
+
|
15
|
+
def two
|
16
|
+
"two"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -6,6 +6,55 @@ describe Decorum::Decorations do
|
|
6
6
|
let(:deco_class_2) { Decorum::Spec::Decorations::SecondDecorator }
|
7
7
|
let(:deco_class_3) { Decorum::Spec::Decorations::ThirdDecorator }
|
8
8
|
|
9
|
+
context 'loading decorators from class defaults' do
|
10
|
+
let(:klass) do
|
11
|
+
Class.new do
|
12
|
+
include Decorum::Decorations
|
13
|
+
decorators Decorum::Spec::Decorations::ClassSpecifiedDecoratorOne, { passed_option: "one" },
|
14
|
+
Decorum::Spec::Decorations::ClassSpecifiedDecoratorTwo, { passed_option: "two" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.decorators' do
|
19
|
+
it 'stores decorators in own state correctly' do
|
20
|
+
expect(klass.decorators.map { |d| d[0].ancestors.include?(Decorum::Decorator) }.inject(:&)).to be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
# the instance method #load_decorators_from_class will insert them in the order given:
|
24
|
+
|
25
|
+
it 'normally gives first priority to last listed' do
|
26
|
+
expect(klass.decorators.map { |d| d[1] } == [{ passed_option: "one" }, { passed_option: "two" }]).to be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'reverses order for first-specfied priority' do
|
30
|
+
klass.decorators :reverse
|
31
|
+
expect(klass.decorators.map { |d| d[1] } == [{ passed_option: "two" }, { passed_option: "one" }]).to be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'rejects malformed options' do
|
35
|
+
expect { klass.decorators(Decorum::Spec::Decorations::FirstDecorator, "invalid decorator argument") }.to raise_error
|
36
|
+
end
|
37
|
+
|
38
|
+
# this probably belongs with other instance methods below, but we've got the
|
39
|
+
# support objects built for it here...
|
40
|
+
describe '#decorators' do
|
41
|
+
let(:obj) { klass.new.load_decorators_from_class }
|
42
|
+
it 'returns self' do
|
43
|
+
# sort of
|
44
|
+
expect(obj.is_a?(klass)).to be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'loads all decorators given by .decorators' do
|
48
|
+
expect(obj.one == "one" && obj.two == "two").to be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'loads decorators in the order given by .decorators' do
|
52
|
+
expect(obj.passed_option == "two").to be_true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
9
58
|
context 'as-yet-undecorated' do
|
10
59
|
# assert some basic assumptions
|
11
60
|
it 'is decoratable' do
|
@@ -20,6 +69,12 @@ describe Decorum::Decorations do
|
|
20
69
|
expect(decorated.undecorated_method).to be_true
|
21
70
|
end
|
22
71
|
|
72
|
+
describe '#is_decorated?' do
|
73
|
+
it 'is false' do
|
74
|
+
expect(decorated.is_decorated?).to be_false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
23
78
|
describe '#decorated_state' do
|
24
79
|
it 'returns a hash' do
|
25
80
|
blank_state = decorated.decorated_state
|
@@ -41,7 +96,7 @@ describe Decorum::Decorations do
|
|
41
96
|
expect(decorated.undecorate(:symbol_arg)).to be_equal(decorated)
|
42
97
|
end
|
43
98
|
|
44
|
-
it 'returns self on spurious
|
99
|
+
it 'returns self on spurious decorator' do
|
45
100
|
expect(decorated.undecorate(deco_class_1)).to be_equal(decorated)
|
46
101
|
end
|
47
102
|
end
|
@@ -71,6 +126,17 @@ describe Decorum::Decorations do
|
|
71
126
|
decorated.decorate(deco_class_1, decorator_options)
|
72
127
|
end
|
73
128
|
|
129
|
+
describe '#is_decorated?' do
|
130
|
+
it 'returns true' do
|
131
|
+
expect(decorated.is_decorated?).to be_true
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'returns false after unloading' do
|
135
|
+
decorated.undecorate(decorated.decorators.first)
|
136
|
+
expect(decorated.is_decorated?).to be_false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
74
140
|
it 'installs intercept' do
|
75
141
|
expect(decorated.is_a?(Decorum::Decorations::Intercept)).to be_true
|
76
142
|
end
|
@@ -127,7 +193,7 @@ describe Decorum::Decorations do
|
|
127
193
|
|
128
194
|
context 'failure' do
|
129
195
|
it 'rejects classes that are not Decorators' do
|
130
|
-
expect { decorated.decorate(Class,{}) }.to raise_error(
|
196
|
+
expect { decorated.decorate(Class,{}) }.to raise_error(TypeError)
|
131
197
|
end
|
132
198
|
|
133
199
|
it 'rejects non unique decorator handles' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decorum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-12-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -95,6 +95,7 @@ files:
|
|
95
95
|
- spec/integration/immediate_methods_spec.rb
|
96
96
|
- spec/spec_helper.rb
|
97
97
|
- spec/support/decorated_state/shared_state_stub.rb
|
98
|
+
- spec/support/decorations/class_specified_decorator_one.rb
|
98
99
|
- spec/support/decorations/decorated_object_stub.rb
|
99
100
|
- spec/support/decorations/first_decorator.rb
|
100
101
|
- spec/support/decorations/second_decorator.rb
|
@@ -144,6 +145,7 @@ test_files:
|
|
144
145
|
- spec/integration/immediate_methods_spec.rb
|
145
146
|
- spec/spec_helper.rb
|
146
147
|
- spec/support/decorated_state/shared_state_stub.rb
|
148
|
+
- spec/support/decorations/class_specified_decorator_one.rb
|
147
149
|
- spec/support/decorations/decorated_object_stub.rb
|
148
150
|
- spec/support/decorations/first_decorator.rb
|
149
151
|
- spec/support/decorations/second_decorator.rb
|