Reputation: 3422
I have created a module to hook methods before a method call in a class :
module Hooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
# everytime we add a method to the class we check if we must redifine it
def method_added(method)
if @hooker_before.present? && @methods_to_hook_before.include?(method)
hooked_method = instance_method(@hooker_before)
@methods_to_hook_before.each do |method_name|
begin
method_to_hook = instance_method(method_name)
rescue NameError => e
return
end
define_method(method_name) do |*args, &block|
hooked_method.bind(self).call
method_to_hook.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
end
def before(*methods_to_hooks, hookers)
@methods_to_hook_before = methods_to_hooks
@hooker_before = hookers[:call]
end
end
end
I have included the module in one of my class :
require_relative 'hooks'
class Block
include Indentation
include Hooks
attr_accessor :file, :indent
before :generate, call: :indent
# after :generate, call: :write_end
def initialize(file, indent=nil)
self.file = file
self.indent = indent
end
def generate
yield
end
end
this Block class is parent to another class that is implementing its own version of the generate method and that is actually implemented.
When my code is running method_added is actually called with method :generate as argument in some kind of infinite loop. I can't figure out why method_added is caught in this infinite loop. Do you know what's wrong with this code ? Here's a link to the full code : link to code on github
Upvotes: 1
Views: 157
Reputation: 28305
You've caused an infinite recursion because you're calling define_method
inside method_added
. The stack trace (which you haven't provided unfortunately) should show this.
A slightly ugly workaround to resolve this could be to explicitly set a variable (e.g. @_adding_a_method
) and use it as a guard clause for method_added
:
module ClassMethods
def method_added(method)
return if @_adding_a_method
if @hooker_before.present? && @methods_to_hook_before.include?(method)
# ...
@_adding_a_method = true
define_method(method_name) do |*args, &block|
# ...
end
@_adding_a_method = false
# ...
end
end
end
However, taking a step back, I'm not really sure what this module is trying to achieve. Couldn't you just achieve this with Module#prepend
instead of this meta-programming?
This code reminds me of what you might find in an old Ruby 1.8/1.9 tutorial on advanced meta-programming techniques; Module#prepend
makes such workarounds redundant for the most part.
Upvotes: 6