decorum 0.3.0 → 0.4.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.
@@ -1,3 +1,8 @@
1
+ # 0.4.0 / 2014-12-26
2
+
3
+ * [FEATURE] specify default decorators in a class with .decorators
4
+ * [FEATURE] #is_decorated?
5
+
1
6
  # 0.3.0 / 2014-2-3
2
7
 
3
8
  * [FEATURE] directly callable decorators
data/README.md CHANGED
@@ -24,10 +24,16 @@ end
24
24
 
25
25
  bp = BirthdayParty.new
26
26
  bp.respond_to?(:shoot_confetti) # ==> false
27
+ bp.is_decorated? # ==> false
27
28
  bp.decorate(Confetti)
29
+ bp.is_decorated? # ==> true
28
30
  bp.shoot_confetti # ==> "boom, yay"
29
31
  ```
30
32
 
33
+ ## New in 0.4.0
34
+ - Decorators and their attributes may now be specified in class definitions, and loaded with `#load_decorators_from_class`
35
+ - `#is_decorated?`
36
+
31
37
  ## New in 0.3.0
32
38
  - Methods may be called directly on decorators
33
39
  - Decorator namespaces
@@ -196,7 +202,7 @@ original definition is found.)
196
202
  A sizeable amount of the world's total Ruby functionality is implemented
197
203
  by overriding `#method_missing`, so decorators shouldn't get in the way.
198
204
  Because Decorum intercepts messages in the objects eigenclass, it also
199
- respects existing overrides. If the decorator chain doesn't claim the
205
+ respects existing class-level overrides. If the decorator chain doesn't claim the
200
206
  message, `super` is called and lookup proceeds normally.
201
207
 
202
208
  #### Unloadable decorators
@@ -205,10 +211,10 @@ Decorators can be unloaded, if necessary:
205
211
 
206
212
  ```ruby
207
213
  @bob.decorate(IsTheMole)
208
- @bob.revoke_security_clearances
209
214
 
210
- class IsTheMole < Decorum::Decorators
211
- # callback
215
+ # meanwhile:
216
+ class IsTheMole < Decorum::Decorator
217
+ # hook method
212
218
  def post_decorate
213
219
  object.revoke_security_clearances
214
220
  end
@@ -219,6 +225,8 @@ class IsTheMole < Decorum::Decorators
219
225
  end
220
226
  end
221
227
  ```
228
+ This example also shows the use of the `#post_decorate` hook.
229
+
222
230
 
223
231
  ### Implementation
224
232
 
@@ -243,7 +251,7 @@ by including Decorum::Decorations, or for a single object, by extending it.
243
251
  Decorum::Decorations at whatever point(s) of the class hierarchy you
244
252
  feel appropriate.
245
253
 
246
- ### Helpers
254
+ ### Using Decorators
247
255
  The decorated object is accessible as either `#root` or `#object`. A helper method:
248
256
 
249
257
  ```ruby
@@ -299,6 +307,31 @@ object; this shared state can be used for a number of purposes. Finally,
299
307
  `default_attributes` lets you set class-level defaults; these will be
300
308
  preempted by options passed to the constructor.
301
309
 
310
+ #### Specifying decorators in classes
311
+
312
+ Decoration may be performed by the instance method `#decorate` above,
313
+ or you can include default decorators and arguments in your class
314
+ definition:
315
+
316
+ ```ruby
317
+ class Coffee
318
+ include Decorum::Decorations
319
+ # two bovine dairy no sugar by default, please:
320
+ decorators Milk, { animal: "cow" }, Milk, { animal: "cow" }
321
+ ...
322
+ end
323
+
324
+ c = Coffee.new
325
+ c.load_decorators_from_class
326
+ c.add_milk
327
+ c.milk_level # ==> 2
328
+ ```
329
+
330
+ Note that this usage does _not_ automatically include decorators on new
331
+ objects. That would require invasive procedures on your object initialization.
332
+ Instead, Decorum provides `#load_decorators_from_class`, which you can call
333
+ in your initializations, or later.
334
+
302
335
  As a side note, you can disable another decorators methods thus:
303
336
 
304
337
  ```ruby
@@ -334,7 +367,7 @@ other things:
334
367
 
335
368
  And so on.
336
369
 
337
- ### `#decorated_tail`
370
+ ### #decorated_tail
338
371
 
339
372
  How exactly did the first MilkDecorator pass `#add_milk`
340
373
  down the chain instead of returning? In general, the decision
@@ -1,5 +1,36 @@
1
1
  module Decorum
2
2
  module Decorations
3
+ def self.included(modyool)
4
+ # class method to declare default decorators
5
+ def modyool.decorators(*args)
6
+ # set first-listed priority
7
+ if args[0] == :reverse
8
+ return @_decorum_stack_reverse = true
9
+ end
10
+
11
+ @_decorum_stack ||= []
12
+
13
+ if !args.empty?
14
+ args.each do |arg|
15
+ if (arg.is_a?(Class) && arg.ancestors.include?(Decorum::Decorator)) || arg.is_a?(Hash)
16
+ next if arg.is_a?(Hash)
17
+ klass = arg
18
+ next_arg = args[args.index(arg) + 1]
19
+ options = next_arg.is_a?(Hash) ? next_arg : {}
20
+ @_decorum_stack << [klass, options]
21
+ else
22
+ raise ArgumentError, "invalid argument to #{self.to_s}.decorate_with: #{arg.to_s}"
23
+ end
24
+ end
25
+ else
26
+ @_decorum_stack_reverse ? @_decorum_stack.reverse : @_decorum_stack
27
+ end
28
+ end
29
+ end
30
+
31
+ # public instance methods
32
+
33
+
3
34
  def decorate(klass, options={})
4
35
  if namespace_method = options.delete(:namespace)
5
36
  decorator = nil
@@ -11,7 +42,7 @@ module Decorum
11
42
  end
12
43
  namespace.decorate(klass, options) { |d| decorator = d }
13
44
  else
14
- namespace = Decorum::DecoratorNamespace.new(self)
45
+ namespace = Decorum::DecoratorNamespace.new(self)
15
46
  namespace.decorate(klass, options) { |d| decorator = d }
16
47
  instance_variable_set(:"@_decorum_#{namespace_method}", namespace)
17
48
  m = Module.new do
@@ -20,6 +51,7 @@ module Decorum
20
51
  end
21
52
  end
22
53
  extend m
54
+ _decorator_namespaces << namespace_method
23
55
  end
24
56
  yield CallableDecorator.new(decorator) if block_given?
25
57
  else
@@ -35,13 +67,22 @@ module Decorum
35
67
  remove_from_decorator_chain(target)
36
68
  self
37
69
  end
38
-
39
70
 
71
+ def is_decorated?
72
+ ![ decorators, _decorator_namespaces.map { |ns| send(ns).decorators } ].flatten.empty?
73
+ end
74
+
40
75
  # returns callable decorators---use this
41
76
  def decorators
42
77
  _decorators.map { |d| CallableDecorator.new(d) }
43
78
  end
44
79
 
80
+ # leaving it to you to, say, call this from #initialize
81
+ def load_decorators_from_class
82
+ self.class.decorators.each { |decorator_class, options| decorate(decorator_class, options) }
83
+ self
84
+ end
85
+
45
86
  # returns raw decorators---don't use this unless
46
87
  # you know what you're doing
47
88
  def _decorators
@@ -75,6 +116,10 @@ module Decorum
75
116
  @_decorated_state
76
117
  end
77
118
  end
119
+
120
+ def _decorator_namespaces
121
+ @_decorator_namespaces ||= []
122
+ end
78
123
 
79
124
  module Decorum::Decorations::Intercept
80
125
  def method_missing(message, *args, &block)
@@ -90,11 +135,11 @@ module Decorum
90
135
  end
91
136
  end
92
137
 
93
- protected
138
+ private
94
139
 
95
140
  def add_to_decorator_chain(klass, options)
96
141
  unless klass.ancestors.include?(Decorum::Decorator)
97
- raise RuntimeError, "decorator chain needs a Decorator"
142
+ raise TypeError, "decorator chain needs a Decorator"
98
143
  end
99
144
 
100
145
  if options[:decorator_handle]
@@ -1,3 +1,3 @@
1
1
  module Decorum
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,21 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorations
4
+ class ClassSpecifiedDecoratorOne < Decorum::Decorator
5
+ attr_accessor :passed_option
6
+
7
+ def one
8
+ "one"
9
+ end
10
+ end
11
+
12
+ class ClassSpecifiedDecoratorTwo < ClassSpecifiedDecoratorOne
13
+ attr_accessor :passed_option
14
+
15
+ def two
16
+ "two"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -6,6 +6,55 @@ 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
10
+ let(:klass) do
11
+ Class.new do
12
+ include Decorum::Decorations
13
+ decorators Decorum::Spec::Decorations::ClassSpecifiedDecoratorOne, { passed_option: "one" },
14
+ Decorum::Spec::Decorations::ClassSpecifiedDecoratorTwo, { passed_option: "two" }
15
+ end
16
+ end
17
+
18
+ describe '.decorators' do
19
+ it 'stores decorators in own state correctly' do
20
+ expect(klass.decorators.map { |d| d[0].ancestors.include?(Decorum::Decorator) }.inject(:&)).to be_true
21
+ end
22
+
23
+ # the instance method #load_decorators_from_class will insert them in the order given:
24
+
25
+ it 'normally gives first priority to last listed' do
26
+ expect(klass.decorators.map { |d| d[1] } == [{ passed_option: "one" }, { passed_option: "two" }]).to be_true
27
+ end
28
+
29
+ it 'reverses order for first-specfied priority' do
30
+ klass.decorators :reverse
31
+ expect(klass.decorators.map { |d| d[1] } == [{ passed_option: "two" }, { passed_option: "one" }]).to be_true
32
+ end
33
+
34
+ it 'rejects malformed options' do
35
+ expect { klass.decorators(Decorum::Spec::Decorations::FirstDecorator, "invalid decorator argument") }.to raise_error
36
+ end
37
+
38
+ # this probably belongs with other instance methods below, but we've got the
39
+ # support objects built for it here...
40
+ describe '#decorators' do
41
+ let(:obj) { klass.new.load_decorators_from_class }
42
+ it 'returns self' do
43
+ # sort of
44
+ expect(obj.is_a?(klass)).to be_true
45
+ end
46
+
47
+ it 'loads all decorators given by .decorators' do
48
+ expect(obj.one == "one" && obj.two == "two").to be_true
49
+ end
50
+
51
+ it 'loads decorators in the order given by .decorators' do
52
+ expect(obj.passed_option == "two").to be_true
53
+ end
54
+ end
55
+ end
56
+ end
57
+
9
58
  context 'as-yet-undecorated' do
10
59
  # assert some basic assumptions
11
60
  it 'is decoratable' do
@@ -20,6 +69,12 @@ describe Decorum::Decorations do
20
69
  expect(decorated.undecorated_method).to be_true
21
70
  end
22
71
 
72
+ describe '#is_decorated?' do
73
+ it 'is false' do
74
+ expect(decorated.is_decorated?).to be_false
75
+ end
76
+ end
77
+
23
78
  describe '#decorated_state' do
24
79
  it 'returns a hash' do
25
80
  blank_state = decorated.decorated_state
@@ -41,7 +96,7 @@ describe Decorum::Decorations do
41
96
  expect(decorated.undecorate(:symbol_arg)).to be_equal(decorated)
42
97
  end
43
98
 
44
- it 'returns self on spurious Decorator' do
99
+ it 'returns self on spurious decorator' do
45
100
  expect(decorated.undecorate(deco_class_1)).to be_equal(decorated)
46
101
  end
47
102
  end
@@ -71,6 +126,17 @@ describe Decorum::Decorations do
71
126
  decorated.decorate(deco_class_1, decorator_options)
72
127
  end
73
128
 
129
+ describe '#is_decorated?' do
130
+ it 'returns true' do
131
+ expect(decorated.is_decorated?).to be_true
132
+ end
133
+
134
+ it 'returns false after unloading' do
135
+ decorated.undecorate(decorated.decorators.first)
136
+ expect(decorated.is_decorated?).to be_false
137
+ end
138
+ end
139
+
74
140
  it 'installs intercept' do
75
141
  expect(decorated.is_a?(Decorum::Decorations::Intercept)).to be_true
76
142
  end
@@ -127,7 +193,7 @@ describe Decorum::Decorations do
127
193
 
128
194
  context 'failure' do
129
195
  it 'rejects classes that are not Decorators' do
130
- expect { decorated.decorate(Class,{}) }.to raise_error(RuntimeError)
196
+ expect { decorated.decorate(Class,{}) }.to raise_error(TypeError)
131
197
  end
132
198
 
133
199
  it 'rejects non unique decorator handles' do
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.3.0
4
+ version: 0.4.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-02-04 00:00:00.000000000 Z
12
+ date: 2014-12-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -95,6 +95,7 @@ files:
95
95
  - spec/integration/immediate_methods_spec.rb
96
96
  - spec/spec_helper.rb
97
97
  - spec/support/decorated_state/shared_state_stub.rb
98
+ - spec/support/decorations/class_specified_decorator_one.rb
98
99
  - spec/support/decorations/decorated_object_stub.rb
99
100
  - spec/support/decorations/first_decorator.rb
100
101
  - spec/support/decorations/second_decorator.rb
@@ -144,6 +145,7 @@ test_files:
144
145
  - spec/integration/immediate_methods_spec.rb
145
146
  - spec/spec_helper.rb
146
147
  - spec/support/decorated_state/shared_state_stub.rb
148
+ - spec/support/decorations/class_specified_decorator_one.rb
147
149
  - spec/support/decorations/decorated_object_stub.rb
148
150
  - spec/support/decorations/first_decorator.rb
149
151
  - spec/support/decorations/second_decorator.rb