Question

I try to write a metaprogramming for execute a method before 'master' method. Why ? Because, I have several class and it's ugly to repeat the call in the head of the method

Case :

class MyClass
  include MySuperModule
  before :method, call: before_method

  def before_method
    puts "Before.."
  end
end

class SomeClass < MyClass
  def method
    puts "Method.."
  end
end

module MySuperModule
  # the awesome code
end

Output :

SomeClass.new.method => "Before.. Method.."

So, I try write a module with ClassMethodsor method_missingwithout success.

Was it helpful?

Solution

You don't need a gem for simple metaprogramming like this. What you can do is redefine the "after" method to call the "before" method and then the original "after" method.

This works even when using before multiple times on the same method or when creating a chain of before calls.

module MySuperModule
  def before meth, opts
    old_method = instance_method(meth)
    define_method(meth) do
      send opts[:call]
      old_method.bind(self).call
    end
  end
end

class MyClass
  extend MySuperModule

  def foo
    puts "foo"
  end

  def bar
    puts "bar"
  end

  def baz
    puts "baz"
  end

  before :foo, call: :bar
  before :bar, call: :baz
end

MyClass.new.foo
# baz
# bar
# foo

OTHER TIPS

If it is just for subclassing purposes you can take advantage of Module#prepend:

class Superclass
  def self.inherited(subclass)
    # subclass.send :prepend, Module.new { on Ruby < 2.1
    subclass.prepend Module.new {
      def method
        before_method
        super
      end
    }
  end

  def before_method
    puts 'Before'
  end
end

class Subclass < Superclass
  def method
    puts 'Method'
  end
end

Subclass.new.method
#=> Before
#=> Method

What you are looking for is Aspect oriented programming support for ruby. There are several gems implementing this, like aquarium.

Another way to do this is to use the rcapture gem. It is pretty awesome.

Eg:

require 'rcapture'
class A
  # Makes the class intercept able
  include RCapture::Interceptable
  def first
    puts 'first'
  end
  def second
    puts 'second'
  end
end

# injects methods to be called before each specified instance method.
A.capture_pre :methods => [:first, :second] do
  puts "hello"
end

n = A.new
n.first
n.second

produces:

hello
first
hello
second

Maybe you can use a decorator. In ruby there is a nice gem called 'drapeer'. See Drapper Link

Every call in ruby runs through set_trace_func so you can hook into that and call exactly what you want. Not the prettiest solution and there are better ways but it does work. Another option is the Hooks gem, though I haven't tried it myself, it looks like it should give you the ability to do what you want.

module MySuperModule
  # the awesome code
end

class MyClass
  include MySuperModule

  def before_method
    puts "Before.."
  end
end

class SomeClass < MyClass
  def method
    puts "Method.."
  end
end

set_trace_func proc { |event, file, line, id, binding, class_name|
  if event == "call" && class_name == SomeClass && id == :method
    caller = binding.eval("self")
    caller.send(:before_method)
  end
}

SomeClass.new.method
  #=> Before..
  #=> Method..
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top