tags:

views:

501

answers:

8

Hi. 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.

+1  A: 

Your guess is right.

You best use alias to bind the original method to another name, and then define the new one to print something and call the old one. If you need to do this repeatedly, you can make a method that does this for any method (I had an example once, but cannot find it now).

PS: your code does not define a function within a function but another function on the same object (yes, this is an undocument feature of Ruby)

class A
  def m
    def n
    end
  end
end

defines both m and n on A.

NB: the way to refer to a function would be

A.method(:m)
Adrian
A: 

This is a slightly unusual question, but interesting. I'd first strongly recommend that you don't try and directly transfer your Python knowledge to Ruby - it's better to learn the idioms of Ruby and apply them directly, rather than try to transfer Python directly. I've used both languages a lot, and they're both best when following their own rules and conventions.

Having said all that, here's some nifty code that you can use.

def with_document func_name, *args
  puts "about to call #{func_name}(#{args.to_s[1...-1]})"
  method(func_name).call *args
end

def square x
  puts x**2
end

def multiply a, b
  puts a*b
end

with_document :square, 5
with_document :multiply, 5, 3

this produces

about to call square(5)
25
about to call multiply(5, 3)
15

which I'm sure you'll agree does the job.

Peter
Thanks! That's interesting. That does map pretty close to Python, with one big difference: I have to be aware of the decoration every time I call the function. This seems to me to destroy the point of decoration: I want to say, at the time I declare the function, "I want this documented", and then be able to call "square 5" later on completely unaware of how it has been decorated.So, without trying to follow Python conventions, how would you accomplish that?
eegg
+1  A: 

Okay, found my code again that does decorators in Ruby. It uses alias to bind the original method to another name, and then define the new one to print something and call the old one. All this is done using eval, such that it can be reused like decorators in Python.

module Document
  def document(symbol)
    self.send :class_eval, """
      alias :#{symbol}_old :#{symbol}
      def #{symbol} *args
        puts 'going to #{symbol} '+args.join(', ')
        #{symbol}_old *args
      end"""  
  end
end

class A 
  extend Document
  def square(n)
    puts n * n
  end
  def multiply(a,b)
    puts a * b
  end
  document :square
  document :multiply
end

a = A.new
a.square 5
a.multiply 3,4

Edit: here the same with a block (no string manipulation pain)

module Document
  def document(symbol)
    self.class_eval do
       symbol_old = "#{symbol}_old".to_sym
       alias_method symbol_old, symbol
       define_method symbol do |*args|
         puts "going to #{symbol} "+args.join(', ')
         self.send symbol_old, *args
       end
    end  
  end
end
Adrian
Aha, well, that's certainly functionally the same. But for me a glaring problem here is the use of eval (also that I really have to glare at it to see how it works).Surely there's a clean way of doing this?
eegg
Something like this:<pre>def document func alias :old_func :func def :func *args # Somehow 'overwrite' the current function ... puts "about to call" method(:old_func).call *args endenddef square x puts x**2enddocument(:square)square 5</pre>But without the syntax error ... !
eegg
It should be possible to pass a block to eval, such that you get rid of the string manipulation. Beside that, I would not know of a way to get this done without `eval`. Meta-programming in Ruby (unlike in Python) makes heavy use of the different eval functions.
Adrian
Added code for block use.
Adrian
On a similar train of thought, I tried taking your way of referring to a function, method(:func), to see if this would work: #! /usr/bin/env ruby def document func alias :old_func :func def new_func *args puts "about to call" method(:old_func).call *args end method(:old_func) = method(:new_func) # Somehow 'overwrite' the current function ... end def square x puts x**2 end document(:square) square 5No luck. Ruby claims to be properly object-oriented, and yet I can't pass simple functions around as objects ... ?
eegg
On a side-note I seemingly can't use Stack Overflow's markdown/basic HTML ...<code> #! /usr/bin/env ruby def document func alias :old_func :func def new_func *args puts "about to call" method(:old_func).call *args end method(:old_func) = method(:new_func) # Somehow 'overwrite' the current function ... end def square x puts x**2 end document(:square) square 5</code>
eegg
Comments only support limited markdown. Can you past your code into http://pastie.org for me?
Adrian
A: 

I believe the corresponding Ruby idiom would be the alias method chain, which is heavily used by Rails. This article also considers it as the Ruby-style decorator.

For your example it should look like this:

class Foo
  def square(x)
    puts x**2
  end

  def square_with_wrap(x)
    puts "I am going to square", x
    square_without_wrap(x)
  end

  alias_method_chain :square, :wrap
end

The alias_method_chain call renames square to square_without_wrap and makes square an alias for square_with_wrap.

I believe Ruby 1.8 doesn't have this method built-in, so you would have to copy it from Rails, but 1.9 should include it.

My Ruby-Skills have gotten a bit rusty, so I'm sorry if the code doesn't actually work, but I'm sure it demonstrates the concept.

mooware
+1 notwithstanding that alias_method_chain is not part of Ruby. :) You can do something similar using plain old 'alias_method' ;)
banister
+1  A: 

Python-like decorators can be implemented in Ruby. I won't try to explain and give examples, because Yehuda Katz has already published a good blog post about decorators DSL in Ruby, so I highly recommend to read it:

UPDATE: I've got a couple of vote downs on this one, so let me explain further.

alias_method (and alias_method_chain) is NOT exactly the same concept as a decorator. It is just a way to re-define method implementation without using inheritance (so client code won't notice a difference, still using the same method call). It could be useful. But also it could be error-prone. Anyone who used Gettext library for Ruby probably noticed that its ActiveRecord integration has been broken with each Rails major upgrade, because aliased version has been following the semantics of an old method.

The purpose of a decorator in general is NOT to change the internals of any given method and still be able to call the original one from a modified version, but to enhance the function behavior. The "entry/exit" use case, which is somewhat close to alias_method_chain, is only a simple demonstration. Another, more useful kind of a decorator could be @login_required, which checks authorization, and only runs the function if authorization was successful, or @trace(arg1, arg2, arg3), which could perform a set of tracing procedures (and be called with different arguments for different methods decoration).

Oleg Shaldybin
definitely the best answer so far.
Peter
github link is broken!
Adrian
Yehuda Katz was just showing you COULD do it, i really really doubt Yehuda would use this code in production. It's very complex AND there are Ruby idioms that serve a similar purpose - such as alias method chaining.
banister
I don't think Yehuda's code is too complicated or non-production ready. It has certain implications, and might need additional tweaks occasionally, but overall is pretty clean and well-tested.
Oleg Shaldybin
@Oleg, Yehuda's implementation makes use of the #extended, #inherited, and #method_added hooks, it also uses #method_missing and dynamic eval strings - take a look at the code, I think you will agree it is complicated and impressive too :) Further, Yehuda himself did not intend his code to be used he was proving a point about the flexibility of the Ruby language, here's what he said on the matter (from his blog): (see next post)
banister
From Yehuda's blog: "My point is *not* that people should take my decorator implementation and use it. I find the decorator approach to be fairly limiting when the power to create new language features in the language itself is so freely available. My intent was to show that Python-style decorators are a small *subset* of the available functionality in Ruby."
banister
@banister, that's true, it uses all these hooks and metaprogramming tricks... so Rails itself does too, and heavily. And I don't think that it cannot be used in production: it just can require some additional tweaking and taking responsibility for possible bugs. But I agree with you, that it's not the simplest thing to consider, and it's more of a feature demonstration than real life problem. But my main point is: if you want decorators, you cannot just substitute them with :alias_method_chain, it's a different concept (see my updated answer).
Oleg Shaldybin
+1  A: 

IMO mooware has the best answer so far and it is the cleanest, simplest and most idiomatic. However he is making use of 'alias_method_chain' which is part of Rails, and not pure Ruby. Here is a rewrite using pure Ruby:

class Foo         
    def square(x)
        puts x**2
    end

    alias_method :orig_square, :square

    def square(x)
        puts "I am going to square #{x}"
        orig_square(x)
    end         
end

You can also accomplish the same thing using modules instead:

module Decorator
    def square(x)
        puts "I am going to square #{x}"
        super
    end
end

class Foo
    def square(x)
        puts x**2
    end
end

# let's create an instance
foo = Foo.new

# let's decorate the 'square' method on the instance
foo.extend Decorator

# let's invoke the new decorated method
foo.square(5) #=> "I am going to square 5"
              #=> 25
banister
+2  A: 

Ok, time for my attempt at an answer. I'm aiming here specifically at Pythoneers trying to reorganize their brains. Here's some heavily documented code that (approximately) does what I was originally trying to do:

Decorating instance methods

#! /usr/bin/env ruby

# First, understand that decoration is not 'built in'.  You have to make
# your class aware of the concept of decoration.  Let's make a module for this.
module Documenter
  def document(func_name)   # This is the function that will DO the decoration: given a function, it'll extend it to have 'documentation' functionality.
    new_name_for_old_function = "#{func_name}_old".to_sym   # We extend the old function by 'replacing' it - but to do that, we need to preserve the old one so we can still call it from the snazzy new function.
    alias_method(new_name_for_old_function, func_name)  # This function, alias_method(), does what it says on the tin - allows us to call either function name to do the same thing.  So now we have TWO references to the OLD crappy function.  Note that alias_method is NOT a built-in function, but is a method of Class - that's one reason we're doing this from a module.
    define_method(func_name) do |*args|   # Here we're writing a new method with the name func_name.  Yes, that means we're REPLACING the old method.
      puts "about to call #{func_name}(#{args.join(', ')})"  # ... do whatever extended functionality you want here ...
      send(new_name_for_old_function, *args)  # This is the same as `self.send`.  `self` here is an instance of your extended class.  As we had TWO references to the original method, we still have one left over, so we can call it here.
      end
    end
  end

class Squarer   # Drop any idea of doing things outside of classes.  Your method to decorate has to be in a class/instance rather than floating globally, because the afore-used functions alias_method and define_method are not global.
  extend Documenter   # We have to give our class the ability to document its functions.  Note we EXTEND, not INCLUDE - this gives Squarer, which is an INSTANCE of Class, the class method document() - we would use `include` if we wanted to give INSTANCES of Squarer the method `document`.  <http://blog.jayfields.com/2006/05/ruby-extend-and-include.html&gt;
  def square(x) # Define our crappy undocumented function.
    puts x**2
    end
  document(:square)  # this is the same as `self.document`.  `self` here is the CLASS.  Because we EXTENDED it, we have access to `document` from the class rather than an instance.  `square()` is now jazzed up for every instance of Squarer.

  def cube(x) # Yes, the Squarer class has got a bit to big for its boots
    puts x**3
    end
  document(:cube)
  end

# Now you can play with squarers all day long, blissfully unaware of its ability to `document` itself.
squarer = Squarer.new
squarer.square(5)
squarer.cube(5)

Still confused? I wouldn't be surprised; this has taken me almost a whole DAY. Some other things you should know:

  • The first thing, which is CRUCIAL, is to read this: http://www.softiesonrails.com/2007/8/15/ruby-101-methods-and-messages. When you call 'foo' in Ruby, what you're actually doing is sending a message to its owner: "please call your method 'foo'". You just can't get a direct hold on functions in Ruby in the way you can in Python; they're slippery and elusive. You can only see them as though shadows on a cave wall; you can only reference them through strings/symbols that happen to be their name. Try and think of every method call 'object.foo(args)' you do in Ruby as the equivalent of this in Python: 'object.getattribute('foo')(args)'.
  • Stop writing any function/method definitions outside of modules/classes.
  • Accept from the get-go that this learning experience is going to be brain-melting, and take your time. If Ruby isn't making sense, punch a wall, go make a cup of coffee, or take a night's sleep.

Decorating class methods

The above code decorates instance methods. What if you want to decorate methods directly on the class? If you read http://www.rubyfleebie.com/understanding-class-methods-in-ruby, you find there are three methods for creating class methods -- but only one of them works for us here.

That is the anonymous class << self technique. Let's do the above but so we can call square() and cube() without instantiating it:

class Squarer

  class << self # class methods go in here
    extend Documenter

    def square(x)
      puts x**2
      end
    document(:square)

    def cube(x)
      puts x**3
      end
    document(:cube)
    end
  end

Squarer.square(5)
Squarer.cube(5)

Have fun!

eegg
Instead of `self.method(new_name_for_old_function).call(*args) ` you can use `send(new_name_for_old_function, *args)`Also you don't need to use 'self' all the time :) In the class body `document(:square)` is fine. If you want this `document` method to be available to ALL classes then `include` the `Documenter` module on the `Class` class.Nice work on your solution, but bear in mind this is really quite an non-idiomatic way of achieving what you want in Ruby, the most common approach is to use `alias_method` as given in some of the answers :)
banister
Thanks; I've updated it a bit - you're right, that is cleaner. I think my mind has just about made the necessary quantum leap to understand all this.
eegg
And updated again as I've figured out how to apply the same principle to class methods.
eegg
@LOL at your most recent update. 10 points if you guess where the `document` method is defined (hint it's not on the class or the eigenclass....but somewhere else altogether...)
banister
I'm afraid I'm going to have to pass up those points to someone else -- I'm stumped.I should add I've noticed (what should have been) an obvious possible problem with the above (and with other solutions here). If you just happen to define `square_old` elsewhere on your class, you ... f*ck things up, in ways that in production would probably be subtle and impossible to identify. Meaning that in order to safely use decoration as above, you need to know is implementation. Which is a bad thing. The only 'workaround' I can think of here is to use a ridiculous random suffix instead of '_old'.
eegg
@eeg, you're right that in theory other definitions of `square_old` could conflict with the alias. However this isn't usually a problem in practice, nonetheless there are other solutions that do not have this problem (ill post one soon).The answer to the question i posed is (wait for it) `document` is defined on the eigenclass OF the eigenclass of Squarer. Creepy, huh? :P
banister
@egg, you can set `square_old` to be private, ie not callable from outside the object. Also, if you append an SHA1 hash or similar to the name, it should be unique enough. If it's good enough for git it should be good enough for us :)
Adrian
+3  A: 

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.

banister
Hallelujah! That's much cleaner. How lovely. +1.
eegg
As an aside, I think I now prefer this technique to Python for at least one reason: passing arguments to the decorator method does not make the technique any more complex. Contrast this with Python: <http://www.artima.com/weblogs/viewpost.jsp?thread=240845>
eegg