decorum 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +3 -0
- data/README.md +59 -6
- data/lib/decorum.rb +3 -39
- data/lib/decorum/aliases.rb +50 -0
- data/lib/decorum/callable_decorator.rb +5 -0
- data/lib/decorum/decorations.rb +65 -58
- data/lib/decorum/decorator.rb +4 -4
- data/lib/decorum/noaliases.rb +42 -0
- data/lib/decorum/version.rb +1 -1
- data/spec/integration/coffee_spec.rb +4 -4
- data/spec/integration/fibonacci_spec.rb +1 -1
- data/spec/integration/immediate_methods_spec.rb +7 -7
- data/spec/support/decorator/decorated_object_stub.rb +2 -2
- data/spec/unit/bare_particular_spec.rb +1 -1
- data/spec/unit/callable_decorator_spec.rb +1 -1
- data/spec/unit/chain_stop_spec.rb +1 -1
- data/spec/unit/decorations_spec.rb +56 -56
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 862e4435ac09860217f17edc406d6779a9f0414d
|
4
|
+
data.tar.gz: 8cbd6050375b8f40110cb5feaf667fee7280977b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ff3a972d777a846831dda2949a69f617372fb0fb896b7594e925b58e4f3a6172e95c68ee85e445f1491d55f68ae7a9b6e7ba8b595aaac662053b8677276d7b2
|
7
|
+
data.tar.gz: bd13f3a374b2fe2b36baed5e33bd03051e945e843438e72ccb747fc6759d205edf9925770c69ad1a6578ac08050daf8d13198ce58f3e7cd0513eca9f2f07a73f
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -25,11 +25,17 @@ end
|
|
25
25
|
bp = BirthdayParty.new
|
26
26
|
bp.respond_to?(:shoot_confetti) # ==> false
|
27
27
|
bp.is_decorated? # ==> false
|
28
|
+
# but wait!
|
28
29
|
bp.decorate(Confetti)
|
30
|
+
# and now...
|
31
|
+
bp.respond_to?(:shoot_confetti) # ==> true
|
29
32
|
bp.is_decorated? # ==> true
|
30
33
|
bp.shoot_confetti # ==> "boom, yay"
|
31
34
|
```
|
32
35
|
|
36
|
+
## New in 0.5.0
|
37
|
+
- The (public) methods in Decorum::Decorations (decorate, undecorate, etc.) may now be aliased. If you were having weird bugs beacuse Decorum was clobbering some existing method named `#decorate`, this is the version for you. More below.
|
38
|
+
|
33
39
|
## New in 0.4.0
|
34
40
|
- Decorators and their attributes may now be specified in class definitions, and loaded with `#load_decorators_from_class`
|
35
41
|
- `#is_decorated?`
|
@@ -245,9 +251,10 @@ See the source for more details.
|
|
245
251
|
|
246
252
|
First, objects need to be decoratable. You can do this for a whole class,
|
247
253
|
by including Decorum::Decorations, or for a single object, by extending it.
|
248
|
-
|
249
|
-
`#decorate` available on the object.
|
250
|
-
|
254
|
+
Note: this alone doesn't change the object's method lookup. It just makes
|
255
|
+
`#decorate` and friends available on the object. (Note: As of 0.5.0 this
|
256
|
+
isn't strictly true. See [Aliasing Decorum Methods](#aliasing-decorum-methods) below.) The behavior is only changed when
|
257
|
+
`#decorate` is called. The easiest way is probably including
|
251
258
|
Decorum::Decorations at whatever point(s) of the class hierarchy you
|
252
259
|
feel appropriate.
|
253
260
|
|
@@ -270,6 +277,7 @@ end
|
|
270
277
|
r = Royalty.find_by_palace_name(:bob)
|
271
278
|
r.respond_to? :styled_name # ==> false
|
272
279
|
r.decorate StyledNameDecorator
|
280
|
+
r.respond_to? :styled_name # ==> true
|
273
281
|
r.styled_name # ==> "His Grace Most Potent Baron Sir Percy Arnold Robert \"Bob\" Gorpthwaite, Esq."
|
274
282
|
```
|
275
283
|
|
@@ -317,7 +325,7 @@ definition:
|
|
317
325
|
class Coffee
|
318
326
|
include Decorum::Decorations
|
319
327
|
# two bovine dairy no sugar by default, please:
|
320
|
-
|
328
|
+
decorum Milk, { animal: "cow" }, Milk, { animal: "cow" }
|
321
329
|
...
|
322
330
|
end
|
323
331
|
|
@@ -327,6 +335,12 @@ c.add_milk
|
|
327
335
|
c.milk_level # ==> 2
|
328
336
|
```
|
329
337
|
|
338
|
+
(This class method was called `decorators` in versions prior to 0.5.0,
|
339
|
+
but in the interest of avoiding naming conflicts, it's been changed to
|
340
|
+
`decorum`. If however you're using the standard [method aliases](#aliasing-decorum-methods),
|
341
|
+
it will install an alias for `decorators`, allowing your existing stuff to
|
342
|
+
keep working without changes.)
|
343
|
+
|
330
344
|
Note that this usage does _not_ automatically include decorators on new
|
331
345
|
objects. That would require invasive procedures on your object initialization.
|
332
346
|
Instead, Decorum provides `#load_decorators_from_class`, which you can call
|
@@ -528,6 +542,46 @@ x.method_in_question # <== "overridden"
|
|
528
542
|
If you declare `immediate` with no arguments, the decorators entire public interface
|
529
543
|
is used.
|
530
544
|
|
545
|
+
### Aliasing Decorum methods
|
546
|
+
|
547
|
+
Including/extending Decorum::Decorations makes a number of public methods (e.g., `#decorate`)
|
548
|
+
available on an object, which can be a problem if methods under those names
|
549
|
+
already exist. As of 0.5.0, these methods are actually implemented with "internalized"
|
550
|
+
names, (e.g., `#_decorum_decorate`) and aliased after the fact. Decorum is loaded by requiring
|
551
|
+
two files:
|
552
|
+
|
553
|
+
```ruby
|
554
|
+
# lib/decorum.rb
|
555
|
+
require_relative 'decorum/noaliases'
|
556
|
+
require_relative 'decorum/aliases'
|
557
|
+
```
|
558
|
+
|
559
|
+
The `noaliases` file loads up everything except aliasing; after requiring this file,
|
560
|
+
public methods in Decorum::Decorations are available by their internal names. Aliases
|
561
|
+
are then loaded for each of the public methods, either as specified in upcased environment variables
|
562
|
+
(e.g., `_DECORUM_DECORATE="my_decorate_alias"`) or failing that, the default values
|
563
|
+
in `aliases`. Here are the public methods and their defaults:
|
564
|
+
|
565
|
+
```ruby
|
566
|
+
# from lib/decorum/aliases.rb
|
567
|
+
DEFAULT_ALIASES = { _decorum_decorate: "decorate",
|
568
|
+
_decorum_undecorate: "undecorate",
|
569
|
+
_decorum_is_decorated?: "is_decorated?",
|
570
|
+
_decorum_decorators: "decorators",
|
571
|
+
_decorum_decorated_state: "decorated_state",
|
572
|
+
_decorum_load_decorators_from_class: "load_decorators_from_class",
|
573
|
+
_decorum_raw_decorators: nil,
|
574
|
+
_decorum_raw_decorators!: nil,
|
575
|
+
_decorum_namespaces: nil }
|
576
|
+
```
|
577
|
+
|
578
|
+
The `.decorum` method made available to modules when they include Decorum::Decorations is also
|
579
|
+
aliased here as either the value of `_DECORUM_CLASS_DECORATORS` or just `.decorators`.
|
580
|
+
|
581
|
+
If you need something else, (e.g., more finely grained aliases) you can just `require decorum/noaliases`,
|
582
|
+
and all of Decorum's aliasing will be skipped. You can then implement your own scheme however you like.
|
583
|
+
|
584
|
+
|
531
585
|
### Decorators All the Way Down
|
532
586
|
|
533
587
|
Decorum includes a class called Decorum::BareParticular, which descends from
|
@@ -539,8 +593,7 @@ return nil by default.
|
|
539
593
|
|
540
594
|
## To-do
|
541
595
|
A few things I can imagine showing up soon:
|
542
|
-
-
|
543
|
-
want those names for something else.
|
596
|
+
- See open issues
|
544
597
|
- Thread safety: probably not an issue if you're retooling your Rails helpers,
|
545
598
|
but consider a case like this:
|
546
599
|
|
data/lib/decorum.rb
CHANGED
@@ -1,39 +1,3 @@
|
|
1
|
-
# decorum.rb
|
2
|
-
|
3
|
-
|
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)
|
20
|
-
end
|
21
|
-
current
|
22
|
-
else
|
23
|
-
superhash_class
|
24
|
-
end
|
25
|
-
|
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'
|
1
|
+
# lib/decorum.rb
|
2
|
+
require_relative 'decorum/noaliases'
|
3
|
+
require_relative 'decorum/aliases'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Here we alias public methods defined by Decorum::Decorations,
|
2
|
+
# because these will appear in other people's classes and should
|
3
|
+
# therefore not cause conflcts. Public methods:
|
4
|
+
# _decorum_decorate
|
5
|
+
# _decorum_undecorate
|
6
|
+
# _decorum_is_decorated?
|
7
|
+
# _decorum_decorators
|
8
|
+
# _decorum_decorated_state
|
9
|
+
# _decorum_load_decorators_from_class
|
10
|
+
#
|
11
|
+
# and these three more "internal" methods, no default aliases:
|
12
|
+
# _decorum_raw_decorators
|
13
|
+
# _decorum_raw_decorators!
|
14
|
+
# _decorum_namespaces
|
15
|
+
#
|
16
|
+
# you can also specify a capitalized method name in env for
|
17
|
+
# something other than default, e.g.,
|
18
|
+
#
|
19
|
+
# _DECORUM_DECORATE='my_funky_decorate_alias'
|
20
|
+
#
|
21
|
+
# otherwise it will fall back on defaults; to bypass this
|
22
|
+
# entirely load decorum/noaliases
|
23
|
+
|
24
|
+
module Decorum
|
25
|
+
module Decorations
|
26
|
+
module ClassMethods
|
27
|
+
class_method_alias = ENV['_DECORUM_CLASS_DECORATORS'] || "decorators"
|
28
|
+
alias_method class_method_alias, :decorum
|
29
|
+
end
|
30
|
+
|
31
|
+
DEFAULT_ALIASES = { _decorum_decorate: "decorate",
|
32
|
+
_decorum_undecorate: "undecorate",
|
33
|
+
_decorum_is_decorated?: "is_decorated?",
|
34
|
+
_decorum_decorators: "decorators",
|
35
|
+
_decorum_decorated_state: "decorated_state",
|
36
|
+
_decorum_load_decorators_from_class: "load_decorators_from_class",
|
37
|
+
_decorum_raw_decorators: nil,
|
38
|
+
_decorum_raw_decorators!: nil,
|
39
|
+
_decorum_namespaces: nil }
|
40
|
+
|
41
|
+
|
42
|
+
DEFAULT_ALIASES.each do |k, v|
|
43
|
+
aliased_name = ENV["#{k.upcase}"] || v
|
44
|
+
if aliased_name
|
45
|
+
alias_method aliased_name, k
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -3,6 +3,11 @@ module Decorum
|
|
3
3
|
def initialize(decorator)
|
4
4
|
@_decorator = decorator
|
5
5
|
end
|
6
|
+
|
7
|
+
# tortuously named to avoid conflict and discourage use
|
8
|
+
def _actual_decorator
|
9
|
+
@_decorator
|
10
|
+
end
|
6
11
|
|
7
12
|
def method_missing(message, *args, &block)
|
8
13
|
response = catch :chain_stop do
|
data/lib/decorum/decorations.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Decorum
|
2
2
|
module Decorations
|
3
|
-
def self.included(modyool)
|
4
3
|
# class method to declare default decorators
|
5
|
-
|
4
|
+
module ClassMethods
|
5
|
+
def decorum(*args)
|
6
6
|
# set first-listed priority
|
7
7
|
if args[0] == :reverse
|
8
8
|
return @_decorum_stack_reverse = true
|
@@ -27,11 +27,15 @@ module Decorum
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
# load the class method(s)
|
32
|
+
def self.included(modyool)
|
33
|
+
modyool.extend Decorum::Decorations::ClassMethods
|
34
|
+
end
|
30
35
|
|
31
36
|
# public instance methods
|
32
37
|
|
33
|
-
|
34
|
-
def decorate(klass, options={})
|
38
|
+
def _decorum_decorate(klass, options={})
|
35
39
|
if namespace_method = options.delete(:namespace)
|
36
40
|
decorator = nil
|
37
41
|
namespace = nil
|
@@ -40,128 +44,131 @@ module Decorum
|
|
40
44
|
unless namespace.is_a?(Decorum::DecoratorNamespace)
|
41
45
|
raise RuntimeError, "#{namespace_method} exists and is not a decorator namespace"
|
42
46
|
end
|
43
|
-
namespace.
|
47
|
+
namespace._decorum_decorate(klass, options) { |d| decorator = d }
|
44
48
|
else
|
45
49
|
namespace = Decorum::DecoratorNamespace.new(self)
|
46
|
-
namespace.
|
47
|
-
instance_variable_set(:"@
|
50
|
+
namespace._decorum_decorate(klass, options) { |d| decorator = d }
|
51
|
+
instance_variable_set(:"@_decorum_ns_#{namespace_method}", namespace)
|
48
52
|
m = Module.new do
|
49
53
|
define_method(namespace_method) do
|
50
|
-
instance_variable_get(:"@
|
54
|
+
instance_variable_get(:"@_decorum_ns_#{namespace_method}")
|
51
55
|
end
|
52
56
|
end
|
53
57
|
extend m
|
54
|
-
|
58
|
+
_decorum_namespaces << namespace_method
|
55
59
|
end
|
56
|
-
yield CallableDecorator.new(decorator) if block_given?
|
60
|
+
yield Decorum::CallableDecorator.new(decorator) if block_given?
|
57
61
|
else
|
58
62
|
extend Decorum::Decorations::Intercept
|
59
|
-
decorator =
|
60
|
-
yield CallableDecorator.new(decorator) if block_given?
|
63
|
+
decorator = _add_to_decorum_chain(klass, options)
|
64
|
+
yield Decorum::CallableDecorator.new(decorator) if block_given?
|
61
65
|
decorator.post_decorate
|
62
66
|
end
|
63
67
|
self
|
64
68
|
end
|
65
69
|
|
66
|
-
def
|
67
|
-
|
70
|
+
def _decorum_undecorate(target)
|
71
|
+
_remove_from_decorum_chain(target)
|
68
72
|
self
|
69
73
|
end
|
70
74
|
|
71
|
-
def
|
72
|
-
![
|
75
|
+
def _decorum_is_decorated?
|
76
|
+
![ _decorum_decorators, _decorum_namespaces.map { |ns| send(ns)._decorum_decorators } ].flatten.empty?
|
73
77
|
end
|
74
78
|
|
75
79
|
# returns callable decorators---use this
|
76
|
-
def
|
77
|
-
|
80
|
+
def _decorum_decorators
|
81
|
+
_decorum_raw_decorators.map { |d| Decorum::CallableDecorator.new(d) }
|
78
82
|
end
|
79
83
|
|
80
84
|
# leaving it to you to, say, call this from #initialize
|
81
|
-
def
|
82
|
-
self.class.decorators.each { |decorator_class, options|
|
85
|
+
def _decorum_load_decorators_from_class
|
86
|
+
self.class.decorators.each { |decorator_class, options| _decorum_decorate(decorator_class, options) }
|
83
87
|
self
|
84
88
|
end
|
85
89
|
|
86
90
|
# returns raw decorators---don't use this unless
|
87
91
|
# you know what you're doing
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
+
def _decorum_raw_decorators
|
93
|
+
# the array of raw decorators is memoized; set
|
94
|
+
# @_decorum_raw_decorators to nil/false to rebuild
|
95
|
+
if @_decorum_raw_decorators
|
96
|
+
return @_decorum_raw_decorators
|
97
|
+
elsif !@_decorum_chain_reference
|
92
98
|
return []
|
93
99
|
end
|
94
100
|
|
95
|
-
|
96
|
-
|
101
|
+
# rebuild the array representation of decorators
|
102
|
+
decorator = @_decorum_chain_reference
|
103
|
+
@_decorum_raw_decorators = []
|
97
104
|
until decorator.is_a?(Decorum::ChainStop)
|
98
|
-
@
|
105
|
+
@_decorum_raw_decorators << decorator
|
99
106
|
decorator = decorator.next_link
|
100
107
|
end
|
101
|
-
@
|
108
|
+
@_decorum_raw_decorators
|
102
109
|
end
|
103
110
|
|
104
111
|
# reset the decorator collection
|
105
|
-
def
|
106
|
-
@
|
107
|
-
|
112
|
+
def _decorum_raw_decorators!
|
113
|
+
@_decorum_raw_decorators = nil
|
114
|
+
_decorum_raw_decorators
|
108
115
|
end
|
109
116
|
|
110
|
-
def
|
111
|
-
@
|
117
|
+
def _decorum_decorated_state(klass=nil)
|
118
|
+
@_decorum_decorated_state ||= {}
|
112
119
|
|
113
120
|
if klass
|
114
|
-
@
|
121
|
+
@_decorum_decorated_state[klass]
|
115
122
|
else
|
116
|
-
@
|
123
|
+
@_decorum_decorated_state
|
117
124
|
end
|
118
125
|
end
|
119
126
|
|
120
|
-
def
|
121
|
-
@
|
127
|
+
def _decorum_namespaces
|
128
|
+
@_decorum_namespaces ||= []
|
122
129
|
end
|
123
130
|
|
124
131
|
module Decorum::Decorations::Intercept
|
125
132
|
def method_missing(message, *args, &block)
|
126
133
|
response = catch :chain_stop do
|
127
|
-
@
|
134
|
+
@_decorum_chain_reference.send(message, *args, &block)
|
128
135
|
end
|
129
136
|
response.is_a?(Decorum::ChainStop) ? super : response
|
130
137
|
end
|
131
138
|
|
132
139
|
def respond_to_missing?(message, include_private = false)
|
133
|
-
|
140
|
+
_decorum_raw_decorators.each { |d| return true if d.respond_to?(message) }
|
134
141
|
super
|
135
142
|
end
|
136
143
|
end
|
137
144
|
|
138
145
|
private
|
139
146
|
|
140
|
-
def
|
147
|
+
def _add_to_decorum_chain(klass, options)
|
141
148
|
unless klass.ancestors.include?(Decorum::Decorator)
|
142
149
|
raise TypeError, "decorator chain needs a Decorator"
|
143
150
|
end
|
144
151
|
|
145
152
|
if options[:decorator_handle]
|
146
|
-
current_names =
|
153
|
+
current_names = _decorum_raw_decorators.map { |d| d.decorator_handle.to_sym }.compact
|
147
154
|
if current_names.include?(options[:decorator_handle].to_sym)
|
148
155
|
raise RuntimeError, "decorator names must be unique over an object"
|
149
156
|
end
|
150
157
|
end
|
151
158
|
|
152
|
-
unless
|
153
|
-
@
|
159
|
+
unless _decorum_decorated_state(klass)
|
160
|
+
@_decorum_decorated_state[klass] = Decorum::DecoratedState.new
|
154
161
|
end
|
155
162
|
|
156
|
-
base = @
|
157
|
-
@
|
163
|
+
base = @_decorum_chain_reference || Decorum::ChainStop.new
|
164
|
+
@_decorum_chain_reference = klass.new(base, self, options)
|
158
165
|
|
159
166
|
if klass.immediate_methods
|
160
167
|
immediate = Module.new do
|
161
168
|
klass.immediate_methods.each do |method_name|
|
162
169
|
define_method(method_name) do |*args, &block|
|
163
170
|
response = catch :chain_stop do
|
164
|
-
@
|
171
|
+
@_decorum_chain_reference.send(__method__, *args, &block)
|
165
172
|
end
|
166
173
|
response.is_a?(Decorum::ChainStop) ? super(*args, &block) : response
|
167
174
|
end
|
@@ -169,30 +176,30 @@ module Decorum
|
|
169
176
|
end
|
170
177
|
extend immediate
|
171
178
|
end
|
172
|
-
|
173
|
-
@
|
179
|
+
_decorum_raw_decorators!
|
180
|
+
@_decorum_chain_reference
|
174
181
|
end
|
175
182
|
|
176
|
-
def
|
177
|
-
if decorator.is_a?(CallableDecorator)
|
178
|
-
decorator = decorator.
|
183
|
+
def _remove_from_decorum_chain(decorator)
|
184
|
+
if decorator.is_a?(Decorum::CallableDecorator)
|
185
|
+
decorator = decorator._actual_decorator
|
179
186
|
end
|
180
187
|
|
181
|
-
unless (decorator.is_a?(Decorum::Decorator) &&
|
188
|
+
unless (decorator.is_a?(Decorum::Decorator) && _decorum_raw_decorators.include?(decorator))
|
182
189
|
return nil
|
183
190
|
end
|
184
191
|
|
185
|
-
if decorator == @
|
186
|
-
@
|
192
|
+
if decorator == @_decorum_chain_reference
|
193
|
+
@_decorum_chain_reference = decorator.next_link
|
187
194
|
else
|
188
|
-
previous_decorator =
|
195
|
+
previous_decorator = _decorum_raw_decorators[_decorum_raw_decorators.index(decorator) - 1]
|
189
196
|
previous_decorator.next_link = decorator.next_link
|
190
197
|
end
|
191
198
|
|
192
|
-
unless
|
193
|
-
@
|
199
|
+
unless _decorum_raw_decorators!.map { |d| d.class }.include?(decorator.class)
|
200
|
+
@_decorum_decorated_state[decorator.class] = nil
|
194
201
|
end
|
195
|
-
|
202
|
+
_decorum_raw_decorators
|
196
203
|
end
|
197
204
|
end
|
198
205
|
end
|
data/lib/decorum/decorator.rb
CHANGED
@@ -5,9 +5,9 @@ module Decorum
|
|
5
5
|
alias_method :object, :root
|
6
6
|
|
7
7
|
def initialize(next_link, root, options)
|
8
|
-
@passed_options
|
9
|
-
@next_link
|
10
|
-
@root
|
8
|
+
@passed_options = options
|
9
|
+
@next_link = next_link
|
10
|
+
@root = root
|
11
11
|
@decorator_handle = options[:decorator_handle]
|
12
12
|
|
13
13
|
defaults = self.class.get_default_attributes
|
@@ -29,7 +29,7 @@ module Decorum
|
|
29
29
|
# a superhash of shared state between Decorators
|
30
30
|
# of the same class
|
31
31
|
def decorated_state
|
32
|
-
root.
|
32
|
+
root._decorum_decorated_state(self.class)
|
33
33
|
end
|
34
34
|
|
35
35
|
# Decorators that want stackable or cumulative
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# decorum/noaliases - this does all of the core setup, everything
|
2
|
+
# except aliasing public methods in Decorum::Decorations. provided
|
3
|
+
# separately so users can bypass the alias process entirely if desired.
|
4
|
+
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
superhash_class = if class_name = ENV['DECORUM_SUPERHASH_CLASS']
|
8
|
+
current = nil
|
9
|
+
class_name.split('::').each do |const_name|
|
10
|
+
modyool = current || Kernel
|
11
|
+
current = modyool.const_get(const_name)
|
12
|
+
end
|
13
|
+
current
|
14
|
+
else
|
15
|
+
OpenStruct
|
16
|
+
end
|
17
|
+
|
18
|
+
shared_state_class = if class_name = ENV['DECORUM_SHARED_STATE_CLASS']
|
19
|
+
current = nil
|
20
|
+
class_name.split('::').each do |const_name|
|
21
|
+
modyool = current || Kernel
|
22
|
+
current = modyool.const_get(const_name)
|
23
|
+
end
|
24
|
+
current
|
25
|
+
else
|
26
|
+
superhash_class
|
27
|
+
end
|
28
|
+
|
29
|
+
module Decorum
|
30
|
+
end
|
31
|
+
|
32
|
+
Decorum::SuperHash = superhash_class
|
33
|
+
Decorum::SharedState = shared_state_class
|
34
|
+
|
35
|
+
require_relative 'version'
|
36
|
+
require_relative 'decorations'
|
37
|
+
require_relative 'decorator'
|
38
|
+
require_relative 'decorated_state'
|
39
|
+
require_relative 'chain_stop'
|
40
|
+
require_relative 'bare_particular'
|
41
|
+
require_relative 'callable_decorator'
|
42
|
+
require_relative 'decorator_namespace'
|
data/lib/decorum/version.rb
CHANGED
@@ -4,9 +4,9 @@ describe "when Bob is ordering a cup of coffee" do
|
|
4
4
|
let(:coffee) { Decorum::Examples::Coffee.new }
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
coffee.
|
8
|
-
coffee.
|
9
|
-
coffee.
|
7
|
+
coffee._decorum_decorate(Decorum::Examples::MilkDecorator, animal: "cow", milk_type: "2 percent")
|
8
|
+
coffee._decorum_decorate(Decorum::Examples::MilkDecorator, animal: "cow", milk_type: "2 percent")
|
9
|
+
coffee._decorum_decorate(Decorum::Examples::SugarDecorator)
|
10
10
|
coffee.add_milk
|
11
11
|
coffee.add_sugar
|
12
12
|
end
|
@@ -22,7 +22,7 @@ describe "when Bob is ordering a cup of coffee" do
|
|
22
22
|
context "things get interesting" do
|
23
23
|
before(:each) do
|
24
24
|
["bear", "man", "pig"].each do |critter|
|
25
|
-
coffee.
|
25
|
+
coffee._decorum_decorate(Decorum::Examples::MilkDecorator, animal: critter)
|
26
26
|
end
|
27
27
|
coffee.add_milk
|
28
28
|
end
|
@@ -8,18 +8,18 @@ describe "When overriding original methods with .immediate" do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it "overrides original methods" do
|
11
|
-
base_object.
|
11
|
+
base_object._decorum_decorate(Decorum::Examples::StrongWilledDecorator)
|
12
12
|
expect(base_object.method_in_question).to eq("overridden")
|
13
13
|
end
|
14
14
|
|
15
15
|
it "reverts to original method when decorator is unloaded" do
|
16
|
-
base_object.
|
17
|
-
base_object.
|
16
|
+
base_object._decorum_decorate(Decorum::Examples::StrongWilledDecorator)
|
17
|
+
base_object._decorum_undecorate(base_object._decorum_decorators.first)
|
18
18
|
expect(base_object.method_in_question).to eq("original")
|
19
19
|
end
|
20
20
|
|
21
21
|
it "stops chain on vanished method" do
|
22
|
-
base_object.
|
22
|
+
base_object._decorum_decorate(Decorum::Examples::StrongWilledDecorator)
|
23
23
|
# raise on violated assumptions rather than have multiple conditions in the same test?
|
24
24
|
resp = base_object.second_immediate_method
|
25
25
|
unless resp == "method dos"
|
@@ -27,17 +27,17 @@ describe "When overriding original methods with .immediate" do
|
|
27
27
|
raise bail_message
|
28
28
|
end
|
29
29
|
|
30
|
-
base_object.
|
30
|
+
base_object._decorum_undecorate(base_object._decorum_decorators.first)
|
31
31
|
expect(base_object.second_immediate_method).to eq("class method_missing")
|
32
32
|
end
|
33
33
|
|
34
34
|
it "recurses" do
|
35
|
-
4.times { base_object.
|
35
|
+
4.times { base_object._decorum_decorate(Decorum::Examples::ImmediateDecorator) }
|
36
36
|
expect(base_object.increment_immediately_shared).to eq(4)
|
37
37
|
end
|
38
38
|
|
39
39
|
it "recurses on namespaced decorator" do
|
40
|
-
4.times { base_object.
|
40
|
+
4.times { base_object._decorum_decorate(Decorum::Examples::ImmediateDecorator, namespace: :foo) }
|
41
41
|
expect(base_object.foo.increment_immediately_shared).to eq(4)
|
42
42
|
end
|
43
43
|
end
|
@@ -8,8 +8,8 @@ module Decorum
|
|
8
8
|
true
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
@
|
11
|
+
def _decorum_decorated_state(*args)
|
12
|
+
@_decorum_decorated_state ||= Decorum::Spec::Decorator::DecoratedStateStub.new
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -4,7 +4,7 @@ describe Decorum::CallableDecorator do
|
|
4
4
|
let(:decorator) do
|
5
5
|
c = Decorum::Examples::Coffee.new
|
6
6
|
decorator = nil
|
7
|
-
c.
|
7
|
+
c._decorum_decorate(Decorum::Examples::MilkDecorator) { |d| decorator = d }
|
8
8
|
decorator
|
9
9
|
end
|
10
10
|
|
@@ -6,39 +6,39 @@ 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
|
9
|
+
context 'loading decorators from class defaults via .decorum' do
|
10
10
|
let(:klass) do
|
11
11
|
Class.new do
|
12
12
|
include Decorum::Decorations
|
13
|
-
|
13
|
+
decorum Decorum::Spec::Decorations::ClassSpecifiedDecoratorOne, { passed_option: "one" },
|
14
14
|
Decorum::Spec::Decorations::ClassSpecifiedDecoratorTwo, { passed_option: "two" }
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
describe '.
|
18
|
+
describe '.decorum' do
|
19
19
|
it 'stores decorators in own state correctly' do
|
20
|
-
expect(klass.
|
20
|
+
expect(klass.decorum.map { |d| d[0].ancestors.include?(Decorum::Decorator) }.inject(:&)).to be true
|
21
21
|
end
|
22
22
|
|
23
23
|
# the instance method #load_decorators_from_class will insert them in the order given:
|
24
24
|
|
25
25
|
it 'normally gives first priority to last listed' do
|
26
|
-
expect(klass.
|
26
|
+
expect(klass.decorum.map { |d| d[1] } == [{ passed_option: "one" }, { passed_option: "two" }]).to be true
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'reverses order for first-specfied priority' do
|
30
30
|
klass.decorators :reverse
|
31
|
-
expect(klass.
|
31
|
+
expect(klass.decorum.map { |d| d[1] } == [{ passed_option: "two" }, { passed_option: "one" }]).to be true
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'rejects malformed options' do
|
35
|
-
expect { klass.
|
35
|
+
expect { klass.decorum(Decorum::Spec::Decorations::FirstDecorator, "invalid decorator argument") }.to raise_error
|
36
36
|
end
|
37
37
|
|
38
38
|
# this probably belongs with other instance methods below, but we've got the
|
39
39
|
# support objects built for it here...
|
40
40
|
describe '#decorators' do
|
41
|
-
let(:obj) { klass.new.
|
41
|
+
let(:obj) { klass.new._decorum_load_decorators_from_class }
|
42
42
|
it 'returns self' do
|
43
43
|
# sort of
|
44
44
|
expect(obj.is_a?(klass)).to be true
|
@@ -69,71 +69,71 @@ describe Decorum::Decorations do
|
|
69
69
|
expect(decorated.undecorated_method).to be true
|
70
70
|
end
|
71
71
|
|
72
|
-
describe '#
|
72
|
+
describe '#_decorum_is_decorated?' do
|
73
73
|
it 'is false' do
|
74
|
-
expect(decorated.
|
74
|
+
expect(decorated._decorum_is_decorated?).to be false
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
describe '#
|
78
|
+
describe '#_decorum_decorated_state' do
|
79
79
|
it 'returns a hash' do
|
80
|
-
blank_state = decorated.
|
80
|
+
blank_state = decorated._decorum_decorated_state
|
81
81
|
expect(blank_state.is_a?(Hash)).to be true
|
82
82
|
end
|
83
83
|
|
84
84
|
it 'returns an empty hash' do
|
85
|
-
blank_state = decorated.
|
85
|
+
blank_state = decorated._decorum_decorated_state
|
86
86
|
expect(blank_state.empty?).to be true
|
87
87
|
end
|
88
88
|
|
89
89
|
it 'returns nil given an argument' do
|
90
|
-
expect(decorated.
|
90
|
+
expect(decorated._decorum_decorated_state(:not_gonna_work)).to be_nil
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
describe '
|
94
|
+
describe '#_decorum_undecorate' do
|
95
95
|
it 'returns self on symbol' do
|
96
|
-
expect(decorated.
|
96
|
+
expect(decorated._decorum_undecorate(:symbol_arg)).to be_equal(decorated)
|
97
97
|
end
|
98
98
|
|
99
99
|
it 'returns self on spurious decorator' do
|
100
|
-
expect(decorated.
|
100
|
+
expect(decorated._decorum_undecorate(deco_class_1)).to be_equal(decorated)
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
105
|
context 'decorated' do
|
106
|
-
describe '#
|
106
|
+
describe '#_decorum_decorate' do
|
107
107
|
it 'returns self on decoration' do
|
108
108
|
real_decorated = decorated
|
109
|
-
expect(decorated.
|
109
|
+
expect(decorated._decorum_decorate(deco_class_1)).to be_equal(real_decorated)
|
110
110
|
end
|
111
111
|
|
112
112
|
it 'calls #post_decorate' do
|
113
|
-
expect(decorated.
|
113
|
+
expect(decorated._decorum_decorate(deco_class_1).post_decorated).to eq("decorated")
|
114
114
|
end
|
115
115
|
|
116
116
|
it 'yields decorator if block_given?' do
|
117
117
|
decorator = nil
|
118
|
-
decorated.
|
119
|
-
actual_decorator = decorated.instance_variable_get(:@
|
120
|
-
expect(decorator.
|
118
|
+
decorated._decorum_decorate(deco_class_1) { |dec| decorator = dec }
|
119
|
+
actual_decorator = decorated.instance_variable_get(:@_decorum_chain_reference)
|
120
|
+
expect(decorator._actual_decorator).to be(actual_decorator)
|
121
121
|
end
|
122
122
|
|
123
123
|
context 'success' do
|
124
124
|
before(:each) do
|
125
125
|
decorator_options = { decorator_handle: "first", shared_attribute: "shared 1", local_attribute: "local 1" }
|
126
|
-
decorated.
|
126
|
+
decorated._decorum_decorate(deco_class_1, decorator_options)
|
127
127
|
end
|
128
128
|
|
129
|
-
describe '#
|
129
|
+
describe '#_decorum_is_decorated?' do
|
130
130
|
it 'returns true' do
|
131
|
-
expect(decorated.
|
131
|
+
expect(decorated._decorum_is_decorated?).to be true
|
132
132
|
end
|
133
133
|
|
134
134
|
it 'returns false after unloading' do
|
135
|
-
decorated.
|
136
|
-
expect(decorated.
|
135
|
+
decorated._decorum_undecorate(decorated._decorum_decorators.first)
|
136
|
+
expect(decorated._decorum_is_decorated?).to be false
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
@@ -142,7 +142,7 @@ describe Decorum::Decorations do
|
|
142
142
|
end
|
143
143
|
|
144
144
|
it 'sets internal state' do
|
145
|
-
internal_state = decorated.instance_variable_get(:@
|
145
|
+
internal_state = decorated.instance_variable_get(:@_decorum_chain_reference)
|
146
146
|
expect(internal_state.is_a?(deco_class_1)).to be true
|
147
147
|
end
|
148
148
|
|
@@ -155,7 +155,7 @@ describe Decorum::Decorations do
|
|
155
155
|
end
|
156
156
|
|
157
157
|
it 'raises NoMethodError on nonexistent method' do
|
158
|
-
expect { decorated.
|
158
|
+
expect { decorated.this_method_absolutely_doesnt_exist }.to raise_error(NoMethodError)
|
159
159
|
end
|
160
160
|
|
161
161
|
it 'is decorated by local attributes' do
|
@@ -167,14 +167,14 @@ describe Decorum::Decorations do
|
|
167
167
|
end
|
168
168
|
|
169
169
|
it 'creates decorated state for decorator class' do
|
170
|
-
expect(decorated.
|
170
|
+
expect(decorated._decorum_decorated_state(deco_class_1).shared_attribute).to eq('shared 1')
|
171
171
|
end
|
172
172
|
|
173
173
|
context 'with multiple decorators' do
|
174
174
|
before(:each) do
|
175
175
|
3.times do |i|
|
176
176
|
klass = send(:"deco_class_#{i + 1}")
|
177
|
-
decorated.
|
177
|
+
decorated._decorum_decorate(klass)
|
178
178
|
end
|
179
179
|
end
|
180
180
|
|
@@ -193,21 +193,21 @@ describe Decorum::Decorations do
|
|
193
193
|
|
194
194
|
context 'failure' do
|
195
195
|
it 'rejects classes that are not Decorators' do
|
196
|
-
expect { decorated.
|
196
|
+
expect { decorated._decorum_decorate(Class, {}) }.to raise_error(TypeError)
|
197
197
|
end
|
198
198
|
|
199
199
|
it 'rejects non unique decorator handles' do
|
200
200
|
3.times do |i|
|
201
201
|
klass = send(:"deco_class_#{i + 1}")
|
202
|
-
decorated.
|
202
|
+
decorated._decorum_decorate(klass, decorator_handle: "deco-#{i + 1}")
|
203
203
|
end
|
204
|
-
expect { decorated.
|
204
|
+
expect { decorated._decorum_decorate(deco_class_1, decorator_handle: "deco-2") }.to raise_error(RuntimeError)
|
205
205
|
end
|
206
206
|
end
|
207
207
|
end
|
208
208
|
|
209
209
|
describe '#respond_to?' do
|
210
|
-
before(:each) { decorated.
|
210
|
+
before(:each) { decorated._decorum_decorate(deco_class_1) }
|
211
211
|
|
212
212
|
it 'returns true for decorated methods' do
|
213
213
|
expect(decorated.respond_to?(:first_decorator_method)).to be true
|
@@ -218,7 +218,7 @@ describe Decorum::Decorations do
|
|
218
218
|
end
|
219
219
|
|
220
220
|
it 'returns false for undefined method' do
|
221
|
-
expect(decorated.respond_to?(:
|
221
|
+
expect(decorated.respond_to?(:this_method_absolutely_doesnt_exist)).to be false
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
@@ -226,31 +226,31 @@ describe Decorum::Decorations do
|
|
226
226
|
before(:each) do
|
227
227
|
3.times do |x|
|
228
228
|
klass = send(:"deco_class_#{x + 1}")
|
229
|
-
decorated.
|
229
|
+
decorated._decorum_decorate(klass, decorator_handle: "deco-#{x + 1}")
|
230
230
|
end
|
231
231
|
end
|
232
232
|
|
233
|
-
describe '#
|
233
|
+
describe '#_decorum_decorators' do
|
234
234
|
it 'accurately reflects loaded decorators and in order' do
|
235
235
|
expected_map = ["deco-3", "deco-2", "deco-1"]
|
236
|
-
expect(decorated.
|
236
|
+
expect(decorated._decorum_decorators.map { |d| d.decorator_handle }).to eq(expected_map)
|
237
237
|
end
|
238
238
|
|
239
239
|
it 'memoizes decorators' do
|
240
|
-
decorated.
|
241
|
-
expect(decorated.
|
240
|
+
decorated._decorum_decorators[0].next_link = decorated._decorum_decorators[2]
|
241
|
+
expect(decorated._decorum_decorators.length).to be(3)
|
242
242
|
end
|
243
243
|
|
244
|
-
it 'refreshes via #
|
245
|
-
decorated.
|
246
|
-
decorated.send(:
|
247
|
-
expect(decorated.
|
244
|
+
it 'refreshes via #_decorum_raw_decorators!' do
|
245
|
+
decorated._decorum_raw_decorators[0].next_link = decorated._decorum_raw_decorators[2]
|
246
|
+
decorated.send(:_decorum_raw_decorators!)
|
247
|
+
expect(decorated._decorum_decorators.length).to be(2)
|
248
248
|
end
|
249
249
|
end
|
250
250
|
|
251
|
-
describe '#
|
251
|
+
describe '#_decorum_undecorate' do
|
252
252
|
before(:each) do
|
253
|
-
@undec = decorated.
|
253
|
+
@undec = decorated._decorum_decorators.detect { |d| d.decorator_handle == "deco-2" }
|
254
254
|
# just to make sure...
|
255
255
|
unless @undec.is_a?(Decorum::CallableDecorator)
|
256
256
|
raise "broken test---no such decorator deco-2; undec was #{@undec.inspect}"
|
@@ -258,23 +258,23 @@ describe Decorum::Decorations do
|
|
258
258
|
end
|
259
259
|
|
260
260
|
it 'undecorates' do
|
261
|
-
decorated.
|
262
|
-
expect(decorated.
|
261
|
+
decorated._decorum_undecorate(@undec)
|
262
|
+
expect(decorated._decorum_decorators.length).to be(2)
|
263
263
|
end
|
264
264
|
|
265
265
|
it 'undecorates the right one' do
|
266
|
-
decorated.
|
266
|
+
decorated._decorum_undecorate(@undec)
|
267
267
|
expected_map = ["deco-3", "deco-1"]
|
268
|
-
expect(decorated.
|
268
|
+
expect(decorated._decorum_decorators.map { |d| d.decorator_handle }).to eq(expected_map)
|
269
269
|
end
|
270
270
|
|
271
271
|
it 'returns self on success' do
|
272
272
|
real_decorated = decorated
|
273
|
-
expect(decorated.
|
273
|
+
expect(decorated._decorum_undecorate(@undec)).to be_equal(real_decorated)
|
274
274
|
end
|
275
275
|
|
276
276
|
context 'once undecorated' do
|
277
|
-
before(:each) { decorated.
|
277
|
+
before(:each) { decorated._decorum_undecorate(@undec) }
|
278
278
|
|
279
279
|
it 'no longer responds to removed decorated method' do
|
280
280
|
expect(decorated.respond_to?(:second_decorator_method)).to be false
|
@@ -289,11 +289,11 @@ describe Decorum::Decorations do
|
|
289
289
|
end
|
290
290
|
|
291
291
|
it 'destroys shared state when last class member is gone' do
|
292
|
-
expect(decorated.
|
292
|
+
expect(decorated._decorum_decorated_state(deco_class_2)).to be_nil
|
293
293
|
end
|
294
294
|
|
295
295
|
it 'does not destroy other shared state' do
|
296
|
-
expect(decorated.
|
296
|
+
expect(decorated._decorum_decorated_state(deco_class_1)).to be_a(Decorum::DecoratedState)
|
297
297
|
end
|
298
298
|
end
|
299
299
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decorum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Cameron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,6 +75,7 @@ files:
|
|
75
75
|
- examples/sugar_decorator.rb
|
76
76
|
- examples/weak_willed_class.rb
|
77
77
|
- lib/decorum.rb
|
78
|
+
- lib/decorum/aliases.rb
|
78
79
|
- lib/decorum/bare_particular.rb
|
79
80
|
- lib/decorum/callable_decorator.rb
|
80
81
|
- lib/decorum/chain_stop.rb
|
@@ -82,6 +83,7 @@ files:
|
|
82
83
|
- lib/decorum/decorations.rb
|
83
84
|
- lib/decorum/decorator.rb
|
84
85
|
- lib/decorum/decorator_namespace.rb
|
86
|
+
- lib/decorum/noaliases.rb
|
85
87
|
- lib/decorum/version.rb
|
86
88
|
- spec/integration/coffee_spec.rb
|
87
89
|
- spec/integration/fibonacci_spec.rb
|