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 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