I'm spending today learning Ruby from a Python perspective. One thing I have completely failed to grapple with is an equivalent of decorators. To pare things down I'm trying to replicate a trivial Python decorator:
#! /usr/bin/env python import math def document(f): def wrap(x): print "I am going to square", x f(x) return wrap @document def square(x): print math.pow(x, 2) square(5)
Running this gives me:
I am going to square 5 25.0
So, I want to create a function square(x), but decorate it so it alerts me as to what it's going to square before it does it. Let's get rid of the sugar to make it more basic:
... def square(x): print math.pow(x, 2) square = document(square) ...
So, how do I replicate this in Ruby? Here's my first attempt:
#! /usr/bin/env ruby def document(f) def wrap(x) puts "I am going to square", x f(x) end return wrap end def square(x) puts x**2 end square = document(square) square(5)
Running this generates:
./ruby_decorate.rb:8:in `document': wrong number of arguments (0 for 1) (ArgumentError) from ./ruby_decorate.rb:15:in `'
Which I guess it because parentheses aren't mandatory and it's taking my "return wrap" as an attempt to "return wrap()". I know of no way to refer to a function without calling it.
I've tried various other things, but nothing gets me far.
Here's another approach that eliminates the problem with conflicts between names of aliased methods (NOTE my other solution using modules for decoration is a good alternative too as it also avoids conflicts):
module Documenter
def document(func_name)
old_method = instance_method(func_name)
define_method(func_name) do |*args|
puts "about to call #{func_name}(#{args.join(', ')})"
old_method.bind(self).call(*args)
end
end
end
The above code works because the old_method
local variable is kept alive in the new 'hello' method by fact of define_method
block being a closure.