decorum 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # 0.2.0 / 2014-01-18
2
+
3
+ * [FEATURE] support/tests for immediate methods
4
+ * better README
5
+
6
+ # 0.0.1 / 2014-01-04
7
+
8
+ initial release
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Decorum
2
2
 
3
- Decorum implements lightweight decorators for Ruby, called "tasteful decorators." (See below.)
4
- It is very small, possibly very fast, and has no requirements outside of the standard library.
5
- Use it wherever.
3
+ Decorum implements lightweight decorators
4
+ for Ruby, called "tasteful decorators." (See below.) It is very small,
5
+ possibly very fast, and has no requirements outside of the standard
6
+ library. Use it wherever.
6
7
 
7
8
  ## Quick Start
8
9
  ```ruby
@@ -23,102 +24,204 @@ bp.respond_to?(:shoot_confetti) # ==> false
23
24
  bp.decorate(Confetti)
24
25
  bp.shoot_confetti # ==> "boom, yay"
25
26
  ```
27
+ ## About
26
28
 
27
- ## Rationale
29
+ [Skip to the action](#usage)
28
30
 
29
- Decorum decorators are in the mold of the traditional, Gang of Four style pattern, with
30
- a few additional conditions. They aren't a subtype of this pattern (for various reasons)
31
- but they agree in the following way: Decorator is a family of basic object oriented
32
- patterns which (a) are implemented with composition/delegation and (b) respect the original
33
- public interface of the objects being decorated. As such, they're suitable for use in
34
- any kind of Ruby program.
31
+ 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
33
+ overall object structure both clear and safe. More on these points below.
35
34
 
36
- ### Isn't a Decorator like a Presenter which is like an HTML macro?
35
+ - Object Identity: After you decorate an object, you're still dealing with that same
36
+ object.
37
+ - Defers to the original interface: by default, Decorum decorators will _not_ override
38
+ the decorated object's public methods. (Though you can instruct it to.) This is intentional.
39
+ - Respects existing overrides of `#method_missing`
40
+ - Decorators are unloadable
41
+
42
+ By adhering to these constraints, decorators tend to do the Right Thing, i.e, integrate into existing
43
+ 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.)
45
+
46
+ In addition, Decorum provides a few helpful features:
47
+
48
+ - Stackable decorators, with shared state
49
+ - Recursion, via `#decorated_tail`
50
+ - Intercept/change messages
51
+ - Build stuff entirely out of decorators
52
+
53
+ As an example of how this is in use right now, suppose you're interfacing a content
54
+ management system with an existing data application. You want to build a sidebar
55
+ of image links. The images are in the CMS, but their metadata are stored in the application.
56
+ You want those systems to stay uncoupled. You can use a decorator to slap the metadata
57
+ on the image at runtime, e.g.,:
58
+
59
+ ```ruby
60
+ 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
+ images = Cms::Images.where(identifier: image_collection.keys)
64
+
65
+ images.each do |img|
66
+ img.decorate(ImageMetaDecorator, image_collection[img.identifier])
67
+ end
68
+
69
+ images[0].url # ==> 'http://blah.foo'
70
+ images[0].alt # ==> 'The Blah Conglomerate'
71
+ ```
37
72
 
38
- In Blogylvania there is considerable disagreement about what these terms entail.
39
- [In RefineryCMS, for example](http://refinerycms.com/guides/extending-controllers-and-models-with-decorators),
40
- "decorating" a class means opening it up with a `class_eval`. (In this conception, the decorator isn't even
41
- an _object_, which is astonishing in Ruby.)
73
+ ### Isn't a Decorator like a Presenter which is like an HTML macro?
42
74
 
43
- I use the terms as follows: a "presenter" is an object which
44
- mediates between a model, controller, etc. and a view. A "decorator" is an object
45
- which answers messages ostensibly bound for another object, and either responds on its behalf or
46
- lets it do whatever it was going to in the first place.
75
+ In Blogylvania there is some disagreement
76
+ about what these terms entail. [For example, in
77
+ RefineryCMS](http://refinerycms.com/guides/extending-controllers-and-models-with-decorators),
78
+ "decorating" a class means opening it up with a `class_eval`. (In this
79
+ conception, the decorator isn't even an object, which is astonishing
80
+ in Ruby.) I use the terms as follows: a "presenter" is an object which
81
+ mediates between a model, controller, etc. and a view. A "decorator" is
82
+ an object which answers messages ostensibly bound for another object, and
83
+ either responds on its behalf or lets it do whatever it was going to in
84
+ the first place. Presenters may or may not be implemented as Decorators;
85
+ Decorators may or may not present.
47
86
 
48
- ### What's so special about these?
87
+ Like "traditional" (i.e., [Gang of Four](http://en.wikipedia.org/wiki/Design_Patterns)-style)
88
+ decorator patterns, Decorum is a general purpose, object-oriented tool. Use it wherever.
49
89
 
50
- Decorum decorators are like GoF decorators, but they are designed to satisfy two more constraints,
51
- _object identity_ and _implementation consistency._
90
+ ### <a name="#tasteful_decorators"></a>Tasteful Decorators
52
91
 
53
92
  #### Object Identity
54
93
 
55
- In the GoF scheme, you aren't dealing with the same object after decoration. That's fine if
56
- it's a totally anonymous interface, but if the identity of the object is significant---if
57
- calling objects are storing references to them, say---it can become a problem.
58
- For example, in a common Rails idiom, if you want to do this:
94
+ Decorators, as conceived of by GoF, Python, etc., masquerade as the
95
+ objects they decorate by responding to their interface. They are _not_ the
96
+ 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
99
+ to the original object will have to update them to get the decorated
100
+ behavior. For example, in a common Rails idiom, in order to do this:
59
101
 
60
102
  ```ruby
61
103
  render @user
62
104
  ```
63
105
 
64
- ...having already `@user = User.find(params[:id])`, you'd better remember to do this:
106
+ ...having already `@user = User.find(params[:id])`, you have to do this:
65
107
 
66
108
  ```ruby
67
109
  if latest_winners.include(@user.id)
68
110
  @user = FreeVacationCruiseDecorator.new(@user)
69
111
  end
70
112
  ```
71
- The controller has to update the reference for `@user` if it wants to decorate it.
72
- The model's decoration status has essentially become part of the controller's state.
73
113
 
114
+ `@user` is an instance variable of the controller, but it has to be
115
+ updated in order for the model to be decorated. In practical terms,
116
+ if you store multiple references to the same object, (say the
117
+ original object is in an array somewhere, in addition to `@user`)
118
+ you have to update both references to get consistent behavior. The model's
119
+ decoration status has essentially become part of the controller's state.
120
+
121
+ ```ruby
122
+ users.include?(@user) # ==> true
123
+ winning_users = users.map { |u| FreeVacationCruiseDecorator.new(u) }
124
+ decorated = winning_users.detect { |u| u.id == @user.id } # the decorated object---should be the "same" thing
125
+ decorated.destination # ==> "tahiti"
126
+ @user.destination # ==> NoMethodError
127
+ ```
74
128
  In Decorum, objects use decorator classes (descendents of Decorum::Decorator) to decorate themselves:
75
129
 
76
130
  ```ruby
77
- if latest_winners.include?(@user.id)
78
- @user.decorate(FreeVacationCruiseDecorator, because: "You are teh awesome!")
79
- @user.assault_with_flashing_gifs! # # ==>= that method wasn't there before!
131
+ users.each do |user|
132
+ user.decorate(FreeVacationCruiseDecorator, because: "You are teh awesome!")
80
133
  end
134
+
135
+ @user.assault_with_flashing_gifs! # ==> that method wasn't there before!
81
136
  ```
82
137
 
83
138
  The "decorated object" is the same old object, because it manages all of its
84
139
  state, including its decorations. References don't need to change,
85
140
  and state stays where it should.
86
141
 
87
- #### Implementation Consistency
88
-
89
- By design, Decorum decorators do not override the methods of the decorated object. In general,
90
- this seems like a bad practice. Obviously, there are edge cases, which is why this will probably
91
- appear as an option at some point, but only with scolding. Consider:
92
-
93
- - It risks breaking interfaces across a class. Even if the decorator respects the
94
- method type, objects which are ostensibly identical (same database id, say) may differ
95
- in their attributes. Suppose Bob's family calls him Skippy; with a NameDecorator, you could
96
- have weird conditions like this:
142
+ #### Defers to the original interface
143
+
144
+ 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
153
+ common examples:
154
+
155
+ - Adding a scrollbar to a window
156
+ - Adding milk to a cup of coffee
157
+ - Providing `#full_name` to an object that supplies `#first_name` and `#last_name`
158
+
159
+ In all of these cases, the decorators provide some new functionality;
160
+ they don't change the object's original implementation. Obviously, you
161
+ shouldn't rule it out just because the common examples don't have it,
162
+ but it's by no means an essential use of the pattern. And from a design
163
+ perspective, it's a red flag that concerns are becoming... unseparated.
164
+ (It also risks weird bugs by breaking transparency, i.e., you can have
165
+ cases where `a` and `b` are literally identical, but have different
166
+ attributes.)
167
+
168
+ When an object is decorated, Decorum inserts its own `#method_missing`
169
+ and `#respond_to_missing?` into the object's eigenclass. Decorum's `#method_missing`
170
+ is only consulted after the original object has abandoned the message. (When
171
+ overriding original methods with `immediate`, each method gets its own
172
+ redirect in the eigenclass as well, intercepting the message before the
173
+ original definition is found.)
174
+
175
+ #### Respects existing overrides of `#method_missing`
176
+
177
+ A sizeable amount of the world's total Ruby functionality is implemented
178
+ by overriding `#method_missing`, so decorators shouldn't get in the way.
179
+ Because Decorum intercepts messages in the objects eigenclass, it also
180
+ respects existing overrides. If the decorator chain doesn't claim the
181
+ message, `super` is called and lookup proceeds normally.
182
+
183
+ #### Unloadable decorators
184
+
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.)
97
188
 
98
189
  ```ruby
99
- work.bob == home.bob
100
- true
101
- work.bob.name == home.bob.name
102
- false
190
+ @bob.decorate(IsTheMole)
191
+ @bob.revoke_security_clearances
192
+ # ideally this would be called automatically by IsTheMole
193
+ # on decoration
194
+
195
+ class IsTheMole < Decorum::Decorators
196
+ def revoke_security_clearances
197
+ clearances = object.decorators.select { |d| d.is_a?(SecurityClearance) }
198
+ clearances.each { |clearance| object.undecorate(clearance) }
199
+ end
200
+ end
103
201
  ```
104
202
 
105
- That may sound academic, but imagine what tracking this bug down might be like.
203
+ ### Implementation
106
204
 
107
- - More importantly: the fact that the method _needs_ overriding implies
108
- the original object doesn't have the relevant state to fulfill it. This seems like evidence
109
- the method belonged in the decorator to begin with.
205
+ As in other implementations, Decorum decorators wrap another object
206
+ to which they forward unknown messages. In both cases, all decorators
207
+ other than the first wrap another decorator. Instead of wrapping the
208
+ original object however, the first decorator in Decorum wraps an instance
209
+ of Decorum::ChainStop. If a method reaches the bottom of the chain, this
210
+ object throws a signal back up the stack to Decorum's `#method_missing`,
211
+ which then calls `super.` (This is a throw/catch, not an exception,
212
+ which would be significantly slower.)
110
213
 
111
- The delegation system in Decorum gives first preference to the original object. The decorator chain
112
- is only consulted if the original object defers the request. GoF require that Decorators respect
113
- the object's original interface; you could say Decorum requires that they respect the original
114
- implementation as well.
214
+ See the source for more details.
115
215
 
116
- ### Tasteful Decorators
117
- Decorators which satisfy the conditions stated earlier plus these two are "tasteful decorators,"
118
- because they stay out of the way. (It's not a comment on other implementations. The name just
119
- stuck.)
216
+ ## <a name="usage"></a> Usage
120
217
 
121
- ## Usage
218
+ First, objects need to be decoratable. You can do this for a whole class,
219
+ by including Decorum::Decorations, or for a single object, by extending it.
220
+ (Note: this alone doesn't change the object's method lookup. It just makes
221
+ `#decorate` available on the object. The behavior is only changed when
222
+ `#decorate` is called.) The easiest method is probably including
223
+ Decorum::Decorations at whatever point(s) of the class hierarchy you
224
+ feel appropriate.
122
225
 
123
226
  ### Helpers
124
227
  The decorated object is accessible as either `#root` or `#object`. A helper method:
@@ -132,18 +235,14 @@ end
132
235
 
133
236
  class StyledNameDecorator < Decorum::Decorator
134
237
  def styled_name
135
- parts = [:fname, :lname, :array_of_middle_names, :array_of_styles].map do |m|
136
- root.send(m)
137
- end.flatten
138
-
139
- ProperOrderOfStyles.sort_and_join_this_madness(parts)
238
+ ProperOrderOfStyles.sort_and_join_this_madness(object)
140
239
  end
141
240
  end
142
241
 
143
242
  r = Royalty.find_by_palace_name(:bob)
144
243
  r.respond_to? :styled_name # ==> false
145
244
  r.decorate StyledNameDecorator
146
- r.styled_name # ==> "Duke Baron His Grace Most Potent Sir Percy Arnold Robert \"Bob\" Gorpthwaite, Esq."
245
+ r.styled_name # ==> "His Grace Most Potent Baron Sir Percy Arnold Robert \"Bob\" Gorpthwaite, Esq."
147
246
  ```
148
247
 
149
248
  A decorator that keeps state: (code for these is in Examples)
@@ -180,6 +279,16 @@ object; this shared state can be used for a number of purposes. Finally,
180
279
  `default_attributes` lets you set class-level defaults; these will be
181
280
  preempted by options passed to the constructor.
182
281
 
282
+ As a side note, you can disable another decorators methods thus:
283
+
284
+ ```ruby
285
+ class MethodDisabler < Decorum::Decorator
286
+ def method_to_be_disabled(*args)
287
+ throw :chain_stop, Decorum::ChainStop.new
288
+ end
289
+ end
290
+ ```
291
+
183
292
  ### Shared State
184
293
 
185
294
  When attributes are declared with `share` (or `accumulator`), they
@@ -187,7 +296,7 @@ are shared among all decorators of that class on a given object:
187
296
  if an object has three MilkDecorators, the `#milk_level`/`#milk_level=` methods
188
297
  literally access the same state on all three.
189
298
  In addition, you get `#milk_level?` and `#reset_milk_level` to
190
- perform self-evident functions.
299
+ perform self-eviYou can insert this at whatever point in the dent functions.
191
300
 
192
301
  Access to the shared state is proxied first through the root object,
193
302
  and then through an instance of Decorum::DecoratedState, before
@@ -202,19 +311,15 @@ other things:
202
311
  - Serialize it, stick it in an HTML `data` attribute, and use it
203
312
  to initailize Javascript applications
204
313
  - Store a Rails view context for rendering
205
-
206
- ...or for more esoteric purposes:
207
-
208
314
  - Provide context-specific response selections for decorators, e.g.,
209
315
  `return current_shared_responder.message(my_condition)`
210
- - Implement polymorphic factories as decorators by storing references to classes
211
316
 
212
317
  And so on.
213
318
 
214
319
  ### `#decorated_tail`
215
320
 
216
- How exactly did the first MilkDecorator know to keep passing `#add_milk`
217
- down the chain instead of returning, you ask? In general, the decision
321
+ How exactly did the first MilkDecorator pass `#add_milk`
322
+ down the chain instead of returning? In general, the decision
218
323
  whether to return directly or to pass the request down the chain for further
219
324
  input rests with the decorator itself. Cumulative decorators, like the milk example,
220
325
  can be implemented in Decorum with a form of tail recursion:
@@ -242,7 +347,7 @@ The state is saved, and because it's shared among all the MilkDecorators,
242
347
  the most recent one on the chain can service the getter method
243
348
  like a normal decorated attribute.
244
349
 
245
- For a standard demonstration of tail recursion in Decorum, see
350
+ For a demonstration of tail recursion in Decorum, see
246
351
  Decorum::Examples::FibonacciDecorator:
247
352
 
248
353
  ```ruby
@@ -291,15 +396,15 @@ value instead, enabling Chain of Responsibility-looking things like this:
291
396
  (sorry, no code in the examples for this one)
292
397
 
293
398
  ```ruby
294
- [ErrorHandler, SuccessHandler].each do |handler|
399
+ handlers = condition ? [ErrorA, SuccessA] : [ErrorB, SuccessB]
400
+ handlers.each do |handler|
295
401
  @agent.decorate(handler)
296
402
  end
297
- this_service = find_service_decorator(params) # # ==> SomeServiceHandler
403
+ this_service = determine_service_decorator(params) # # ==> SomeServiceHandler
298
404
  @agent.decorate(this_service)
299
405
  @agent.service_request(params)
300
406
 
301
407
  # meanwhile:
302
-
303
408
  class SomeServiceHandler < Decorum::Decorators
304
409
  def service_request
305
410
  status = perform_request_on(object)
@@ -316,7 +421,27 @@ You can now parameterize your responses based on whatever conditions you like,
316
421
  by loading different decorators before the request is serviced. If nobody claims
317
422
  the specialized method, your default will be returned instead.
318
423
 
319
- ### It's Decorators All the Way Down
424
+ ### Overriding existing methods
425
+
426
+ To give decorator methods preference over an objects existing methods (if you
427
+ must) declare the method `immediate`:
428
+
429
+ ```ruby
430
+ class StrongWilledDecorator < Decorum::Decorator
431
+ immediate :method_in_question
432
+
433
+ def method_in_question
434
+ "overridden"
435
+ end
436
+ end
437
+
438
+ x = WeakWilledClass.new
439
+ x.method_in_question # <== "original"
440
+ x.decorate(StrongWilledDecorator)
441
+ x.method_in_question # <== "overridden"
442
+ ```
443
+
444
+ ### Decorators All the Way Down
320
445
 
321
446
  Decorum includes a class called Decorum::BareParticular, which descends from
322
447
  SuperHash. You can initialize any values you like on it, call them as methods,
@@ -325,29 +450,30 @@ feature of this class is that it can be decorated, so you can create objects
325
450
  whose interfaces are defined entirely by their decorators, and which will
326
451
  return nil by default.
327
452
 
328
- ## to-do
453
+ ## To-do
329
454
  A few things I can imagine showing up soon:
455
+ - Probably the most important thing is before/after callbacks for decoration
456
+ and undecoration.
330
457
  - Namespaced decorators, probably showing up as a method on the root object,
331
458
  e.g., `object.my_namespace.namespaced_method`
332
459
  - Thread safety: probably not an issue if you're retooling your Rails helpers,
333
- but consider a use case like this:
460
+ but consider a case like this:
334
461
 
335
462
  ```ruby
336
- 10.times do
337
- my_decorator = nil # scope the name
338
- @object.decorate(RequestHandler) { |d| my_decorator = d }
463
+ 10.times do |i|
464
+ port = port_base + i # 3001, 3002, 3003...
465
+ @server.decorate(RequestHandler, port: port )
339
466
  Thread.new do
340
- my_decorator.listen_for_changes_to_shared_state(...)
467
+ @server.listen_for_changes_to_shared_state(port: port)
341
468
  end
342
469
  end
343
470
  ```
344
-
345
- - Easy subclassing of Decorum::DecoratedState
471
+ - Easy subclassing of Decorum::DecoratedState, so you can do wacky things with it
346
472
 
347
473
  &c. I'm open to suggestion.
348
474
 
349
475
  ## Contributing
350
- I wrote most of this super late at night, so that would be awesome:
476
+ I wrote most of this super late at night, (don't worry, the tests pass) so that would be awesome:
351
477
 
352
478
  1. Fork it
353
479
  2. Create your feature branch (`git checkout -b my-new-feature`)
data/decorum.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["erik.cameron@gmail.com"]
11
11
  spec.description = %q{Tasteful decorators for Ruby. Use it wherever.}
12
12
  spec.summary = %q{Decorum implements the Decorator pattern (more or less) in a fairly unobtrusive way.}
13
- spec.homepage = ""
13
+ spec.homepage = "http://erikcameron.github.io/"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -0,0 +1,25 @@
1
+ module Decorum
2
+ module Examples
3
+ class StrongWilledDecorator < Decorum::Decorator
4
+ immediate :method_in_question
5
+ immediate :second_immediate_method, :third_immediate_method
6
+ immediate :fourth_immediate_method
7
+
8
+ def method_in_question
9
+ "overridden"
10
+ end
11
+
12
+ def second_immediate_method
13
+ "method dos"
14
+ end
15
+
16
+ def third_immediate_method
17
+ "method tres"
18
+ end
19
+
20
+ def fourth_immediate_method
21
+ "method quatro"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ module Decorum
2
+ module Examples
3
+ class WeakWilledClass
4
+ include Decorum::Decorations
5
+
6
+ def method_in_question
7
+ "original"
8
+ end
9
+
10
+ def method_missing(*args)
11
+ "class method_missing"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -73,12 +73,27 @@ module Decorum
73
73
 
74
74
  base = @_decorator_chain || Decorum::ChainStop.new
75
75
  @_decorator_chain = klass.new(base, self, options)
76
+
77
+ if klass.immediate_methods
78
+ immediate = Module.new do
79
+ klass.immediate_methods.each do |method_name|
80
+ define_method(method_name) do |*args, &block|
81
+ response = catch :chain_stop do
82
+ @_decorator_chain.send(__method__, *args, &block)
83
+ end
84
+ response.is_a?(Decorum::ChainStop) ? super(*args, &block) : response
85
+ end
86
+ end
87
+ end
88
+ extend immediate
89
+ end
90
+
76
91
  decorators!
77
92
  @_decorator_chain
78
93
  end
79
94
 
80
95
  def remove_from_decorator_chain(decorator)
81
- return nil unless decorators.include?(decorator)
96
+ return nil unless decorator.is_a?(Decorum::Decorator) && decorators.include?(decorator)
82
97
 
83
98
  if decorator == @_decorator_chain
84
99
  @_decorator_chain = decorator.next_link
@@ -80,6 +80,15 @@ module Decorum
80
80
  def get_default_attributes
81
81
  @default_attributes || {}
82
82
  end
83
+
84
+ # allow Decorator classes to override the decorated object's
85
+ # public methods (tsk tsk)
86
+ def immediate(*method_names)
87
+ @immediate_methods ||= []
88
+ @immediate_methods += method_names
89
+ end
90
+
91
+ attr_reader :immediate_methods
83
92
  end
84
93
  end
85
94
  end
@@ -1,3 +1,3 @@
1
1
  module Decorum
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe "When overriding original methods with .immediate" do
4
+ let(:base_object) { Decorum::Examples::WeakWilledClass.new }
5
+
6
+ it "has an original method" do
7
+ expect(base_object.method_in_question).to eq("original")
8
+ end
9
+
10
+ it "overrides original methods" do
11
+ base_object.decorate(Decorum::Examples::StrongWilledDecorator)
12
+ expect(base_object.method_in_question).to eq("overridden")
13
+ end
14
+
15
+ it "reverts to original method when decorator is unloaded" do
16
+ base_object.decorate(Decorum::Examples::StrongWilledDecorator)
17
+ base_object.undecorate(base_object.decorators.first)
18
+ expect(base_object.method_in_question).to eq("original")
19
+ end
20
+
21
+ it "stops chain on vanished method" do
22
+ base_object.decorate(Decorum::Examples::StrongWilledDecorator)
23
+ # raise on violated assumptions rather than have multiple conditions in the same test?
24
+ resp = base_object.second_immediate_method
25
+ unless resp == "method dos"
26
+ bail_message = "Bad test data: base_object doesn't have #second_immediate_method, got #{resp}"
27
+ raise bail_message
28
+ end
29
+
30
+ base_object.undecorate(base_object.decorators.first)
31
+ expect(base_object.second_immediate_method).to eq("class method_missing")
32
+ end
33
+ end
@@ -15,6 +15,22 @@ describe Decorum::Decorator do
15
15
  it 'responds to .accumulator' do
16
16
  expect(Decorum::Decorator.respond_to?(:accumulator)).to be_true
17
17
  end
18
+
19
+ it 'responds to .default_attributes' do
20
+ expect(Decorum::Decorator.respond_to?(:default_attributes)).to be_true
21
+ end
22
+
23
+ it 'responds to .get_default_attributes' do
24
+ expect(Decorum::Decorator.respond_to?(:get_default_attributes)).to be_true
25
+ end
26
+
27
+ it 'responds to .immediate' do
28
+ expect(Decorum::Decorator.respond_to?(:immediate)).to be_true
29
+ end
30
+
31
+ it 'responds to .immediate_methods' do
32
+ expect(Decorum::Decorator.respond_to?(:immediate_methods)).to be_true
33
+ end
18
34
 
19
35
  describe '#decorated_state' do
20
36
  it 'defers to the root object' do
@@ -129,7 +145,7 @@ describe Decorum::Decorator do
129
145
  end
130
146
  end
131
147
 
132
- context 'when attributes via declared personally' do
148
+ context 'when attributes are declared personally' do
133
149
  describe '#setter' do
134
150
  it 'sets local attribute via initialize' do
135
151
  expect(decorator.name).to eq('bob')
@@ -163,4 +179,23 @@ describe Decorum::Decorator do
163
179
  expect(response).to be_a(Decorum::ChainStop)
164
180
  end
165
181
  end
182
+
183
+ context 'when methods are declared immediate' do
184
+ it 'includes them in @immediate_methods' do
185
+ expect(Decorum::Examples::StrongWilledDecorator.immediate_methods.include?(:method_in_question)).to be_true
186
+ end
187
+
188
+ it 'respects various forms of declaration' do
189
+ # i.e.:
190
+ # - it respects mulitple immediate declarations
191
+ # - it respects single methods or an array of methods
192
+ # see Examples::StrongWilledDecorator
193
+ methods = ["second", "third", "fourth"].map do |pre|
194
+ "#{pre}_immediate_method".to_sym
195
+ end
196
+
197
+ got_em = methods.map { |m| Decorum::Examples::StrongWilledDecorator.immediate_methods.include?(m) }.inject(:&)
198
+ expect(got_em).to be_true
199
+ end
200
+ end
166
201
  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.0.1
4
+ version: 0.2.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-04 00:00:00.000000000 Z
12
+ date: 2014-01-19 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
+ - CHANGELOG.md
70
71
  - Gemfile
71
72
  - LICENSE.txt
72
73
  - README.md
@@ -75,7 +76,9 @@ files:
75
76
  - examples/coffee.rb
76
77
  - examples/fibonacci_decorator.rb
77
78
  - examples/milk_decorator.rb
79
+ - examples/strong_willed_decorator.rb
78
80
  - examples/sugar_decorator.rb
81
+ - examples/weak_willed_class.rb
79
82
  - lib/decorum.rb
80
83
  - lib/decorum/bare_particular.rb
81
84
  - lib/decorum/chain_stop.rb
@@ -85,6 +88,7 @@ files:
85
88
  - lib/decorum/version.rb
86
89
  - spec/integration/coffee_spec.rb
87
90
  - spec/integration/fibonacci_spec.rb
91
+ - spec/integration/immediate_methods_spec.rb
88
92
  - spec/spec_helper.rb
89
93
  - spec/support/decorated_state/shared_state_stub.rb
90
94
  - spec/support/decorations/decorated_object_stub.rb
@@ -100,7 +104,7 @@ files:
100
104
  - spec/unit/decorated_state_spec.rb
101
105
  - spec/unit/decorations_spec.rb
102
106
  - spec/unit/decorator_spec.rb
103
- homepage: ''
107
+ homepage: http://erikcameron.github.io/
104
108
  licenses:
105
109
  - MIT
106
110
  post_install_message:
@@ -129,6 +133,7 @@ summary: Decorum implements the Decorator pattern (more or less) in a fairly uno
129
133
  test_files:
130
134
  - spec/integration/coffee_spec.rb
131
135
  - spec/integration/fibonacci_spec.rb
136
+ - spec/integration/immediate_methods_spec.rb
132
137
  - spec/spec_helper.rb
133
138
  - spec/support/decorated_state/shared_state_stub.rb
134
139
  - spec/support/decorations/decorated_object_stub.rb