decorum 0.4.1 → 0.5.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.
- 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
|