Module: Kinda::Hookable
- Includes:
- Core
- Defined in:
- lib/hookable.rb
Defined Under Namespace
Classes: HookedMethod
Constant Summary collapse
- ClassMethods =
inheritable_extend do [:before, :after, :around].each do |kind| define_method(kind) do |method_name, &block| add_hook(kind, method_name, &block) end end alias_method :on, :after def method_hooked?(method_name) self_and_ancestors.each do |ancestor| ancestor.is_a?(Module) ? next : break unless ancestor.respond_to?(:hooked_methods) return true if ancestor.hooked_methods.include?(method_name) end false end protected def hooked_methods @hooked_methods ||= Hash.new do |hash, method_name| hash[method_name] = Hookable::HookedMethod.new(self, method_name) end end def patch_method(method_name) return if Thread.current[:__adding_method__] # puts "Patching method ##{method_name} in #{self.inspect}" original_method = instance_method(method_name) Thread.current[:__adding_method__] = true define_method(method_name) do |*args, &block| singleton_class_send(:exec_method, method_name, original_method, self, *args, &block) end Thread.current[:__adding_method__] = false hooked_methods[method_name].method_patched = true end private def add_hook(kind, method_name, &block) hooked_methods[method_name.to_sym].hooks[kind] << block patch_method_if_necessary(method_name.to_sym) end def exec_method(method_name, original_method, original_self, *args, &block) result = nil chained_hooks = [] << lambda do |*args, &block| find_hooks(method_name, :before).each do |hook| original_self.call_with_this(hook, *args, &block) end result = original_method.bind(original_self).call(*args, &block) if original_method find_hooks(method_name, :after).each do |hook| original_self.call_with_this(hook, *args, &block) end end hooks = find_hooks(method_name, :around) hooks.each_index do |index| chained_hooks << lambda do |*args, &block| original_self.call_with_this(hooks[index], chained_hooks[index], *args, &block) end end chained_hooks.last.call(*args, &block) result end def find_hooks(method_name, kind) hooks = [] self_and_ancestors.each do |ancestor| ancestor.is_a?(Module) ? next : break unless ancestor.respond_to?(:hooked_methods) next unless ancestor.hooked_methods.include?(method_name) next unless ancestor.hooked_methods[method_name].hooks.include?(kind) hooks.concat(ancestor.hooked_methods[method_name].hooks[kind]) end hooks end def method_added(method_name) super if method_hooked?(method_name) patch_method(method_name) end end def patch_method_if_necessary(method_name) self_and_ancestors.each do |ancestor| ancestor.is_a?(Module) ? next : break unless ancestor.respond_to?(:patch_method) method_defined_in_this_class = ancestor.instance_method(method_name).owner == ancestor rescue false if method_defined_in_this_class ancestor.patch_method(method_name) unless ancestor.hooked_methods[method_name].method_patched break end end end end
Instance Method Summary collapse
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *args, &block) ⇒ Object (private)
117 118 119 120 121 122 123 |
# File 'lib/hookable.rb', line 117 def method_missing(method_name, *args, &block) if method_hooked?(method_name) singleton_class_send(:exec_method, method_name, nil, self, *args, &block) else super end end |
Instance Method Details
#respond_to?(method_name, include_private = false) ⇒ Boolean
111 112 113 |
# File 'lib/hookable.rb', line 111 def respond_to?(method_name, include_private=false) super || method_hooked?(method_name) end |