jacopo beschi: yet another web guy

Ruby: Dynamically create callbacks with metaprogramming

Created at: Jun/12/2015 jacopo beschi

Hello guys, in this post I'll explain you how you can handle callbacks with metaprogramming on ruby on rails. But before going deeper into detail you should ask me the reason of that: why shall you use callbacks instead of using general oop techniques? For example in a classic oop design given that you have:

Class X
   def method_x(*args)
      #do something
   end
end

if you want to do something after calling method x you can easilly override the method in a module like this:

module X
    def method_x(*args)
        super(*args)
        # do something more
    end
end

And then include the module X in your class X. So why should you handle that with metaprogramming? As first in certain situations you cannot override the method using a classical oop approach, for example when you are already leveraging some metaprogramming to dynamically create a method_x inside an included module like this:

module Y
    def self.included(base)
        base.instance_eval do
            def method_x(*args)
                # do something
            end
        end
    end
 end

In that case the method_x is dynamically created and you cannot override it easilly and use the "super" classical oop, therefore you need to handle that with a callback. Callbacks also allow you to handle that in a more fashioned and readable way.
Let me show you how can you do an after_method_x callback: Firs of all include the Callbacks method i've created:

module Callbacks
    extend ActiveSupport::Concern

  module ClassMethods
      def do_after(method)
        guid = SecureRandom.uuid
        define_method("#{guid}") do |*args|
          yield self, __send__("#{guid}_#{method}", *args), *args
        end
        alias_method "#{guid}_#{method}", method
        alias_method enum, "#{guid}"
      end
    end
 end

When you use the method do_after it creates a new method using guid to ensure uniqueness, that method yields self allowing to pass a custom block, and then calls the original method aliased as #{guid}_#{method}. To use that in your class you coud do like this:

Class X
    include Callbacks
    do_after(:method_x) do |klass, result, *args|
        # do wathever you like here in the block
    end
 end

And we have handled a do_after leveraging metaprogramming. I let you imagine the other hooks by yourself. Have fun and Happy coding!

comments powered by Disqus