around_the_world 0.4.3 → 0.5.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/lib/around_the_world.rb +41 -29
- data/lib/around_the_world/errors.rb +1 -0
- data/lib/around_the_world/method_wrapper.rb +115 -0
- data/lib/around_the_world/proxy_module.rb +21 -0
- data/lib/around_the_world/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 102af00bd8cee2883f110fbdc21569c3d28d5a6f3ddf991f9d0ff61e6e495ce9
|
4
|
+
data.tar.gz: 65e03179f83fb8e7fce01dc8d5656c6270de7a4cea41b0abfd21fa440f984803
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 463f3e58ebc0cf77787711929a010cc1f1e9750cf39a8a3d88da9f29c19df982dfb72efa6b09ce852097a731afa906ad557f8b35170fc0a4895966337d52ab6c
|
7
|
+
data.tar.gz: 5597d4aa0d6f958429546e8ccd6e8607565ea59b09d8d63255ae60e139da1018d5a2f47b46cb78e5056876c19e0117c3c8960bf5a916f708632728f75b09aeb3
|
data/lib/around_the_world.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "around_the_world/version"
|
4
3
|
require_relative "around_the_world/errors"
|
4
|
+
require_relative "around_the_world/method_wrapper"
|
5
|
+
require_relative "around_the_world/proxy_module"
|
6
|
+
require_relative "around_the_world/version"
|
5
7
|
require "active_support/concern"
|
6
8
|
|
7
9
|
module AroundTheWorld
|
@@ -14,7 +16,7 @@ module AroundTheWorld
|
|
14
16
|
#
|
15
17
|
# @example
|
16
18
|
# class SomeClass
|
17
|
-
# around_method :dont_look_in_here
|
19
|
+
# around_method :dont_look_in_here do
|
18
20
|
# things_happened = super
|
19
21
|
#
|
20
22
|
# if things_happened
|
@@ -32,36 +34,46 @@ module AroundTheWorld
|
|
32
34
|
# SomeClass.new.dont_look_in_here
|
33
35
|
# => "Something happened!"
|
34
36
|
#
|
37
|
+
# @example
|
38
|
+
# around_method :dont_look_in_here, prevent_double_wrapping_for: :memoization do
|
39
|
+
# @memoized ||= super
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# around_method :dont_look_in_here, prevent_double_wrapping_for: :memoization do
|
43
|
+
# @memoized ||= super
|
44
|
+
# end
|
45
|
+
# # => AroundTheWorld::DoubleWrapError:
|
46
|
+
# "Module AroundTheWorld:ProxyModule:memoization already defines the method :dont_look_in_here"
|
47
|
+
#
|
48
|
+
# around_method :dont_look_in_here do
|
49
|
+
# do_something_else
|
50
|
+
# super
|
51
|
+
# end
|
52
|
+
# # => no error raised
|
53
|
+
#
|
35
54
|
# @param method_name [Symbol]
|
36
|
-
# @param proxy_module_name [String]
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def around_method_proxy_module(proxy_module_name)
|
54
|
-
namespaced_proxy_module_name = "#{self}::#{proxy_module_name}"
|
55
|
-
|
56
|
-
const_set(proxy_module_name, Module.new) unless const_defined?(namespaced_proxy_module_name)
|
57
|
-
|
58
|
-
const_get(namespaced_proxy_module_name)
|
59
|
-
end
|
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
|
+
# @param :prevent_double_wrapping_for [Object]
|
65
|
+
# If defined, this prevents wrapping the method twice for a given purpose. Accepts any argument.
|
66
|
+
def around_method(method_name, proxy_module_name = nil, prevent_double_wrapping_for: nil, &block)
|
67
|
+
if proxy_module_name
|
68
|
+
prevent_double_wrapping_for ||= proxy_module_name
|
60
69
|
|
61
|
-
|
62
|
-
|
70
|
+
puts <<~DEPRECATION
|
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
|
63
75
|
|
64
|
-
|
76
|
+
MethodWrapper.wrap(method_name, self, prevent_double_wrapping_for, &block)
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AroundTheWorld
|
4
|
+
class MethodWrapper
|
5
|
+
class << self
|
6
|
+
# Passes arguments directly to {#new} - see docs
|
7
|
+
def wrap(method_name, target, prevent_double_wrapping_for = false, &block)
|
8
|
+
new(method_name, target, prevent_double_wrapping_for, &block).wrap
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :method_name, :target, :prevent_double_wrapping_for, :block
|
13
|
+
|
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]
|
17
|
+
# An identifier to define the proxy module's purpose in the ancestor tree.
|
18
|
+
# A method can only be wrapped once for a given purpose, though it can be wrapped
|
19
|
+
# again for other purposes, or for no given purpose.
|
20
|
+
# @block The block that will be executed when the method is invoked.
|
21
|
+
# Should always call super, at least conditionally.
|
22
|
+
def initialize(method_name, target, prevent_double_wrapping_for = false, &block)
|
23
|
+
@method_name = method_name.to_sym
|
24
|
+
@target = target
|
25
|
+
@prevent_double_wrapping_for = prevent_double_wrapping_for
|
26
|
+
@block = block
|
27
|
+
end
|
28
|
+
|
29
|
+
# Defines the wrapped method inside a proxy module and prepends the proxy module to the target module if necessary.
|
30
|
+
def wrap
|
31
|
+
ensure_method_defined!
|
32
|
+
prevent_double_wrapping! if prevent_double_wrapping?
|
33
|
+
|
34
|
+
define_proxy_method
|
35
|
+
target.prepend proxy_module unless target.ancestors.include?(proxy_module)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def ensure_method_defined!
|
41
|
+
return if target.instance_methods(true).include?(method_name) || target.private_method_defined?(method_name)
|
42
|
+
|
43
|
+
raise MethodNotDefinedError, "#{target} does not define :#{method_name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def prevent_double_wrapping!
|
47
|
+
return unless proxy_module_for(prevent_double_wrapping_for)&.instance_methods&.include?(method_name)
|
48
|
+
|
49
|
+
raise DoubleWrapError, "Module #{proxy_module} already defines the method :#{method_name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def define_proxy_method
|
53
|
+
proxy_module.define_method(method_name, &block)
|
54
|
+
|
55
|
+
proxy_module.instance_exec(method_name, method_privacy) do |method_name, method_privacy|
|
56
|
+
case method_privacy
|
57
|
+
when :protected
|
58
|
+
protected method_name
|
59
|
+
when :private
|
60
|
+
private method_name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_privacy
|
66
|
+
if target.protected_method_defined?(method_name)
|
67
|
+
:protected
|
68
|
+
elsif target.private_method_defined?(method_name)
|
69
|
+
:private
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def prevent_double_wrapping?
|
74
|
+
!prevent_double_wrapping_for.blank?
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [AroundTheWorld::ProxyModule] The proxy module upon which the method wrapper will be defined
|
78
|
+
def proxy_module
|
79
|
+
return @proxy_module ||= proxy_module_for(prevent_double_wrapping_for) if prevent_double_wrapping?
|
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)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AroundTheWorld
|
4
|
+
class ProxyModule < Module
|
5
|
+
attr_reader :purpose
|
6
|
+
|
7
|
+
def initialize(purpose: nil)
|
8
|
+
@purpose = purpose unless purpose.blank?
|
9
|
+
end
|
10
|
+
|
11
|
+
def for?(purpose)
|
12
|
+
return false if self.purpose.blank?
|
13
|
+
|
14
|
+
self.purpose == purpose
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"#<#{self.class.name}#{":#{purpose}" if purpose}>"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Allen Rettberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -35,6 +35,8 @@ files:
|
|
35
35
|
- README.md
|
36
36
|
- lib/around_the_world.rb
|
37
37
|
- lib/around_the_world/errors.rb
|
38
|
+
- lib/around_the_world/method_wrapper.rb
|
39
|
+
- lib/around_the_world/proxy_module.rb
|
38
40
|
- lib/around_the_world/version.rb
|
39
41
|
homepage: https://www.freshly.com
|
40
42
|
licenses:
|