decorum 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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