decorum 0.2.0 → 0.3.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/.travis.yml +6 -0
- data/CHANGELOG.md +9 -0
- data/README.md +86 -35
- data/Rakefile +3 -0
- data/examples/immediate_decorator.rb +22 -0
- data/lib/decorum.rb +34 -9
- data/lib/decorum/callable_decorator.rb +18 -0
- data/lib/decorum/decorated_state.rb +1 -1
- data/lib/decorum/decorations.rb +56 -19
- data/lib/decorum/decorator.rb +16 -4
- data/lib/decorum/decorator_namespace.rb +13 -0
- data/lib/decorum/version.rb +1 -1
- data/spec/integration/immediate_methods_spec.rb +10 -0
- data/spec/support/decorations/first_decorator.rb +5 -0
- data/spec/support/decorator/chatty_root_object_stub.rb +11 -0
- data/spec/unit/callable_decorator_spec.rb +22 -0
- data/spec/unit/decorations_spec.rb +8 -5
- data/spec/unit/decorator_namespace_spec.rb +17 -0
- data/spec/unit/decorator_spec.rb +7 -0
- data/spec/unit/superhash_spec.rb +20 -0
- metadata +14 -2
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# 0.3.0 / 2014-2-3
|
2
|
+
|
3
|
+
* [FEATURE] directly callable decorators
|
4
|
+
* [FEATURE] decorator namespaces
|
5
|
+
* [FEATURE] set superhash class from environment variables
|
6
|
+
* [FEATURE] immediate all
|
7
|
+
* [FEATURE] #post_decorate
|
8
|
+
* a more nuanced position on the immediacy issue, etc. (see README)
|
9
|
+
|
1
10
|
# 0.2.0 / 2014-01-18
|
2
11
|
|
3
12
|
* [FEATURE] support/tests for immediate methods
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# Decorum
|
2
|
+
[](http://badge.fury.io/rb/decorum)
|
3
|
+
[](https://travis-ci.org/erikcameron/decorum)
|
2
4
|
|
3
5
|
Decorum implements lightweight decorators
|
4
6
|
for Ruby, called "tasteful decorators." (See below.) It is very small,
|
@@ -6,6 +8,7 @@ possibly very fast, and has no requirements outside of the standard
|
|
6
8
|
library. Use it wherever.
|
7
9
|
|
8
10
|
## Quick Start
|
11
|
+
|
9
12
|
```ruby
|
10
13
|
gem install decorum
|
11
14
|
|
@@ -24,12 +27,20 @@ bp.respond_to?(:shoot_confetti) # ==> false
|
|
24
27
|
bp.decorate(Confetti)
|
25
28
|
bp.shoot_confetti # ==> "boom, yay"
|
26
29
|
```
|
30
|
+
|
31
|
+
## New in 0.3.0
|
32
|
+
- Methods may be called directly on decorators
|
33
|
+
- Decorator namespaces
|
34
|
+
- Set superhash and shared state classes via environment
|
35
|
+
- Entire decorator interfaces may be declared `immediate` (see below)
|
36
|
+
- #post_decorate callback
|
37
|
+
|
27
38
|
## About
|
28
39
|
|
29
40
|
[Skip to the action](#usage)
|
30
41
|
|
31
42
|
Decorum expands on the traditional Decorator concept by satisfying [a few additional
|
32
|
-
contraints](#
|
43
|
+
contraints](#tasteful-decorators). The constraints are designed to make decorators' role in your
|
33
44
|
overall object structure both clear and safe. More on these points below.
|
34
45
|
|
35
46
|
- Object Identity: After you decorate an object, you're still dealing with that same
|
@@ -41,13 +52,13 @@ the decorated object's public methods. (Though you can instruct it to.) This is
|
|
41
52
|
|
42
53
|
By adhering to these constraints, decorators tend to do the Right Thing, i.e, integrate into existing
|
43
54
|
applications easily, and stay out of the way when they aren't doing your bidding. Hence "[tasteful
|
44
|
-
decorators](#
|
55
|
+
decorators](#tasteful-decorators)." (Not meant to imply others are tacky. The name just stuck.)
|
45
56
|
|
46
57
|
In addition, Decorum provides a few helpful features:
|
47
58
|
|
48
59
|
- Stackable decorators, with shared state
|
49
60
|
- Recursion, via `#decorated_tail`
|
50
|
-
- Intercept/change messages
|
61
|
+
- Intercept/change/reroute messages, a la Chain of Reponsibility
|
51
62
|
- Build stuff entirely out of decorators
|
52
63
|
|
53
64
|
As an example of how this is in use right now, suppose you're interfacing a content
|
@@ -58,16 +69,17 @@ on the image at runtime, e.g.,:
|
|
58
69
|
|
59
70
|
```ruby
|
60
71
|
image_collection = Application::ImageData.sidebar_images
|
61
|
-
# say this returns a hash keyed by identifier:
|
62
|
-
# { blah: { url: 'http://blah.foo/', alt: 'The Blah Conglomerate' ... }}
|
63
72
|
images = Cms::Images.where(identifier: image_collection.keys)
|
73
|
+
# ==> { blah: { url: 'http://blah.foo/', alt: 'The Blah Conglomerate' ... }}
|
64
74
|
|
65
75
|
images.each do |img|
|
66
76
|
img.decorate(ImageMetaDecorator, image_collection[img.identifier])
|
67
77
|
end
|
68
78
|
|
79
|
+
# defined in ImageMetaDecorator:
|
69
80
|
images[0].url # ==> 'http://blah.foo'
|
70
81
|
images[0].alt # ==> 'The Blah Conglomerate'
|
82
|
+
images[0].fetch_thumbnail_or_queue_to_create
|
71
83
|
```
|
72
84
|
|
73
85
|
### Isn't a Decorator like a Presenter which is like an HTML macro?
|
@@ -87,15 +99,15 @@ Decorators may or may not present.
|
|
87
99
|
Like "traditional" (i.e., [Gang of Four](http://en.wikipedia.org/wiki/Design_Patterns)-style)
|
88
100
|
decorator patterns, Decorum is a general purpose, object-oriented tool. Use it wherever.
|
89
101
|
|
90
|
-
###
|
102
|
+
### Tasteful Decorators
|
91
103
|
|
92
104
|
#### Object Identity
|
93
105
|
|
94
106
|
Decorators, as conceived of by GoF, Python, etc., masquerade as the
|
95
107
|
objects they decorate by responding to their interface. They are _not_ the
|
96
108
|
original objects themselves. This may or may not be a problem, depending
|
97
|
-
on how your
|
98
|
-
it risks breaking encapsulation
|
109
|
+
on how your code is structured. In general though, (I doubt this is news)
|
110
|
+
it risks breaking encapsulation: Any code which stores direct references
|
99
111
|
to the original object will have to update them to get the decorated
|
100
112
|
behavior. For example, in a common Rails idiom, in order to do this:
|
101
113
|
|
@@ -142,14 +154,20 @@ and state stays where it should.
|
|
142
154
|
#### Defers to the original interface
|
143
155
|
|
144
156
|
Unless instructed otherwise, Decorum will not override existing methods
|
145
|
-
on the decorated object.
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
157
|
+
on the decorated object. (See below for how to instruct it otherwise.)
|
158
|
+
There are certainly cases where this is useful, and having come across
|
159
|
+
one myself since the 0.2.0 release, I've amended my position slightly. But
|
160
|
+
not much.
|
161
|
+
|
162
|
+
I'm suspicious of this practice, as it blurs the line between
|
163
|
+
"decorating" an object and straight-up monkey patching it. The fact that the method
|
164
|
+
_needs_ overriding implies the original object doesn't have the relevant
|
165
|
+
state to fulfill it. The method is now spread out over two classes,
|
166
|
+
that of the original object and that of the decorator. This may be
|
167
|
+
unavoidable, or monkey patching may be your intention---in that case,
|
168
|
+
by all means. But I don't think it's a pattern to be designed to.
|
169
|
+
|
170
|
+
The paradigm cases of decorators don't generally address overriding either. Consider three
|
153
171
|
common examples:
|
154
172
|
|
155
173
|
- Adding a scrollbar to a window
|
@@ -163,7 +181,8 @@ but it's by no means an essential use of the pattern. And from a design
|
|
163
181
|
perspective, it's a red flag that concerns are becoming... unseparated.
|
164
182
|
(It also risks weird bugs by breaking transparency, i.e., you can have
|
165
183
|
cases where `a` and `b` are literally identical, but have different
|
166
|
-
attributes.)
|
184
|
+
attributes.) With Decorum, you can implement that entire domain of behavior,
|
185
|
+
possibly using a [namespace](#namespaced-decorators).
|
167
186
|
|
168
187
|
When an object is decorated, Decorum inserts its own `#method_missing`
|
169
188
|
and `#respond_to_missing?` into the object's eigenclass. Decorum's `#method_missing`
|
@@ -182,17 +201,18 @@ message, `super` is called and lookup proceeds normally.
|
|
182
201
|
|
183
202
|
#### Unloadable decorators
|
184
203
|
|
185
|
-
Decorators can be unloaded, if necessary
|
186
|
-
this, and the need for callbacks, e.g., `#after_decorate`. Definitely one
|
187
|
-
my next priorities.)
|
204
|
+
Decorators can be unloaded, if necessary:
|
188
205
|
|
189
206
|
```ruby
|
190
207
|
@bob.decorate(IsTheMole)
|
191
208
|
@bob.revoke_security_clearances
|
192
|
-
# ideally this would be called automatically by IsTheMole
|
193
|
-
# on decoration
|
194
209
|
|
195
210
|
class IsTheMole < Decorum::Decorators
|
211
|
+
# callback
|
212
|
+
def post_decorate
|
213
|
+
object.revoke_security_clearances
|
214
|
+
end
|
215
|
+
|
196
216
|
def revoke_security_clearances
|
197
217
|
clearances = object.decorators.select { |d| d.is_a?(SecurityClearance) }
|
198
218
|
clearances.each { |clearance| object.undecorate(clearance) }
|
@@ -213,13 +233,13 @@ which would be significantly slower.)
|
|
213
233
|
|
214
234
|
See the source for more details.
|
215
235
|
|
216
|
-
##
|
236
|
+
## Usage
|
217
237
|
|
218
238
|
First, objects need to be decoratable. You can do this for a whole class,
|
219
239
|
by including Decorum::Decorations, or for a single object, by extending it.
|
220
240
|
(Note: this alone doesn't change the object's method lookup. It just makes
|
221
241
|
`#decorate` available on the object. The behavior is only changed when
|
222
|
-
`#decorate` is called.) The easiest
|
242
|
+
`#decorate` is called.) The easiest way is probably including
|
223
243
|
Decorum::Decorations at whatever point(s) of the class hierarchy you
|
224
244
|
feel appropriate.
|
225
245
|
|
@@ -296,14 +316,12 @@ are shared among all decorators of that class on a given object:
|
|
296
316
|
if an object has three MilkDecorators, the `#milk_level`/`#milk_level=` methods
|
297
317
|
literally access the same state on all three.
|
298
318
|
In addition, you get `#milk_level?` and `#reset_milk_level` to
|
299
|
-
perform self-
|
319
|
+
perform self-evident functions.
|
300
320
|
|
301
321
|
Access to the shared state is proxied first through the root object,
|
302
322
|
and then through an instance of Decorum::DecoratedState, before
|
303
|
-
ultimately pointing to an instance of Decorum::
|
304
|
-
|
305
|
-
normally OpenStruct, to limit Decorum's dependencies to the standard
|
306
|
-
library, but you can override it; I use Hashr personally.)
|
323
|
+
ultimately pointing to an instance of Decorum::SharedState. This
|
324
|
+
class can be set via the environment (see lib/decorum.rb).
|
307
325
|
|
308
326
|
In the examples above and below, shared state is mainly used to
|
309
327
|
accumulate results, like in `#milk_level`. It can also be used for
|
@@ -421,6 +439,39 @@ You can now parameterize your responses based on whatever conditions you like,
|
|
421
439
|
by loading different decorators before the request is serviced. If nobody claims
|
422
440
|
the specialized method, your default will be returned instead.
|
423
441
|
|
442
|
+
### Calling decorators directly
|
443
|
+
|
444
|
+
An object's decorators are available via `#decorators`, or by passing a block to `#decorate`:
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
@object.decorate(SomeDecorator) { |dec| @this_decorator = dec }
|
448
|
+
@object.decorators # <== array of decorators
|
449
|
+
|
450
|
+
# start a request in the middle of the decorator chain:
|
451
|
+
@the_decorator_im_looking_for = @object.decorators.detect { |d| d.name == "cool decorator, bro" }
|
452
|
+
@the_decorator_im_looking_for.some_method
|
453
|
+
```
|
454
|
+
|
455
|
+
(Note that these methods pass the decorator(s) back wrapped in Decorum::CallableDecorator,
|
456
|
+
which is necessary to call methods on them directly.)
|
457
|
+
|
458
|
+
### Namespaced Decorators
|
459
|
+
|
460
|
+
As noted above, overriding an objects public methods breaks behavior over two
|
461
|
+
different classes. To package a domain of behavior in a namespace using Decorum:
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
@request.decorate(MyRouter, namespace: "routes")
|
465
|
+
@request.routes.my_route(path: "foo/bar")
|
466
|
+
# namespaces pass anything they can't do back to the root
|
467
|
+
# object, so they effectively override it:
|
468
|
+
@request.routes.defined_on_request_object? # <== true
|
469
|
+
```
|
470
|
+
|
471
|
+
This way, you don't have to define a `#my_route` stub on the original class for
|
472
|
+
undecorated instances, and the public method of the original object is available
|
473
|
+
on (and in) the namespace.
|
474
|
+
|
424
475
|
### Overriding existing methods
|
425
476
|
|
426
477
|
To give decorator methods preference over an objects existing methods (if you
|
@@ -441,6 +492,9 @@ x.decorate(StrongWilledDecorator)
|
|
441
492
|
x.method_in_question # <== "overridden"
|
442
493
|
```
|
443
494
|
|
495
|
+
If you declare `immediate` with no arguments, the decorators entire public interface
|
496
|
+
is used.
|
497
|
+
|
444
498
|
### Decorators All the Way Down
|
445
499
|
|
446
500
|
Decorum includes a class called Decorum::BareParticular, which descends from
|
@@ -452,10 +506,8 @@ return nil by default.
|
|
452
506
|
|
453
507
|
## To-do
|
454
508
|
A few things I can imagine showing up soon:
|
455
|
-
-
|
456
|
-
|
457
|
-
- Namespaced decorators, probably showing up as a method on the root object,
|
458
|
-
e.g., `object.my_namespace.namespaced_method`
|
509
|
+
- An easy way to alias the main methods (#decorate, #undecorate), as you might
|
510
|
+
want those names for something else.
|
459
511
|
- Thread safety: probably not an issue if you're retooling your Rails helpers,
|
460
512
|
but consider a case like this:
|
461
513
|
|
@@ -468,12 +520,11 @@ A few things I can imagine showing up soon:
|
|
468
520
|
end
|
469
521
|
end
|
470
522
|
```
|
471
|
-
- Easy subclassing of Decorum::DecoratedState, so you can do wacky things with it
|
472
523
|
|
473
524
|
&c. I'm open to suggestion.
|
474
525
|
|
475
526
|
## Contributing
|
476
|
-
I wrote most of this super late at night, (don't worry, the tests pass) so that would be awesome:
|
527
|
+
I wrote most of this super late at night, (don't worry, the tests pass!) so that would be awesome:
|
477
528
|
|
478
529
|
1. Fork it
|
479
530
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
data/Rakefile
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Decorum
|
2
|
+
module Examples
|
3
|
+
class ImmediateDecorator < Decorum::Decorator
|
4
|
+
immediate
|
5
|
+
share :immediately_shared
|
6
|
+
|
7
|
+
def method_in_question
|
8
|
+
"overridden"
|
9
|
+
end
|
10
|
+
|
11
|
+
def another_method_in_question
|
12
|
+
"overridden"
|
13
|
+
end
|
14
|
+
|
15
|
+
def increment_immediately_shared
|
16
|
+
self.immediately_shared ||= 0
|
17
|
+
self.immediately_shared += 1
|
18
|
+
decorated_tail(immediately_shared) { next_link.increment_immediately_shared }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/decorum.rb
CHANGED
@@ -1,14 +1,39 @@
|
|
1
|
+
# decorum.rb
|
1
2
|
require 'ostruct'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
superhash_class = if class_name = ENV['DECORUM_SUPERHASH_CLASS']
|
5
|
+
current = nil
|
6
|
+
class_name.split('::').each do |const_name|
|
7
|
+
modyool = current || Kernel
|
8
|
+
current = modyool.const_get(const_name)
|
9
|
+
end
|
10
|
+
current
|
11
|
+
else
|
12
|
+
OpenStruct
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_state_class = if class_name = ENV['DECORUM_SHARED_STATE_CLASS']
|
16
|
+
current = nil
|
17
|
+
class_name.split('::').each do |const_name|
|
18
|
+
modyool = current || Kernel
|
19
|
+
current = modyool.const_get(const_name)
|
6
20
|
end
|
21
|
+
current
|
22
|
+
else
|
23
|
+
superhash_class
|
7
24
|
end
|
8
25
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
26
|
+
module Decorum
|
27
|
+
end
|
28
|
+
|
29
|
+
Decorum::SuperHash = superhash_class
|
30
|
+
Decorum::SharedState = shared_state_class
|
31
|
+
|
32
|
+
require_relative 'decorum/version'
|
33
|
+
require_relative 'decorum/decorations'
|
34
|
+
require_relative 'decorum/decorator'
|
35
|
+
require_relative 'decorum/decorated_state'
|
36
|
+
require_relative 'decorum/chain_stop'
|
37
|
+
require_relative 'decorum/bare_particular'
|
38
|
+
require_relative 'decorum/callable_decorator'
|
39
|
+
require_relative 'decorum/decorator_namespace'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Decorum
|
2
|
+
class CallableDecorator
|
3
|
+
def initialize(decorator)
|
4
|
+
@_decorator = decorator
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(message, *args, &block)
|
8
|
+
response = catch :chain_stop do
|
9
|
+
@_decorator.send(message, *args, &block)
|
10
|
+
end
|
11
|
+
if response.is_a?(Decorum::ChainStop)
|
12
|
+
@_decorator.root.send(message, *args, &block)
|
13
|
+
else
|
14
|
+
response
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/decorum/decorations.rb
CHANGED
@@ -1,9 +1,33 @@
|
|
1
1
|
module Decorum
|
2
2
|
module Decorations
|
3
3
|
def decorate(klass, options={})
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
if namespace_method = options.delete(:namespace)
|
5
|
+
decorator = nil
|
6
|
+
namespace = nil
|
7
|
+
if self.respond_to?(namespace_method)
|
8
|
+
namespace = send(namespace_method)
|
9
|
+
unless namespace.is_a?(Decorum::DecoratorNamespace)
|
10
|
+
raise RuntimeError, "#{namespace_method} exists and is not a decorator namespace"
|
11
|
+
end
|
12
|
+
namespace.decorate(klass, options) { |d| decorator = d }
|
13
|
+
else
|
14
|
+
namespace = Decorum::DecoratorNamespace.new(self)
|
15
|
+
namespace.decorate(klass, options) { |d| decorator = d }
|
16
|
+
instance_variable_set(:"@_decorum_#{namespace_method}", namespace)
|
17
|
+
m = Module.new do
|
18
|
+
define_method(namespace_method) do
|
19
|
+
instance_variable_get(:"@_decorum_#{namespace_method}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
extend m
|
23
|
+
end
|
24
|
+
yield CallableDecorator.new(decorator) if block_given?
|
25
|
+
else
|
26
|
+
extend Decorum::Decorations::Intercept
|
27
|
+
decorator = add_to_decorator_chain(klass, options)
|
28
|
+
yield CallableDecorator.new(decorator) if block_given?
|
29
|
+
decorator.post_decorate
|
30
|
+
end
|
7
31
|
self
|
8
32
|
end
|
9
33
|
|
@@ -12,7 +36,15 @@ module Decorum
|
|
12
36
|
self
|
13
37
|
end
|
14
38
|
|
39
|
+
|
40
|
+
# returns callable decorators---use this
|
15
41
|
def decorators
|
42
|
+
_decorators.map { |d| CallableDecorator.new(d) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# returns raw decorators---don't use this unless
|
46
|
+
# you know what you're doing
|
47
|
+
def _decorators
|
16
48
|
if @_decorators
|
17
49
|
return @_decorators
|
18
50
|
elsif !@_decorator_chain
|
@@ -28,6 +60,12 @@ module Decorum
|
|
28
60
|
@_decorators
|
29
61
|
end
|
30
62
|
|
63
|
+
# reset the decorator collection
|
64
|
+
def _decorators!
|
65
|
+
@_decorators = nil
|
66
|
+
_decorators
|
67
|
+
end
|
68
|
+
|
31
69
|
def decorated_state(klass=nil)
|
32
70
|
@_decorated_state ||= {}
|
33
71
|
|
@@ -47,7 +85,7 @@ module Decorum
|
|
47
85
|
end
|
48
86
|
|
49
87
|
def respond_to_missing?(message, include_private = false)
|
50
|
-
|
88
|
+
_decorators.each { |d| return true if d.respond_to?(message) }
|
51
89
|
super
|
52
90
|
end
|
53
91
|
end
|
@@ -56,14 +94,13 @@ module Decorum
|
|
56
94
|
|
57
95
|
def add_to_decorator_chain(klass, options)
|
58
96
|
unless klass.ancestors.include?(Decorum::Decorator)
|
59
|
-
raise RuntimeError
|
97
|
+
raise RuntimeError, "decorator chain needs a Decorator"
|
60
98
|
end
|
61
99
|
|
62
100
|
if options[:decorator_handle]
|
63
|
-
current_names =
|
101
|
+
current_names = _decorators.map { |d| d.decorator_handle.to_sym }.compact
|
64
102
|
if current_names.include?(options[:decorator_handle].to_sym)
|
65
|
-
|
66
|
-
raise RuntimeError.new("decorator names must be unique over an object")
|
103
|
+
raise RuntimeError, "decorator names must be unique over an object"
|
67
104
|
end
|
68
105
|
end
|
69
106
|
|
@@ -87,30 +124,30 @@ module Decorum
|
|
87
124
|
end
|
88
125
|
extend immediate
|
89
126
|
end
|
90
|
-
|
91
|
-
decorators!
|
127
|
+
_decorators!
|
92
128
|
@_decorator_chain
|
93
129
|
end
|
94
130
|
|
95
131
|
def remove_from_decorator_chain(decorator)
|
96
|
-
|
132
|
+
if decorator.is_a?(CallableDecorator)
|
133
|
+
decorator = decorator.instance_variable_get(:@_decorator)
|
134
|
+
end
|
135
|
+
|
136
|
+
unless (decorator.is_a?(Decorum::Decorator) && _decorators.include?(decorator))
|
137
|
+
return nil
|
138
|
+
end
|
97
139
|
|
98
140
|
if decorator == @_decorator_chain
|
99
141
|
@_decorator_chain = decorator.next_link
|
100
142
|
else
|
101
|
-
previous_decorator =
|
143
|
+
previous_decorator = _decorators[_decorators.index(decorator) - 1]
|
102
144
|
previous_decorator.next_link = decorator.next_link
|
103
145
|
end
|
104
146
|
|
105
|
-
unless
|
147
|
+
unless _decorators!.map { |d| d.class }.include?(decorator.class)
|
106
148
|
@_decorated_state[decorator.class] = nil
|
107
149
|
end
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
def decorators!
|
112
|
-
@_decorators = nil
|
113
|
-
decorators
|
150
|
+
_decorators
|
114
151
|
end
|
115
152
|
end
|
116
153
|
end
|
data/lib/decorum/decorator.rb
CHANGED
@@ -20,6 +20,11 @@ module Decorum
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
# override if you want
|
25
|
+
def post_decorate
|
26
|
+
nil
|
27
|
+
end
|
23
28
|
|
24
29
|
# a superhash of shared state between Decorators
|
25
30
|
# of the same class
|
@@ -37,6 +42,7 @@ module Decorum
|
|
37
42
|
end
|
38
43
|
response.is_a?(Decorum::ChainStop) ? current_value : response
|
39
44
|
end
|
45
|
+
alias_method :defer, :decorated_tail
|
40
46
|
|
41
47
|
# delegate to next_link
|
42
48
|
# note that we are not faking #respond_to? because
|
@@ -82,13 +88,19 @@ module Decorum
|
|
82
88
|
end
|
83
89
|
|
84
90
|
# allow Decorator classes to override the decorated object's
|
85
|
-
# public methods
|
91
|
+
# public methods; use with no args to declare the entire interface
|
86
92
|
def immediate(*method_names)
|
87
|
-
|
88
|
-
|
93
|
+
if method_names.empty?
|
94
|
+
@all_immediate = true
|
95
|
+
else
|
96
|
+
@immediate_methods ||= []
|
97
|
+
@immediate_methods += method_names
|
98
|
+
end
|
89
99
|
end
|
90
100
|
|
91
|
-
|
101
|
+
def immediate_methods
|
102
|
+
@all_immediate ? instance_methods(false) : (@immediate_methods || [])
|
103
|
+
end
|
92
104
|
end
|
93
105
|
end
|
94
106
|
end
|
data/lib/decorum/version.rb
CHANGED
@@ -30,4 +30,14 @@ describe "When overriding original methods with .immediate" do
|
|
30
30
|
base_object.undecorate(base_object.decorators.first)
|
31
31
|
expect(base_object.second_immediate_method).to eq("class method_missing")
|
32
32
|
end
|
33
|
+
|
34
|
+
it "recurses" do
|
35
|
+
4.times { base_object.decorate(Decorum::Examples::ImmediateDecorator) }
|
36
|
+
expect(base_object.increment_immediately_shared).to eq(4)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "recurses on namespaced decorator" do
|
40
|
+
4.times { base_object.decorate(Decorum::Examples::ImmediateDecorator, namespace: :foo) }
|
41
|
+
expect(base_object.foo.increment_immediately_shared).to eq(4)
|
42
|
+
end
|
33
43
|
end
|
@@ -4,6 +4,7 @@ module Decorum
|
|
4
4
|
class FirstDecorator < Decorum::Decorator
|
5
5
|
share :shared_attribute
|
6
6
|
attr_accessor :local_attribute
|
7
|
+
attr_reader :post_decorated
|
7
8
|
|
8
9
|
def first_decorator_method
|
9
10
|
"first"
|
@@ -14,6 +15,10 @@ module Decorum
|
|
14
15
|
def respect_previously_defined_methods?
|
15
16
|
false
|
16
17
|
end
|
18
|
+
|
19
|
+
def post_decorate
|
20
|
+
@post_decorated = "decorated"
|
21
|
+
end
|
17
22
|
end
|
18
23
|
end
|
19
24
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::CallableDecorator do
|
4
|
+
let(:decorator) do
|
5
|
+
c = Decorum::Examples::Coffee.new
|
6
|
+
decorator = nil
|
7
|
+
c.decorate(Decorum::Examples::MilkDecorator) { |d| decorator = d }
|
8
|
+
decorator
|
9
|
+
end
|
10
|
+
|
11
|
+
it "enables methods to be called directly on decorators" do
|
12
|
+
decorator.add_milk
|
13
|
+
expect(decorator.root.milk_level).to eq(1)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "testing assumptions" do
|
17
|
+
# just make sure we did this right...
|
18
|
+
it "is a Decorator" do
|
19
|
+
expect(decorator.is_a?(Decorum::CallableDecorator)).to be_true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -5,7 +5,6 @@ describe Decorum::Decorations do
|
|
5
5
|
let(:deco_class_1) { Decorum::Spec::Decorations::FirstDecorator }
|
6
6
|
let(:deco_class_2) { Decorum::Spec::Decorations::SecondDecorator }
|
7
7
|
let(:deco_class_3) { Decorum::Spec::Decorations::ThirdDecorator }
|
8
|
-
|
9
8
|
|
10
9
|
context 'as-yet-undecorated' do
|
11
10
|
# assert some basic assumptions
|
@@ -54,12 +53,16 @@ describe Decorum::Decorations do
|
|
54
53
|
real_decorated = decorated
|
55
54
|
expect(decorated.decorate(deco_class_1)).to be_equal(real_decorated)
|
56
55
|
end
|
56
|
+
|
57
|
+
it 'calls #post_decorate' do
|
58
|
+
expect(decorated.decorate(deco_class_1).post_decorated).to eq("decorated")
|
59
|
+
end
|
57
60
|
|
58
61
|
it 'yields decorator if block_given?' do
|
59
62
|
decorator = nil
|
60
63
|
decorated.decorate(deco_class_1) { |dec| decorator = dec }
|
61
64
|
actual_decorator = decorated.instance_variable_get(:@_decorator_chain)
|
62
|
-
expect(decorator).to be(actual_decorator)
|
65
|
+
expect(decorator.instance_variable_get(:@_decorator)).to be(actual_decorator)
|
63
66
|
end
|
64
67
|
|
65
68
|
context 'success' do
|
@@ -173,8 +176,8 @@ describe Decorum::Decorations do
|
|
173
176
|
end
|
174
177
|
|
175
178
|
it 'refreshes via #decorators!' do
|
176
|
-
decorated.
|
177
|
-
decorated.send(:
|
179
|
+
decorated._decorators[0].next_link = decorated._decorators[2]
|
180
|
+
decorated.send(:_decorators!)
|
178
181
|
expect(decorated.decorators.length).to be(2)
|
179
182
|
end
|
180
183
|
end
|
@@ -183,7 +186,7 @@ describe Decorum::Decorations do
|
|
183
186
|
before(:each) do
|
184
187
|
@undec = decorated.decorators.detect { |d| d.decorator_handle == "deco-2" }
|
185
188
|
# just to make sure...
|
186
|
-
unless @undec.is_a?(Decorum::
|
189
|
+
unless @undec.is_a?(Decorum::CallableDecorator)
|
187
190
|
raise "broken test---no such decorator deco-2; undec was #{@undec.inspect}"
|
188
191
|
end
|
189
192
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::DecoratorNamespace do
|
4
|
+
let(:root) { Decorum::Spec::Decorator::DecoratedObjectStub.new }
|
5
|
+
let(:chatty_root) { Decorum::Spec::Decorator::ChattyRootObjectStub.new }
|
6
|
+
let(:ns_class) { Decorum::DecoratorNamespace }
|
7
|
+
|
8
|
+
it "is decoratable" do
|
9
|
+
ns = ns_class.new(root)
|
10
|
+
expect(ns.is_a?(Decorum::Decorations)).to be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "defers to root object on unknown messages" do
|
14
|
+
ns = ns_class.new(chatty_root)
|
15
|
+
expect(ns.asdfasdfadf).to eq("root")
|
16
|
+
end
|
17
|
+
end
|
data/spec/unit/decorator_spec.rb
CHANGED
@@ -197,5 +197,12 @@ describe Decorum::Decorator do
|
|
197
197
|
got_em = methods.map { |m| Decorum::Examples::StrongWilledDecorator.immediate_methods.include?(m) }.inject(:&)
|
198
198
|
expect(got_em).to be_true
|
199
199
|
end
|
200
|
+
|
201
|
+
context 'with no arguments' do
|
202
|
+
it 'immediafies its whole public interface' do
|
203
|
+
deco_class = Decorum::Examples::ImmediateDecorator
|
204
|
+
expect(deco_class.immediate_methods).to eq(deco_class.instance_methods(false))
|
205
|
+
end
|
206
|
+
end
|
200
207
|
end
|
201
208
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::SuperHash do
|
4
|
+
let(:soop) { Decorum::SuperHash.new(a: "z", b: "y", c: "x") }
|
5
|
+
|
6
|
+
it "accepts a hash" do
|
7
|
+
expect(soop.a).to eq("z")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns nil on nonexistent methods" do
|
11
|
+
expect(soop.asdfasdfasd).to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
context "after initialization" do
|
15
|
+
it "has working setters" do
|
16
|
+
soop.d = "w"
|
17
|
+
expect(soop.d).to eq("w")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
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.3.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-02-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -67,6 +67,7 @@ extensions: []
|
|
67
67
|
extra_rdoc_files: []
|
68
68
|
files:
|
69
69
|
- .gitignore
|
70
|
+
- .travis.yml
|
70
71
|
- CHANGELOG.md
|
71
72
|
- Gemfile
|
72
73
|
- LICENSE.txt
|
@@ -75,16 +76,19 @@ files:
|
|
75
76
|
- decorum.gemspec
|
76
77
|
- examples/coffee.rb
|
77
78
|
- examples/fibonacci_decorator.rb
|
79
|
+
- examples/immediate_decorator.rb
|
78
80
|
- examples/milk_decorator.rb
|
79
81
|
- examples/strong_willed_decorator.rb
|
80
82
|
- examples/sugar_decorator.rb
|
81
83
|
- examples/weak_willed_class.rb
|
82
84
|
- lib/decorum.rb
|
83
85
|
- lib/decorum/bare_particular.rb
|
86
|
+
- lib/decorum/callable_decorator.rb
|
84
87
|
- lib/decorum/chain_stop.rb
|
85
88
|
- lib/decorum/decorated_state.rb
|
86
89
|
- lib/decorum/decorations.rb
|
87
90
|
- lib/decorum/decorator.rb
|
91
|
+
- lib/decorum/decorator_namespace.rb
|
88
92
|
- lib/decorum/version.rb
|
89
93
|
- spec/integration/coffee_spec.rb
|
90
94
|
- spec/integration/fibonacci_spec.rb
|
@@ -96,14 +100,18 @@ files:
|
|
96
100
|
- spec/support/decorations/second_decorator.rb
|
97
101
|
- spec/support/decorations/third_decorator.rb
|
98
102
|
- spec/support/decorator/basic_decorator.rb
|
103
|
+
- spec/support/decorator/chatty_root_object_stub.rb
|
99
104
|
- spec/support/decorator/decorated_object_stub.rb
|
100
105
|
- spec/support/decorator/decorated_state_stub.rb
|
101
106
|
- spec/support/decorator/decorator_stub.rb
|
102
107
|
- spec/unit/bare_particular_spec.rb
|
108
|
+
- spec/unit/callable_decorator_spec.rb
|
103
109
|
- spec/unit/chain_stop_spec.rb
|
104
110
|
- spec/unit/decorated_state_spec.rb
|
105
111
|
- spec/unit/decorations_spec.rb
|
112
|
+
- spec/unit/decorator_namespace_spec.rb
|
106
113
|
- spec/unit/decorator_spec.rb
|
114
|
+
- spec/unit/superhash_spec.rb
|
107
115
|
homepage: http://erikcameron.github.io/
|
108
116
|
licenses:
|
109
117
|
- MIT
|
@@ -141,11 +149,15 @@ test_files:
|
|
141
149
|
- spec/support/decorations/second_decorator.rb
|
142
150
|
- spec/support/decorations/third_decorator.rb
|
143
151
|
- spec/support/decorator/basic_decorator.rb
|
152
|
+
- spec/support/decorator/chatty_root_object_stub.rb
|
144
153
|
- spec/support/decorator/decorated_object_stub.rb
|
145
154
|
- spec/support/decorator/decorated_state_stub.rb
|
146
155
|
- spec/support/decorator/decorator_stub.rb
|
147
156
|
- spec/unit/bare_particular_spec.rb
|
157
|
+
- spec/unit/callable_decorator_spec.rb
|
148
158
|
- spec/unit/chain_stop_spec.rb
|
149
159
|
- spec/unit/decorated_state_spec.rb
|
150
160
|
- spec/unit/decorations_spec.rb
|
161
|
+
- spec/unit/decorator_namespace_spec.rb
|
151
162
|
- spec/unit/decorator_spec.rb
|
163
|
+
- spec/unit/superhash_spec.rb
|