decorum 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/decorum.png)](http://badge.fury.io/rb/decorum)
|
3
|
+
[![Build Status](https://travis-ci.org/erikcameron/decorum.png?branch=master)](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
|