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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 102af00bd8cee2883f110fbdc21569c3d28d5a6f3ddf991f9d0ff61e6e495ce9
4
- data.tar.gz: 65e03179f83fb8e7fce01dc8d5656c6270de7a4cea41b0abfd21fa440f984803
3
+ metadata.gz: 27a886d37bde56a1c6edb2c15b966cda10a449c0212d5133e7678109ffe25d10
4
+ data.tar.gz: 35bf0553fdf79e411fce5dc412075f3daff8d2e61eae3361525110f43f9e6b5a
5
5
  SHA512:
6
- metadata.gz: 463f3e58ebc0cf77787711929a010cc1f1e9750cf39a8a3d88da9f29c19df982dfb72efa6b09ce852097a731afa906ad557f8b35170fc0a4895966337d52ab6c
7
- data.tar.gz: 5597d4aa0d6f958429546e8ccd6e8607565ea59b09d8d63255ae60e139da1018d5a2f47b46cb78e5056876c19e0117c3c8960bf5a916f708632728f75b09aeb3
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(method_name, target, prevent_double_wrapping_for = false, &block)
8
- new(method_name, target, prevent_double_wrapping_for, &block).wrap
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, :prevent_double_wrapping_for, :block
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, target, prevent_double_wrapping_for = false, &block)
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 proxy_module_for(prevent_double_wrapping_for)&.instance_methods&.include?(method_name)
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
- 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)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module AroundTheWorld
4
4
  # This constant is managed by spicerack
5
- VERSION = "0.5.0"
5
+ VERSION = "0.6.0.pre"
6
6
  end
@@ -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
- 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
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
- 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
85
+ private
75
86
 
76
- MethodWrapper.wrap(method_name, self, prevent_double_wrapping_for, &block)
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.5.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: 2018-12-04 00:00:00.000000000 Z
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: '0'
60
+ version: 1.3.1
59
61
  requirements: []
60
62
  rubyforge_project:
61
63
  rubygems_version: 2.7.6