decorum 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - jruby-head
4
+ - ruby-head
5
+ - 1.9.3
6
+ - 2.0.0
@@ -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](#tasteful_decorators). The constraints are designed to make decorators' role in your
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](#tasteful_decorators)." (Not meant to imply others are tacky. The name just stuck.)
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
- ### <a name="#tasteful_decorators"></a>Tasteful Decorators
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 app is structured. In general though, (I doubt this is news)
98
- it risks breaking encapsulation. Any code which stores direct references
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. In practice, this might be useful, (see below
146
- for how to instruct it otherwise) but from a design standpoint, it
147
- looks to me like an anti-pattern. The fact that the method _needs_
148
- overriding implies the original object doesn't have the relevant state to
149
- fulfill it. The method is now spread out over two classes, that of the original
150
- object and that of the decorator.
151
-
152
- The paradigm cases of decorators don't generally address this, either. Consider three
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. (The following case illustrates
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
- ## <a name="usage"></a> Usage
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 method is probably including
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-eviYou can insert this at whatever point in the dent functions.
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::SuperHash. (SuperHash
304
- is used for a few things---see the source. It's
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
- - Probably the most important thing is before/after callbacks for decoration
456
- and undecoration.
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
@@ -1 +1,4 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ task default: :spec
@@ -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
@@ -1,14 +1,39 @@
1
+ # decorum.rb
1
2
  require 'ostruct'
2
3
 
3
- module Decorum
4
- # or Hashr, or whatever---just run the test suite
5
- class SuperHash < OpenStruct
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
- require "decorum/version"
10
- require 'decorum/decorations'
11
- require 'decorum/decorator'
12
- require 'decorum/decorated_state'
13
- require 'decorum/chain_stop'
14
- require 'decorum/bare_particular'
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
@@ -1,7 +1,7 @@
1
1
  module Decorum
2
2
  class DecoratedState
3
3
  def initialize(options={})
4
- @shared_state = Decorum::SuperHash.new(options)
4
+ @shared_state = Decorum::SharedState.new(options)
5
5
  end
6
6
 
7
7
  # this is one of two areas---the other being
@@ -1,9 +1,33 @@
1
1
  module Decorum
2
2
  module Decorations
3
3
  def decorate(klass, options={})
4
- extend Decorum::Decorations::Intercept
5
- decorator = add_to_decorator_chain(klass, options)
6
- yield decorator if block_given?
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
- decorators.each { |d| return true if d.respond_to?(message) }
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.new("decorator chain needs a Decorator")
97
+ raise RuntimeError, "decorator chain needs a Decorator"
60
98
  end
61
99
 
62
100
  if options[:decorator_handle]
63
- current_names = decorators.map { |d| d.decorator_handle.to_sym }.compact
101
+ current_names = _decorators.map { |d| d.decorator_handle.to_sym }.compact
64
102
  if current_names.include?(options[:decorator_handle].to_sym)
65
- # is this a little harsh?
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
- return nil unless decorator.is_a?(Decorum::Decorator) && decorators.include?(decorator)
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 = decorators[decorators.index(decorator) - 1]
143
+ previous_decorator = _decorators[_decorators.index(decorator) - 1]
102
144
  previous_decorator.next_link = decorator.next_link
103
145
  end
104
146
 
105
- unless decorators!.map { |d| d.class }.include?(decorator.class)
147
+ unless _decorators!.map { |d| d.class }.include?(decorator.class)
106
148
  @_decorated_state[decorator.class] = nil
107
149
  end
108
- decorators
109
- end
110
-
111
- def decorators!
112
- @_decorators = nil
113
- decorators
150
+ _decorators
114
151
  end
115
152
  end
116
153
  end
@@ -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 (tsk tsk)
91
+ # public methods; use with no args to declare the entire interface
86
92
  def immediate(*method_names)
87
- @immediate_methods ||= []
88
- @immediate_methods += method_names
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
- attr_reader :immediate_methods
101
+ def immediate_methods
102
+ @all_immediate ? instance_methods(false) : (@immediate_methods || [])
103
+ end
92
104
  end
93
105
  end
94
106
  end
@@ -0,0 +1,13 @@
1
+ module Decorum
2
+ class DecoratorNamespace
3
+ include Decorum::Decorations
4
+
5
+ def initialize(root_object)
6
+ @_root_object = root_object
7
+ end
8
+
9
+ def method_missing(message, *args, &block)
10
+ @_root_object.send(message, *args, &block)
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module Decorum
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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,11 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class ChattyRootObjectStub
5
+ def method_missing(*args)
6
+ "root"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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.decorators[0].next_link = decorated.decorators[2]
177
- decorated.send(:decorators!)
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::Decorator)
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
@@ -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.2.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-01-19 00:00:00.000000000 Z
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