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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +26 -1
- data/bin/console +23 -0
- data/lib/decorations.rb +18 -15
- data/lib/decorations/version.rb +1 -1
- data/lib/decorator.rb +60 -7
- data/lib/decorator_dsl.rb +49 -7
- data/lib/decorators/all.rb +3 -0
- data/lib/retry_decorator.rb +41 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19f65ba587fed4254a671f731a5ff34c0b477d16d72816fb70b75990b1677476
|
4
|
+
data.tar.gz: 13180ddcd9a0bfbcd88a31eeb86c085c912fd84d8a834724332c7afd34922357
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dea09bed3cd7f8a2409b11a5d8fa8cc34a23fd5b205aabc4a275e51d2f37b6759aa789980742e96c7dc9bbed438b7c944bf5178be2ebee409453c9f947d326e
|
7
|
+
data.tar.gz: 073f81ea30c740401218cd8d02c4e0e483170c82587de4e295b9ecb644ae32ef1a5117cd80cbdab9ce395e2509d5479656b42f11d57de01a85a0bc4e612979bd
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[](https://badge.fury.io/rb/decorations)
|
4
4
|
[](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 <<
|
53
|
+
@decorators << { klass: klass, args: args, block: block }
|
54
54
|
end
|
55
55
|
|
56
56
|
private
|
57
57
|
|
58
58
|
##
|
59
|
-
#
|
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 [
|
63
|
+
# @return [Array<Decorator>]
|
66
64
|
#
|
67
65
|
# @api private
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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.
|
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
|
data/lib/decorations/version.rb
CHANGED
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
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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,
|
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
|
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
|
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
|
-
|
143
|
+
if after_set
|
144
|
+
@after_set = false
|
145
|
+
@after_method << name
|
146
|
+
end
|
147
|
+
|
148
|
+
return unless around_set
|
107
149
|
|
108
|
-
@
|
109
|
-
@
|
150
|
+
@around_set = false
|
151
|
+
@around_method << name
|
110
152
|
end
|
111
153
|
end
|
@@ -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
|
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
|