gg0512
gg0512

Reputation: 91

Dynamically define a method inside an instance method

I am working on a project of context-oriented programming in ruby. And I come to this problem:

Suppose that I have a class Klass:

class Klass
    def my_method
        proceed
    end
end

I also have a proc stored inside a variable impl. And impl contains { puts "it works!" }.

From somewhere outside Klass, I would like to define a method called proceed inside the method my_method. So that if a call Klass.new.my_method, I get the result "it works".

So the final result should be something like that:

class Klass
    def my_method
        def proceed
            puts "it works!"
        end
        proceed
    end
end

Or if you have any other idea to make the call of proceed inside my_method working, it's also good. But the proceed of another method (let's say my_method_2) isn't the same as my_method. In fact, the proceed of my_method represent an old version of my_method. And the proceed of my_method_2 represent an old version of my_method_2.

Thanks for your help

Upvotes: 0

Views: 124

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110675

One way of doing that is to construct a hash whose keys are the names of the methods calling proceed and whose values are procs that represent the implementations of proceed for each method calling it.

class Klass
  singleton_class.send(:attr_reader, :proceeds)
  @proceeds = {}
  def my_method1(*args)
    proceed(__method__,*args)
  end
  def my_method2(*args)
    proceed(__method__,*args)
  end
  def proceed(m, *args)
    self.class.proceeds[m].call(*args)
  end
end

def define_proceed(m, &block)
  Klass.proceeds[m] = Proc.new &block
end

define_proceed(:my_method1) { |*arr| arr.sum }
define_proceed(:my_method2) { |a,b| "%s-%s" % [a,b] }

k = Klass.new
k.my_method1(1,2,3)        #=> 6
k.my_method2("cat", "dog") #=> "cat-dog"

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

Disclaimer: you are doing it wrong!

There must be more robust, elegant and rubyish way to achieve what you want. If you still want to abuse metaprogramming, here you go:

class Klass
  def self.proceeds
    @proceeds ||= {}
  end

  def def_proceed
    self.class.proceeds[caller.first[/`.*?'/]] = Proc.new
  end

  def proceed *args
    self.class.proceeds[caller.first[/`.*?'/]].(*args)
  end

  def m_1
    def_proceed { puts 1 }
    proceed
  end

  def m_2
    def_proceed { puts 2 }
    proceed
  end
end

inst = Klass.new

inst.m_1
#⇒ 1
inst.m_2
#⇒ 2

What you in fact need, is Module#prepend and call super from there.

Upvotes: 1

Related Questions