decorations 1.0.1 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d114bfb9a30d1f5a382ea785ed9989beb4b02cdeb9961596cdaee14c1a9d74e
4
- data.tar.gz: fbe4643ebba0cf10637fd5784d1c891494eec3b1657768f8e51b311ca4f9727e
3
+ metadata.gz: 19f65ba587fed4254a671f731a5ff34c0b477d16d72816fb70b75990b1677476
4
+ data.tar.gz: 13180ddcd9a0bfbcd88a31eeb86c085c912fd84d8a834724332c7afd34922357
5
5
  SHA512:
6
- metadata.gz: 736e20f6624e3043951d689a78d69acf531928e9ae77544eab1e763975be528d44c9ceea6319339d8f542bd2ce649b8722785d8a27b9a948356b9871ff8075b1
7
- data.tar.gz: 8fb9c4dc70da6d5faf762ec3ec9e99cb91a07d8beb58f45c8c803eaafbacd18c9d6011ba850b0b97497960804c0d3fc716e736a9dc89d3cfa7eb17c3cc8ae69f
6
+ metadata.gz: 4dea09bed3cd7f8a2409b11a5d8fa8cc34a23fd5b205aabc4a275e51d2f37b6759aa789980742e96c7dc9bbed438b7c944bf5178be2ebee409453c9f947d326e
7
+ data.tar.gz: 073f81ea30c740401218cd8d02c4e0e483170c82587de4e295b9ecb644ae32ef1a5117cd80cbdab9ce395e2509d5479656b42f11d57de01a85a0bc4e612979bd
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decorations (1.0.1)
4
+ decorations (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/decorations.svg)](https://badge.fury.io/rb/decorations)
4
4
  [![Build Status](https://travis-ci.org/watmin/Ruby-decorations.svg?branch=master)](https://travis-ci.org/watmin/Ruby-decorations)
5
5
 
6
- Python like decorators for Ruby. Inspired by Rack and previous attempts at decorations
6
+ Python like decorators for Ruby. Inspired by Rack, Rails and previous attempts at decorations. Only uses Ruby's standard library.
7
7
 
8
8
  ## Installation
9
9
 
@@ -44,6 +44,21 @@ class LoggingDecorator < Decorator
44
44
  end
45
45
  end
46
46
 
47
+ class LoggingAroundDecorator < Decorator
48
+ around
49
+ def print_start_finish
50
+ @start_time = Time.now
51
+ puts "[#{@start_time}] #{decorated_class}.#{decorated_method.name} was called"
52
+
53
+ ret = yield
54
+
55
+ end_time = Time.now
56
+ puts "[#{end_time}] #{decorated_class}.#{decorated_method.name} has finished. Took #{end_time - @start_time} seconds"
57
+
58
+ ret
59
+ end
60
+ end
61
+
47
62
  class Application
48
63
  extend Decorations
49
64
 
@@ -52,6 +67,11 @@ class Application
52
67
  a + b
53
68
  end
54
69
 
70
+ decorate LoggingAroundDecorator
71
+ def perform_task(a, b: 2)
72
+ a + b
73
+ end
74
+
55
75
  decorate LoggingDecorator
56
76
  def perform_task_with_a_block
57
77
  yield
@@ -64,6 +84,11 @@ app.perform_task(2, b: 2)
64
84
  # [2019-11-01 19:50:59 -0700] Application.perform_task has finished. Took 3.51e-05 seconds
65
85
  # => 4
66
86
 
87
+ app.perform_another_task(2, b: 2)
88
+ # [2019-11-02 15:32:19 -0700] Application.perform_another_task was called
89
+ # [2019-11-02 15:32:19 -0700] Application.perform_another_task has finished. Took 3.04e-05 seconds
90
+ # => 4
91
+
67
92
  app.perform_task_with_a_block { puts 'in a block' }
68
93
  # [2019-11-01 19:55:37 -0700] Application.perform_task_with_a_block was called
69
94
  # in a block
data/bin/console CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'decorations'
5
+ require 'decorators/all'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -43,5 +44,27 @@ class Demo
43
44
  end
44
45
  end
45
46
 
47
+
48
+ class HasIssues
49
+ extend Decorations
50
+
51
+ def initialize(starting_value)
52
+ @starting_value = starting_value
53
+ end
54
+
55
+ decorate RetryDecorator, tries: 3, backoff: true
56
+ def do_thing
57
+ loop do
58
+ @starting_value += 1
59
+ break if @starting_value % 3 == 0
60
+
61
+ raise StandardError, 'something is wrong'
62
+ end
63
+
64
+ puts 'it works'
65
+ 9
66
+ end
67
+ end
68
+
46
69
  require 'pry'
47
70
  Pry.start
data/lib/decorations.rb CHANGED
@@ -48,29 +48,28 @@ module Decorations
48
48
  # # => am I decorated?
49
49
  #
50
50
  # @api public
51
- def decorate(klass, *args)
51
+ def decorate(klass, *args, &block)
52
52
  @decorators ||= []
53
- @decorators << [klass, args]
53
+ @decorators << { klass: klass, args: args, block: block }
54
54
  end
55
55
 
56
56
  private
57
57
 
58
58
  ##
59
- # Appends the decorators to the decorated_methods hash
59
+ # Builds an array of decorators for the method
60
60
  #
61
61
  # @param name [Symbol] the method name being decorated
62
- # @param decorators [Array<Decorator>] the decorators defined
63
- # @param decorated_methods [Hash] the decorated methods for the class
64
62
  #
65
- # @return [Void]
63
+ # @return [Array<Decorator>]
66
64
  #
67
65
  # @api private
68
- def append_decorations(name, decorators, decorated_methods)
69
- decorators.each do |klass, args|
70
- decoration = klass.new(*args)
71
- decoration.__send__(:decorated_class=, self)
72
- decoration.__send__(:decorated_method=, instance_method(name))
73
- decorated_methods[name] << decoration
66
+ def build_decorators(name)
67
+ decorated_methods[name].map do |decoration|
68
+ decorator = decoration[:klass].new(*decoration[:args], &decoration[:block])
69
+ decorator.__send__(:decorated_class=, decoration[:decorated_class])
70
+ decorator.__send__(:decorated_method=, decoration[:decorated_method])
71
+
72
+ decorator
74
73
  end
75
74
  end
76
75
 
@@ -83,16 +82,20 @@ module Decorations
83
82
  #
84
83
  # @api private
85
84
  def method_added(name)
86
- return if @disabled
87
85
  return unless @decorators
88
86
 
89
87
  @decorated_methods ||= Hash.new { |h, k| h[k] = [] }
90
- append_decorations(name, @decorators, @decorated_methods)
88
+ @decorated_methods[name] = @decorators.map do |decorator|
89
+ decorator.merge(
90
+ decorated_class: self,
91
+ decorated_method: instance_method(name)
92
+ )
93
+ end
91
94
  @decorators = nil
92
95
 
93
96
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
94
97
  def #{name}(*args, &block)
95
- chain = self.class.decorated_methods[#{name.inspect}].dup
98
+ chain = self.class.__send__(:build_decorators, #{name.inspect})
96
99
  Decorator.new.__send__(:call, self, chain, *args, &block)
97
100
  end
98
101
  RUBY_EVAL
@@ -3,5 +3,5 @@
3
3
  module Decorations
4
4
  ##
5
5
  # The version of the Ruby-decorations gem
6
- VERSION = '1.0.1'
6
+ VERSION = '1.1.0'
7
7
  end
data/lib/decorator.rb CHANGED
@@ -39,18 +39,71 @@ class Decorator
39
39
  #
40
40
  # @api private
41
41
  def call(this, chain, *args, &block)
42
+ execute_before_methods
43
+ ret = execute_around_methods(build_next_caller(this, chain, *args, &block))
44
+ execute_after_methods
45
+
46
+ ret
47
+ end
48
+
49
+ ##
50
+ # Produces a proc that invokes the next caller
51
+ #
52
+ # @param this [Instance] the instance of the object with the wrapper method
53
+ # @param chain [Array<Decorator>] the remaining decorators to be called
54
+ # @param args [Array<Object>] the arguments to pass to the wrapper method
55
+ # @param block [Proc] the block to pass to the wrapped method
56
+ #
57
+ # @return [Proc]
58
+ #
59
+ # @api private
60
+ def build_next_caller(this, chain, *args, &block)
42
61
  next_caller = chain.shift
43
62
 
44
- self.class.__send__(:before_method)&.each { |method_name| __send__(method_name) }
63
+ if next_caller.nil?
64
+ proc { decorated_method.bind(this).call(*args, &block) }
65
+ else
66
+ proc { next_caller.__send__(:call, this, chain, *args, &block) }
67
+ end
68
+ end
45
69
 
46
- ret = if next_caller.nil?
47
- decorated_method.bind(this).call(*args, &block)
48
- else
49
- next_caller.__send__(:call, this, chain, *args, &block)
50
- end
70
+ ##
71
+ # Executes the before methods
72
+ #
73
+ # @return [Void]
74
+ #
75
+ # @api private
76
+ def execute_before_methods
77
+ self.class.__send__(:before_method)&.each { |method_name| __send__(method_name) }
78
+ end
51
79
 
52
- self.class.__send__(:after_method)&.each { |method_name| __send__(method_name) }
80
+ ##
81
+ # Executes the around methods
82
+ #
83
+ # @param next_caller [Proc] the proc to execute the next caller
84
+ #
85
+ # @return [Object] the result of the next_caller
86
+ #
87
+ # @api private
88
+ def execute_around_methods(next_caller)
89
+ arounds = self.class.__send__(:around_method)
90
+ if arounds.is_a?(Set)
91
+ ret = nil
92
+ arounds.each { |method_name| ret = __send__(method_name) { next_caller.call } }
93
+ else
94
+ ret = next_caller.call
95
+ end
53
96
 
54
97
  ret
55
98
  end
99
+
100
+ ##
101
+ # Executes the after methods
102
+ #
103
+ # @return [Void]
104
+ #
105
+ # @api private
106
+ def execute_after_methods
107
+ self.class.__send__(:after_method)&.each { |method_name| __send__(method_name) }
108
+ end
56
109
  end
data/lib/decorator_dsl.rb CHANGED
@@ -17,7 +17,9 @@ module DecoratorDsl
17
17
  class << receiver
18
18
  private
19
19
 
20
- attr_accessor :before_method, :before_set, :after_method, :after_set
20
+ attr_accessor :before_method, :before_set,
21
+ :after_method, :after_set,
22
+ :around_method, :around_set
21
23
  end
22
24
  end
23
25
 
@@ -29,7 +31,7 @@ module DecoratorDsl
29
31
  # @example
30
32
  # class DecoratorDemo < Decorator
31
33
  # before :print_message
32
- # def print_message(this, chain, *args)
34
+ # def print_message
33
35
  # puts "'#{decorated_class}' has '#{decorated_method.name}' decorated"
34
36
  # end
35
37
  # end
@@ -62,7 +64,7 @@ module DecoratorDsl
62
64
  # @example
63
65
  # class DecoratorDemo < Decorator
64
66
  # after
65
- # def print_message(this, chain, *args)
67
+ # def print_message
66
68
  # puts "'#{decorated_class}' has '#{decorated_method.name}' decorated"
67
69
  # end
68
70
  # end
@@ -87,6 +89,41 @@ module DecoratorDsl
87
89
  @after_set = true
88
90
  end
89
91
 
92
+ ##
93
+ # Adds the next method to be hook into executing around decorated method
94
+ #
95
+ # @return [Void]
96
+ #
97
+ # @example
98
+ # class DecoratorDemo < Decorator
99
+ # around
100
+ # def print_message
101
+ # puts "'#{decorated_class}' has '#{decorated_method.name}' decorated"
102
+ # yield
103
+ # puts "'#{decorated_class}' has '#{decorated_method.name}' decorated"
104
+ # end
105
+ # end
106
+ #
107
+ # class Demo
108
+ # extend Decorations
109
+ #
110
+ # decorate DecoratorDemo
111
+ # def demo_method
112
+ # puts 'am I decorated?'
113
+ # end
114
+ # end
115
+ #
116
+ # demo = Demo.new
117
+ # demo.demo_method
118
+ # # => am I decorated?
119
+ # # => 'Demo' has 'demo_method' decorated
120
+ #
121
+ # @api public
122
+ def around
123
+ @around_method ||= Set.new
124
+ @around_set = true
125
+ end
126
+
90
127
  private
91
128
 
92
129
  ##
@@ -97,15 +134,20 @@ module DecoratorDsl
97
134
  # @return [Void]
98
135
  #
99
136
  # @api private
100
- def method_added(name)
137
+ def method_added(name) # rubocop:disable Metrics/MethodLength
101
138
  if before_set
102
139
  @before_set = false
103
140
  @before_method << name
104
141
  end
105
142
 
106
- return unless after_set
143
+ if after_set
144
+ @after_set = false
145
+ @after_method << name
146
+ end
147
+
148
+ return unless around_set
107
149
 
108
- @after_set = false
109
- @after_method << name
150
+ @around_set = false
151
+ @around_method << name
110
152
  end
111
153
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'retry_decorator'
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'decorator'
4
+
5
+ ##
6
+ # Simple retry decorator
7
+ class RetryDecorator < Decorator
8
+ ##
9
+ # Builds a new RetryDecorator
10
+ #
11
+ # @param tries [Integer] Number of retries to make
12
+ # @param from [Class] Exception class to recsue from
13
+ # @param backoff [Boolean] Whether or not sleep between retries
14
+ # @param sleep_duration [Integer] starting sleep value to increment
15
+ #
16
+ # @api private
17
+ def initialize(tries:, from: StandardError, backoff: false, sleep_duration: -1)
18
+ @tries = tries
19
+ @from = from
20
+ @backoff = backoff
21
+ @sleep_duration = sleep_duration
22
+ end
23
+
24
+ private
25
+
26
+ around
27
+ ##
28
+ # Peforms the retries around the decorated method
29
+ #
30
+ # @return [Void]
31
+ #
32
+ # @api private
33
+ def do_retries
34
+ yield
35
+ rescue @from => e
36
+ raise e unless (@tries -= 1).positive?
37
+
38
+ sleep (@sleep_duration += 1)**2 if @backoff # rubocop:disable Lint/ParenthesesAsGroupedExpression
39
+ retry
40
+ end
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decorations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Shields
@@ -160,6 +160,8 @@ files:
160
160
  - lib/decorations/version.rb
161
161
  - lib/decorator.rb
162
162
  - lib/decorator_dsl.rb
163
+ - lib/decorators/all.rb
164
+ - lib/retry_decorator.rb
163
165
  homepage: https://github.com/watmin/Ruby-decorations
164
166
  licenses:
165
167
  - MIT