module BBLib::Hooks

Adds method hooking capability to a class. Intended to be used as a mixin.

Public Instance Methods

_add_hooked_method(type, hook, method) click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 71
def _add_hooked_method(type, hook, method)
  hook = hook.object_id if hook.is_a?(Proc)
  history = _hooked_methods[type]
  history[hook] = {} unless history[hook]
  history[hook][method] = instance_method(method)
end
_defining_hook?() click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 78
def _defining_hook?
  @_defining_hook ||= false
end
_hook_after_method(method, hook, opts = {}) click to toggle source

Current opts: send_args - Sends the arguments of the method to the after method. send_value - Sends the return value of the method to the hook method. send_value_ary - Sends the return value of the method to the hook method

> with the splat operator.

modify_value - Opts must also include one of the two above. Passes the returned

> value of the method to the hook and returns the hooks value

> rather than the original methods value.

send_all - Sends a hash containing the args, method and value (return).

# File lib/bblib/core/mixins/hooks.rb, line 121
def _hook_after_method(method, hook, opts = {})
  return false if method == hook
  _add_hooked_method(:after, hook, method)
  original = instance_method(method)
  @_defining_hook = true
  define_method(method) do |*args, &block|
    rtr = original.bind(self).call(*args, &block)
    if opts[:send_args]
      (hook.is_a?(Proc) ? hook : method(hook)).call(*args)
    elsif opts[:send_return] || opts[:send_value]
      result = (hook.is_a?(Proc) ? hook : method(hook)).call(rtr)
      rtr = result if opts[:modify_value] || opts[:modify_return]
    elsif opts[:send_return_ary] || opts[:send_value_ary]
      result = (hook.is_a?(Proc) ? hook : method(hook)).call(*rtr)
      rtr = result if opts[:modify_value] || opts[:modify_return]
    elsif opts[:send_all]
      result = (hook.is_a?(Proc) ? hook : method(hook)).call(args: args, value: rtr, method: method)
    else
      (hook.is_a?(Proc) ? hook : method(hook)).call
    end
    rtr
  end
  @_defining_hook = false
  true
end
_hook_before_method(method, hook, opts = {}) click to toggle source

Current opts: send_args - Sends the arguments of the method to the before hook. modify_args - Replaces the original args with the returned value of the send_method - Sends the method name as an argument to the hooked method.

before hook method.

try_first - Sends the args to the desired hook first and if the result

is non-nil, the result is sent instead of calling the hooked
method.
# File lib/bblib/core/mixins/hooks.rb, line 90
def _hook_before_method(method, hook, opts = {})
  return false if method == hook
  _add_hooked_method(:before, hook, method)
  original = instance_method(method)
  @_defining_hook = true
  define_method(method) do |*args, &block|
    if opts[:send_args] || opts[:send_arg] || opts[:modify_args] || opts[:send_method] || opts[:try_first]
      margs = args
      margs = [method] + args if opts[:send_method]
      margs = args + [opts[:add_args]].flatten(1) if opts[:add_args]
      result = (hook.is_a?(Proc) ? hook : method(hook)).call(*margs)
      return result if result && opts[:try_first]
      args = result if opts[:modify_args]
    else
      hook.is_a?(Proc) ? hook.call : method(hook).call
    end
    original.bind(self).call(*args, &block)
  end
  @_defining_hook = false
  true
end
_hook_method(method, force: false) click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 33
def _hook_method(method, force: false)
  return false if _defining_hook?
  [:before, :after].each do |hook_type|
    _hooks[hook_type].find_all { |hook, data| data[:methods].include?(method) || data[:opts][:block] && hook == method }.to_h.each do |hook, data|
      hook = data[:opts][:block] if data[:opts][:block]
      next if !force && _hooked_methods[hook_type] && _hooked_methods[hook_type][hook.is_a?(Proc) ? hook.object_id : hook] && _hooked_methods[hook_type][hook.is_a?(Proc) ? hook.object_id : hook].include?(method)
      send("_hook_#{hook_type}_method", method, hook, data[:opts])
    end
  end
end
_hooked_methods() click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 67
def _hooked_methods
  @_hooked_methods ||= { before: {}, after: {} }
end
_hooks() click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 63
def _hooks
  @_hooks ||= _superclass_hooks
end
_superclass_hooks() click to toggle source

def _hook_all

_hooks.each do |type, hooks|
  hooks.each do |hook, data|
    data[:methods].each do |method|
      _hook_method(method)
    end
  end
end

end

# File lib/bblib/core/mixins/hooks.rb, line 54
def _superclass_hooks
  hooks = { before: {}, after: {} }
  ancestors.reverse.each do |ancestor|
    next if ancestor == self
    hooks = hooks.deep_merge(ancestor.send(:_hooks)) if ancestor.respond_to?(:_hooks)
  end
  hooks
end
method_added(method) click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 17
def method_added(method)
  if _defining_hook?
    @_defining_hook = false
  else
    _hook_method(method)
  end
end
singleton_method_added(method) click to toggle source
# File lib/bblib/core/mixins/hooks.rb, line 25
def singleton_method_added(method)
  if _defining_hook?
    @_defining_hook = false
  else
    self.singleton_class.send(:_hook_method, method, force: true) if self.singleton_class.respond_to?(:_hook_method)
  end
end