around_the_world 0.5.0 → 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/around_the_world/method_wrapper/proxy_creation.rb +47 -0
- data/lib/around_the_world/method_wrapper.rb +34 -48
- data/lib/around_the_world/proxy_module.rb +14 -3
- data/lib/around_the_world/rewrapper.rb +33 -0
- data/lib/around_the_world/version.rb +1 -1
- data/lib/around_the_world.rb +30 -18
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27a886d37bde56a1c6edb2c15b966cda10a449c0212d5133e7678109ffe25d10
|
4
|
+
data.tar.gz: 35bf0553fdf79e411fce5dc412075f3daff8d2e61eae3361525110f43f9e6b5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0673b719dc14327044758b00bc15419d2913b76b81393ebe4840bd9d22ba60f63d74842013b0c4aefdae0a23b232cb19b2b5fef84c5930e9e0bda3f36c72d56d
|
7
|
+
data.tar.gz: 57612955ef4da508567167eea0c6398060b7a40992baf8a99cb84033e8981c62b43beda02e95d2b3070ebe89f632e8b9d67e8c4d69c930463f91c5c8c9b187f8
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AroundTheWorld
|
4
|
+
class MethodWrapper
|
5
|
+
module ProxyCreation
|
6
|
+
private
|
7
|
+
|
8
|
+
# @return [Boolean] Returns true if the method has beeen wrapped for the given purpose,
|
9
|
+
# whether or not that wrapper applies to subclassed methods.
|
10
|
+
def already_wrapped?(method_name, target, purpose)
|
11
|
+
existing_proxy_modules(target).any? { |mod| mod.for?(purpose) && mod.defines_proxy_method?(method_name) }
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [AroundTheWorld::ProxyModule] Either an already-defined proxy module for the given purpose,
|
15
|
+
# or a new proxy module if one does not exist for the given purpose.
|
16
|
+
def proxy_module_with_purpose(method_name, target, purpose, wrap_subclasses)
|
17
|
+
existing_proxy_module_with_purpose(method_name, target, purpose, wrap_subclasses) ||
|
18
|
+
ProxyModule.new(purpose: purpose, wrap_subclasses: wrap_subclasses?)
|
19
|
+
end
|
20
|
+
|
21
|
+
def existing_proxy_module_with_purpose(method_name, target, purpose, wrap_subclasses)
|
22
|
+
existing_proxy_modules(target).reverse_each.find do |ancestor|
|
23
|
+
ancestor.for?(purpose) &&
|
24
|
+
!ancestor.defines_proxy_method?(method_name) &&
|
25
|
+
ancestor.wraps_subclasses? == wrap_subclasses
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Array<AroundTheWorld::ProxyModule>] All ProxyModules +prepended+ to the target module.
|
30
|
+
def existing_proxy_modules(target)
|
31
|
+
target_ancestry_index = base_ancestry_index(target)
|
32
|
+
|
33
|
+
@existing_proxy_modules ||= {}
|
34
|
+
@existing_proxy_modules[target] ||= target.ancestors.select.with_index do |ancestor, index|
|
35
|
+
next if index >= target_ancestry_index
|
36
|
+
|
37
|
+
ancestor.is_a? ProxyModule
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Integer] The index of the target module in its ancestry array
|
42
|
+
def base_ancestry_index(target)
|
43
|
+
target.ancestors.index(target)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,28 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "method_wrapper/proxy_creation"
|
4
|
+
|
3
5
|
module AroundTheWorld
|
4
6
|
class MethodWrapper
|
7
|
+
include ProxyCreation
|
8
|
+
|
9
|
+
private_class_method :new
|
10
|
+
|
5
11
|
class << self
|
6
|
-
# Passes arguments directly to {#new} - see docs
|
7
|
-
def wrap(
|
8
|
-
new(
|
12
|
+
# Passes arguments directly to {#new} - see {#initialize} for full docs
|
13
|
+
def wrap(**args, &block)
|
14
|
+
new(**args, &block).wrap
|
9
15
|
end
|
10
16
|
end
|
11
17
|
|
12
|
-
attr_reader :method_name, :target
|
18
|
+
attr_reader :method_name, :target
|
13
19
|
|
14
|
-
# @param method_name [String, Symbol] The name of the method to be wrapped.
|
15
|
-
# @param target [Module] The class or module containing the method to be wrapped.
|
16
|
-
# @param prevent_double_wrapping_for [String, Symbol]
|
20
|
+
# @param :method_name [String, Symbol] The name of the method to be wrapped.
|
21
|
+
# @param :target [Module] The class or module containing the method to be wrapped.
|
22
|
+
# @param :prevent_double_wrapping_for [String, Symbol]
|
17
23
|
# An identifier to define the proxy module's purpose in the ancestor tree.
|
18
24
|
# A method can only be wrapped once for a given purpose, though it can be wrapped
|
19
25
|
# again for other purposes, or for no given purpose.
|
26
|
+
# @param :wrap_subclasses [Boolean]
|
27
|
+
# If true, the given method will still be wrapped by the given block in subclasses that override the given method.
|
28
|
+
# If false, subclasses that override the method will also override the wrapping block.
|
29
|
+
# Default: false
|
20
30
|
# @block The block that will be executed when the method is invoked.
|
21
31
|
# Should always call super, at least conditionally.
|
22
|
-
def initialize(method_name
|
32
|
+
def initialize(method_name:, target:, prevent_double_wrapping_for: nil, wrap_subclasses: false, &block)
|
33
|
+
raise TypeError, "target must be a module or a class" unless target.is_a?(Module)
|
34
|
+
|
23
35
|
@method_name = method_name.to_sym
|
24
36
|
@target = target
|
25
|
-
@prevent_double_wrapping_for = prevent_double_wrapping_for
|
37
|
+
@prevent_double_wrapping_for = prevent_double_wrapping_for || nil
|
38
|
+
@wrap_subclasses = wrap_subclasses
|
26
39
|
@block = block
|
27
40
|
end
|
28
41
|
|
@@ -37,6 +50,16 @@ module AroundTheWorld
|
|
37
50
|
|
38
51
|
private
|
39
52
|
|
53
|
+
attr_reader :prevent_double_wrapping_for, :wrap_subclasses, :block
|
54
|
+
|
55
|
+
def prevent_double_wrapping?
|
56
|
+
prevent_double_wrapping_for.present?
|
57
|
+
end
|
58
|
+
|
59
|
+
def wrap_subclasses?
|
60
|
+
wrap_subclasses.present?
|
61
|
+
end
|
62
|
+
|
40
63
|
def ensure_method_defined!
|
41
64
|
return if target.instance_methods(true).include?(method_name) || target.private_method_defined?(method_name)
|
42
65
|
|
@@ -44,7 +67,7 @@ module AroundTheWorld
|
|
44
67
|
end
|
45
68
|
|
46
69
|
def prevent_double_wrapping!
|
47
|
-
return unless
|
70
|
+
return unless already_wrapped?(method_name, target, prevent_double_wrapping_for)
|
48
71
|
|
49
72
|
raise DoubleWrapError, "Module #{proxy_module} already defines the method :#{method_name}"
|
50
73
|
end
|
@@ -70,46 +93,9 @@ module AroundTheWorld
|
|
70
93
|
end
|
71
94
|
end
|
72
95
|
|
73
|
-
def prevent_double_wrapping?
|
74
|
-
!prevent_double_wrapping_for.blank?
|
75
|
-
end
|
76
|
-
|
77
96
|
# @return [AroundTheWorld::ProxyModule] The proxy module upon which the method wrapper will be defined
|
78
97
|
def proxy_module
|
79
|
-
|
80
|
-
|
81
|
-
@proxy_module ||= first_available_proxy_module || ProxyModule.new
|
82
|
-
end
|
83
|
-
|
84
|
-
# @param purpose [String, Symbol] An identifier to define the proxy module's purpose in the ancestor tree.
|
85
|
-
# @return [AroundTheWorld::ProxyModule] Either an already-defined proxy module for the given purpose,
|
86
|
-
# or a new proxy module if one does not exist for the gibven purpose.
|
87
|
-
def proxy_module_for(purpose)
|
88
|
-
raise ArgumentError if purpose.blank?
|
89
|
-
|
90
|
-
existing_proxy_modules.find { |proxy_module| proxy_module.for?(purpose) } ||
|
91
|
-
ProxyModule.new(purpose: prevent_double_wrapping_for)
|
92
|
-
end
|
93
|
-
|
94
|
-
# @return [AroundTheWorld::ProxyModule] The first defined ProxyModule that does not define the method to be wrapped.
|
95
|
-
# @return [NilClass] If all prepended ProxyModules already define the method,
|
96
|
-
# or no ProxyModules have been prepended yet.
|
97
|
-
def first_available_proxy_module
|
98
|
-
existing_proxy_modules.reverse_each.find { |ancestor| !ancestor.instance_methods.include?(method_name) }
|
99
|
-
end
|
100
|
-
|
101
|
-
# @return [Array<AroundTheWorld::ProxyModule>] All ProxyModules +prepended+ to the target module.
|
102
|
-
def existing_proxy_modules
|
103
|
-
@existing_proxy_modules ||= target.ancestors.select.with_index do |ancestor, index|
|
104
|
-
next if index >= base_ancestry_index
|
105
|
-
|
106
|
-
ancestor.is_a? ProxyModule
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# @return [Integer] The index of the target module in its ancestry array
|
111
|
-
def base_ancestry_index
|
112
|
-
@base_ancestry_index = target.ancestors.index(target)
|
98
|
+
@proxy_module ||= proxy_module_with_purpose(method_name, target, prevent_double_wrapping_for, wrap_subclasses?)
|
113
99
|
end
|
114
100
|
end
|
115
101
|
end
|
@@ -4,18 +4,29 @@ module AroundTheWorld
|
|
4
4
|
class ProxyModule < Module
|
5
5
|
attr_reader :purpose
|
6
6
|
|
7
|
-
def initialize(purpose: nil)
|
7
|
+
def initialize(purpose: nil, wrap_subclasses: false)
|
8
8
|
@purpose = purpose unless purpose.blank?
|
9
|
+
@wrap_subclasses = wrap_subclasses
|
9
10
|
end
|
10
11
|
|
11
12
|
def for?(purpose)
|
12
|
-
return false if self.purpose.blank?
|
13
|
-
|
14
13
|
self.purpose == purpose
|
15
14
|
end
|
16
15
|
|
16
|
+
def wraps_subclasses?
|
17
|
+
wrap_subclasses.present?
|
18
|
+
end
|
19
|
+
|
17
20
|
def inspect
|
18
21
|
"#<#{self.class.name}#{":#{purpose}" if purpose}>"
|
19
22
|
end
|
23
|
+
|
24
|
+
def defines_proxy_method?(method_name)
|
25
|
+
instance_methods(true).include?(method_name.to_sym) || private_method_defined?(method_name.to_sym)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :wrap_subclasses
|
20
31
|
end
|
21
32
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array"
|
4
|
+
require_relative "method_wrapper/proxy_creation"
|
5
|
+
|
6
|
+
module AroundTheWorld
|
7
|
+
class Rewrapper
|
8
|
+
include MethodWrapper::ProxyCreation
|
9
|
+
|
10
|
+
private_class_method :new
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def rewrap(target, proxy_modules)
|
14
|
+
new(target, proxy_modules).rewrap
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :target, :proxy_modules
|
19
|
+
|
20
|
+
def initialize(target, proxy_modules)
|
21
|
+
@target = target
|
22
|
+
@proxy_modules = Array.wrap(proxy_modules)
|
23
|
+
end
|
24
|
+
|
25
|
+
def rewrap
|
26
|
+
proxy_modules.each do |mod|
|
27
|
+
next if existing_proxy_modules(target).include?(mod)
|
28
|
+
|
29
|
+
target.prepend mod
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/around_the_world.rb
CHANGED
@@ -2,13 +2,19 @@
|
|
2
2
|
|
3
3
|
require_relative "around_the_world/errors"
|
4
4
|
require_relative "around_the_world/method_wrapper"
|
5
|
+
require_relative "around_the_world/rewrapper"
|
5
6
|
require_relative "around_the_world/proxy_module"
|
6
7
|
require_relative "around_the_world/version"
|
7
8
|
require "active_support/concern"
|
9
|
+
require "active_support/descendants_tracker"
|
8
10
|
|
9
11
|
module AroundTheWorld
|
10
12
|
extend ActiveSupport::Concern
|
11
13
|
|
14
|
+
included do
|
15
|
+
extend ActiveSupport::DescendantsTracker
|
16
|
+
end
|
17
|
+
|
12
18
|
class_methods do
|
13
19
|
protected
|
14
20
|
|
@@ -52,28 +58,34 @@ module AroundTheWorld
|
|
52
58
|
# # => no error raised
|
53
59
|
#
|
54
60
|
# @param method_name [Symbol]
|
55
|
-
# @param proxy_module_name [String]
|
56
|
-
# DEPRECATED: this argument is deprecated and will be removed in a future release.
|
57
|
-
# Use the :prevent_double_wrapping_for option instead.
|
58
|
-
# The camelized name of a custom module to place the wrapper method in. This is necessary
|
59
|
-
# to enable wrapping a single method more than once since a module cannot super to itself.
|
60
|
-
# It's recommended to name the module after what the method wrapper will do, for example
|
61
|
-
# LogsAnEvent for a wrapper method that logs something. Because of the potential for
|
62
|
-
# overriding previously wrapped methods, this parameter is required.
|
63
|
-
#
|
64
61
|
# @param :prevent_double_wrapping_for [Object]
|
65
62
|
# If defined, this prevents wrapping the method twice for a given purpose. Accepts any argument.
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
# @param :wrap_subclasses [Boolean]
|
64
|
+
# If true, the given method will still be wrapped by the given block in subclasses that override the given method.
|
65
|
+
# If false, subclasses that override the method will also override the wrapping block.
|
66
|
+
# Default: false
|
67
|
+
def around_method(method_name, prevent_double_wrapping_for: nil, wrap_subclasses: false, &block)
|
68
|
+
MethodWrapper.wrap(
|
69
|
+
method_name: method_name,
|
70
|
+
target: self,
|
71
|
+
prevent_double_wrapping_for: prevent_double_wrapping_for,
|
72
|
+
wrap_subclasses: wrap_subclasses,
|
73
|
+
&block
|
74
|
+
)
|
75
|
+
|
76
|
+
descendants.each { |child| Rewrapper.rewrap(child, proxy_modules_for_subwrapping) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def inherited(child)
|
80
|
+
super
|
81
|
+
|
82
|
+
Rewrapper.rewrap(child, proxy_modules_for_subwrapping)
|
83
|
+
end
|
69
84
|
|
70
|
-
|
71
|
-
DEPRECATION WARNING: the proxy_module_name argument is deprecated and will be removed
|
72
|
-
in version 1.0. Please use the :prevent_double_wrapping_for option instead.
|
73
|
-
DEPRECATION
|
74
|
-
end
|
85
|
+
private
|
75
86
|
|
76
|
-
|
87
|
+
def proxy_modules_for_subwrapping
|
88
|
+
ancestors.select { |mod| mod.is_a?(ProxyModule) && self < mod && mod.wraps_subclasses? }
|
77
89
|
end
|
78
90
|
end
|
79
91
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: around_the_world
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Allen Rettberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -36,7 +36,9 @@ files:
|
|
36
36
|
- lib/around_the_world.rb
|
37
37
|
- lib/around_the_world/errors.rb
|
38
38
|
- lib/around_the_world/method_wrapper.rb
|
39
|
+
- lib/around_the_world/method_wrapper/proxy_creation.rb
|
39
40
|
- lib/around_the_world/proxy_module.rb
|
41
|
+
- lib/around_the_world/rewrapper.rb
|
40
42
|
- lib/around_the_world/version.rb
|
41
43
|
homepage: https://www.freshly.com
|
42
44
|
licenses:
|
@@ -53,9 +55,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
55
|
version: '0'
|
54
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
57
|
requirements:
|
56
|
-
- - "
|
58
|
+
- - ">"
|
57
59
|
- !ruby/object:Gem::Version
|
58
|
-
version:
|
60
|
+
version: 1.3.1
|
59
61
|
requirements: []
|
60
62
|
rubyforge_project:
|
61
63
|
rubygems_version: 2.7.6
|