Decorators In Ruby

2009/07/15

Few months ago I was really suprised to find out that Ruby doesn’t have any “official” feature similar to Java’s annotations or C#’s attributes or Python’s decorators. And recently I discovered the reason for that: it’s simple to implement them with already available Ruby features. In this article I’ll describe how to mimic Python’s delegates.

So what we want is to put some simple “declarative” construct before method definition and this construct should somehow “enhance” the method: attach to it additional behaviour, annotate it with some meta-data, make advice to a framework to use it as an event handler for some event or whatever. Ruby syntax should look like this:

some_decorator(decorator_arguments)
def some_method(method_arguments)
    method_body
end

Unfortunately, the syntax doesn’t give a cue that this is not an ordinary static method call, but exactly decorator. If it helps you, put it into [brackets] to make it look like C# attributes :)

As starting point let’s pick a decorator that will just happily print in the console that he got access to the target method. The decorator should subscribe for a notification about method definitions, notify that he got access to the method, unsubscribe and invoke a next subscriber if any. To accomplish this we need just two main ingredients: method_added and define_singleton_method methods. method_added is called whenever a method is defined. define_singleton_method is new method in ruby 1.9, that allows defining class methods. It’s possible to implement it in version 1.8 also. So our decorator should look somehow like this:

class X
    def self.log_defined(prefix="")
        next_subs = self.method(:method_added)

        define_singleton_method(:method_added) {|name|
            define_singleton_method(:method_added, &next_subs)
            print "#{prefix}method defined: #{name}\n"
            method_added(name) if next_subs
        }
    end

    log_defined "1st decorator: "
    log_defined "2nd decorator: "
    def f; end
end

Output:
2nd decorator: method defined: f
1st decorator: method defined: f

Notice that the decorators are applied in reverse order. It’s exactly what we wanted to emulate Python’s syntax.

Now let’s do a bit more complex thing. We will make a decorator that will print a message to console before and after each call of the target method.

class Y
    def self.log_called(prefix="")
        next_subs = method(:method_added)
        define_singleton_method(:method_added) {|name|
            to_decorate = instance_method(name)
            define_singleton_method(:method_added, &next_subs)
            define_method(name) {|*args, &block|
                print "#{prefix}before call: #{name}\n"
                begin
                    to_decorate.bind(self).call(*args, &block)
                ensure
                    print "#{prefix}after call: #{name}\n"
                end
            }
        }
    end

    log_called "1st decorator: "
    log_called "2nd decorator: "
    def f(*args); [*args, yield] end
end
Y.new.f(:first, :second) { :yield } 

Output:
1st decorator: before call: f
2nd decorator: before call: f
2nd decorator: after call: f
1st decorator: after call: f

Here we use instance_method to get UnboundMethod of our target method. We cannot call it directly like in Python. Instead we have to bind it to some instance of the class before calling. Despite Ruby is dynamic language we can only bind method to the same class or to its subclass. Also notice that most of the code is evaluated in the context of the current class. But the block that is passed to define_method is evaluated in the context of an instance of the class. We use it to define new implementation of the method.

Actually I don’t like actual decorator code is mixed with calls to Ruby API for method defining. Let’s extract the common part for typical decorators into the following helper method:

module Decorators
    def decorate_next_def(&block)
        next_subs = self.method(:method_added)

        define_singleton_method(:method_added) {|name|
            define_singleton_method(:method_added, &next_subs)

            to_decorate = instance_method(name)
            case decorated = block[name, to_decorate]
            when to_decorate
                method_added(name) if next_subs
            when Proc, Method
                define_method(name, &decorated)
            else raise "unsupported decorated value. Should be Proc, Method, or original unbound method"
            end
        }
    end

end

module MyDecorators
    include Decorators
    def log_defined prefix=""
        decorate_next_def {|name, to_decorate|
            print "#{prefix}method defined: #{name}\n"
            to_decorate
        }
    end

    def log_called prefix=""
        decorate_next_def {|name, to_decorate|
            proc {|*args, &block|
                print "#{prefix}before call: #{name}\n"
                begin
                    to_decorate.bind(self)[*args, &block]
                ensure
                    print "#{prefix}after call: #{name}\n"
                end
            }
        }
    end
end

Good. Now we have a tool helping to define decorators. Let’s check how simple it is to define a decorator in Ruby comparing to Python. PEP- 318  – the porposal for decorators for Python defined here. Let’s take the longest decorator example described there and “port” it to ruby. I think the longest and most interesting is “accept” decorator. It performs type checking for function arguments.

module MyDecorators
    def accepts(*types)
        decorate_next_def{|name, f|
            types.count == f.arity or raise
            proc{|*args, &block|
                for a, t in args.zip(types)
                    a.is_a?(t) or raise "arg class #{a.class} does not match #{t}"
                end
                f.bind(self)[*args, &block]
            }
        }
    end
end

class T
    extend MyDecorators

    accepts(Numeric, String)
    def f(n, s)
        p([n, s])
    end
end

T.new.f(1, "s")
T.new.f("1", "s")

Output:
[1, "s"]
snippet4.rb:9:in `block (3 levels) in accepts': arg class String does not match Numeric (RuntimeError)
        from snippet4.rb:8:in `each'
        from snippet4.rb:8:in `block (2 levels) in accepts'
        from snippet4.rb:27:in `<main>'

You see it’s quite similar to original python implementation and has the same number of lines. So you can freely borrow syntatic solutions exploiting decorators from Python (or Groovy/Scala/Java/C#) for you framework/DSL/library implemented in Ruby. And you can consider the decorators as good alternative to explicit blocks for several cases of declarative definition of classes.

P.S. Implementation of define_singleton_method:

class Object
    def self.define_singleton_method(name, &block)
        class_eval {
            extend Module.new {
                define_method(name, &block)
            }
        }
    end
end

P.P.S.
After I published the post I found another one published 4 days earlier about the same topic :)

About these ads

One Response to “Decorators In Ruby”

  1. Steph Says:

    Awesome tutorial! Thanks!


Пакiнуць каментарый

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: