decorations 1.0.1 → 1.1.0

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