around_the_world 0.4.3 → 0.5.0

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: bf3d2cd621e18996b29ef47dc0fd22a933cf6fa4add557b04a1cecbafbd6ae49
4
- data.tar.gz: 2584124852e8f1d30b240bdd59da3ddebe92f1e553b72cf187be45030400f7e0
3
+ metadata.gz: 102af00bd8cee2883f110fbdc21569c3d28d5a6f3ddf991f9d0ff61e6e495ce9
4
+ data.tar.gz: 65e03179f83fb8e7fce01dc8d5656c6270de7a4cea41b0abfd21fa440f984803
5
5
  SHA512:
6
- metadata.gz: 97f1643c1942aef0020fc6c3d82deb5e75bf871f927d9571ddb898aa34816b6ee2b0c756089855e323b35ce89176a1ad8f54bc5fb70ca912f867e2037cd1c28b
7
- data.tar.gz: be6f2f090b1c17a1e1dd094f030393400eddccdac6ac39d159427c9111fdc5ec1afd9475073ac380ca16243734d78194da1e256a3442dc1a2967c0f4cb41e0f6
6
+ metadata.gz: 463f3e58ebc0cf77787711929a010cc1f1e9750cf39a8a3d88da9f29c19df982dfb72efa6b09ce852097a731afa906ad557f8b35170fc0a4895966337d52ab6c
7
+ data.tar.gz: 5597d4aa0d6f958429546e8ccd6e8607565ea59b09d8d63255ae60e139da1018d5a2f47b46cb78e5056876c19e0117c3c8960bf5a916f708632728f75b09aeb3
@@ -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, "DoesAThing" do
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] The camelized name of a custom module to place the wrapper method in.
37
- # This is necessary to enable wrapping a single method more than once
38
- # since a module cannot super to itself.
39
- # It's recommended to name the module after what the method wrapper will do,
40
- # for example LogsAnEvent for a wrapper method that logs something.
41
- # Because of the potential for overriding previously wrapped methods, this
42
- # parameter is required.
43
- def around_method(method_name, proxy_module_name, &block)
44
- proxy_module = around_method_proxy_module(proxy_module_name)
45
- ensure_around_method_uniqueness!(method_name, proxy_module)
46
-
47
- proxy_module.define_method(method_name, &block)
48
- prepend proxy_module unless ancestors.include?(proxy_module)
49
- end
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
- def ensure_around_method_uniqueness!(method_name, proxy_module)
62
- return unless proxy_module.instance_methods.include?(method_name.to_sym)
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
- raise DoubleWrapError, "Module #{proxy_module} already defines the method :#{method_name}"
76
+ MethodWrapper.wrap(method_name, self, prevent_double_wrapping_for, &block)
65
77
  end
66
78
  end
67
79
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AroundTheWorld
4
+ class MethodNotDefinedError < NoMethodError; end
4
5
  class DoubleWrapError < StandardError; end
5
6
  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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module AroundTheWorld
4
4
  # This constant is managed by spicerack
5
- VERSION = "0.4.3"
5
+ VERSION = "0.5.0"
6
6
  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.3
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-10-10 00:00:00.000000000 Z
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: