around_the_world 0.5.0 → 0.6.0.pre
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/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
|