Ruby annotations

In the programming world, an annotation is a way to mark the code that is below it for many purposes. In Cucumber, the tags it uses are a kind of annotation to apply callbacks and classify some features and scenarios:

@billing
Feature: Verify billing
 
  @important
  Scenario: Missing product description
 
  Scenario: Several products

In the case of VRaptor, we can use annotations to specify a controller as a REST resource or to restrict the access method to an action:

@Resource
public class ShoppingCartController {
    ...
}
 
public class ProductsController {
    @Post
    @Path("/products")
    public void add(Product product) {...}
    ...
}

In the Ruby language, two annotations that are very used are the methods protected and private, which without arguments restrict the visibility of the methods defined below them:

class User
  def jump; end;
 
  protected
  def eat; end;
  def flirt; end;
 
  private
  def sleep; end;
  def dream; end;
end

In some situations we may want to annotate our code like this, for example to specify that a method is deprecated, functionality that is offered by the gem Canivete from Douglas Campos:

class Bomb
  deprecate
  def explode; end
end

In the previous code, when somebody calls Bomp#explode, the interpreter will effectively execute the method, but also will output: Warning: calling deprecated method Bomb.explode.

The key to this behavior is the method_added method which is present in all Ruby modules and which is called every time a method is defined, receiving the name of the method as the parameter.

We can use this method, for example, to create an annotation that specifies that the methods declared below it can only be called by an admin, something like this:

class Module
  def method_added(name)
    unless @_admin_only.nil? or @_proxy_method
      @_proxy_method = true
      alias_method "_admin_#{name}", name
      module_eval <<-STRING
        def #{name}(*args, &block)
          _admin_#{name}(*args, &block) if admin?
        end
      STRING
      @_proxy_method = false
    end
  end
 
  def admin_only
    @_admin_only = true
  end
end
 
class User
  def admin?
    [true, false][rand(2)]
  end
 
  admin_only
 
  def update_password
    puts "password updated"
  end
 
  def restart_server
    puts "server restarted"
  end
end

In the previous code, #admin? will randonmly return true or false and depending of the result the called method will be executed or not.

5 Comments »

  1. Bookmarked under ‘cool ruby code’, I’ve been doing Ruby over 6 years now and I’m always learning new techniques.

    Comment by Kris — May 28, 2010 @ 4:11 am
  2. An interesting point is that the private and protected methods can also take a block, and it would be simple to add that functionality to your helper like so:

    
    def admin_only
      @_admin_only = true
      if block_given?
        yield
        @_admin_only = false
      end
    end
    

    you might also want to override the private and protected methods to set @_admin_only to false again, so that they can work better

    Lenary

    Comment by Lenary — May 28, 2010 @ 8:48 am
  3. Nice article :)
    Does @_admin_only is a class instance variable?

    Comment by slawosz — May 28, 2010 @ 3:21 pm
  4. Great article. Just started with Ruby (and Rails) a few months ago from the Java world and meta-programming is the key to the castle.

    Comment by Eric Anderson — May 29, 2010 @ 5:36 pm
  5. @Lenary

    Yes, you’re right, that would be cool.

    @slawosz

    Yes, I like to named it with ‘_’ to be kind of ‘private’

    Thanks for the feedback.

    Comment by Diego Carrion — June 6, 2010 @ 1:00 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.
(c) 2010 Diego Carrion | powered by WordPress with Barecity