Module: Collapsium::Support::Methods
- Included in:
- EnvironmentOverride, PathedAccess, ViralCapabilities, ViralCapabilities
- Defined in:
- lib/collapsium/support/methods.rb
Overview
Functionality for extending the behaviour of Hash methods
Class Method Summary collapse
-
.loop_detected?(the_binding, stack) ⇒ Boolean
Given a call stack and a binding, returns true if there seems to be a loop in the call stack with the binding causing it, false otherwise.
-
.repeated(array) ⇒ Object
Given an input array, return repeated sequences from the array.
Instance Method Summary collapse
- #resolve_helpers(base, method_name, raise_on_missing) ⇒ Object
-
#wrap_method(base, method_name, options = {}, &wrapper_block) ⇒ Object
Given the base module, wraps the given method name in the given block.
Class Method Details
.loop_detected?(the_binding, stack) ⇒ Boolean
Given a call stack and a binding, returns true if there seems to be a loop in the call stack with the binding causing it, false otherwise.
136 137 138 139 140 141 142 143 144 145 |
# File 'lib/collapsium/support/methods.rb', line 136 def loop_detected?(the_binding, stack) # Make a temporary stack with the binding pushed tmp_stack = stack.dup tmp_stack << the_binding loops = Methods.repeated(tmp_stack) # If we do find a loop with the current binding involved, we'll just # call the wrapped method. return loops.include?(the_binding) end |
.repeated(array) ⇒ Object
Given an input array, return repeated sequences from the array. It’s used in loop detection.
128 129 130 131 132 |
# File 'lib/collapsium/support/methods.rb', line 128 def repeated(array) counts = Hash.new(0) array.each { |val| counts[val] += 1 } return counts.reject { |_, count| count == 1 }.keys end |
Instance Method Details
#resolve_helpers(base, method_name, raise_on_missing) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/collapsium/support/methods.rb', line 95 def resolve_helpers(base, method_name, raise_on_missing) # The base class must define an instance method of method_name, otherwise # this will NameError. That's also a good check that sensible things are # being done. base_method = nil def_method = nil if base.is_a? Module # Modules *may* not be fully defined when this is called, so in some # cases it's best to ignore NameErrors. begin base_method = base.instance_method(method_name.to_sym) rescue NameError if raise_on_missing raise end return base_method, def_method end def_method = base.method(:define_method) else # For Objects and Classes, the unbound method will later be bound to # the object or class to define the method on. base_method = base.method(method_name.to_s).unbind # With regards to method defintion, we only want to define methods # for the specific instance (i.e. use :define_singleton_method). def_method = base.method(:define_singleton_method) end return base_method, def_method end |
#wrap_method(base, method_name, options = {}, &wrapper_block) ⇒ Object
Given the base module, wraps the given method name in the given block. The block must accept the wrapped_method as the first parameter, followed by any arguments and blocks the super method might accept.
The canonical usage example is of a module that when prepended wraps some methods with extra functionality:
“‘ruby
module MyModule
class << self
include ::Collapsium::Support::Methods
def prepended(base)
wrap_method(base, :method_name) do |wrapped_method, *args, &block|
# modify args, if desired
result = wrapped_method.call(*args, &block)
# do something with the result, if desired
next result
end
end
end
end
“‘
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/collapsium/support/methods.rb', line 43 def wrap_method(base, method_name, = {}, &wrapper_block) # Option defaults (need to check for nil if we default to true) if [:raise_on_missing].nil? [:raise_on_missing] = true end # Grab helper methods base_method, def_method = resolve_helpers(base, method_name, [:raise_on_missing]) if base_method.nil? # Indicates that we're not done building a Module yet return end # Hack for calling the private method "define_method" def_method.call(method_name) do |*args, &method_block| # We're trying to prevent loops by maintaining a stack of wrapped # method invocations. @__collapsium_methods_callstack ||= [] # Our current binding is based on the wrapper block and our own class, # as well as the arguments (CRC32). require 'zlib' signature = Zlib::crc32(JSON::dump(args)) the_binding = [wrapper_block.object_id, self.class.object_id, signature] # We'll either pass the wrapped method to the wrapper block, or invoke # it ourselves. wrapped_method = base_method.bind(self) # If we do find a loop with the current binding involved, we'll just # call the wrapped method. if Methods.loop_detected?(the_binding, @__collapsium_methods_callstack) next wrapped_method.call(*args, &method_block) end # If there is no loop, call the wrapper block and pass along the # wrapped method as the first argument. args.unshift(wrapped_method) # Then yield to the given wrapper block. The wrapper should decide # whether to call the old method or not. But by modifying our stack # before/after the invocation, we allow the loop detection above to # work. @__collapsium_methods_callstack << the_binding result = wrapper_block.call(*args, &method_block) @__collapsium_methods_callstack.pop next result end end |