< Home

My love affair with Ruby around aliases

Recently I have been dipping in and out of metaprogramming in Ruby. After a while I came to the conclusion, or rather I was pushed by fellow colleges into believing that ruby metaprogramming is not necessary on a day to day basis, and should be used sparingly. The reason behind this is because it can be rather confusing to somebody who has a lack of experience in metaprogramming to understand what is going on. Say for example a junior developer gets assigned a project that is heavily based on metaprogramming, it will take them twice as long to work out what is going on and making changes can cause a ripple effect if you do not know what you are doing. Does the benefit on DRY become outweighed by readability at this point? I will let you draw your own conclusion on that argument.

However there are some situations where small sprinkles of sugar can help you achieve something that, may otherwise prove difficult to achieve in a friendly fashion. Ruby’s Around Aliases are an example of this.

To give you a very brief overview of aliases, Ruby aliases allow you to give an alternate name to a method.

An example would be:

class SayHello
  def hello

  alias :hi :hello

obj = SayHello.new
obj.hello   #=> "Hello"
obj.hi      #=> "Hello"

As you can see in the above code, I have created a class called SayHello that holds an instance method of hello. I have then called alias :hi :hello, which then allows me to call the hello instance method by calling either .hi or .hello.

With this you create an alias, then redefine the method that you created the alias on, the alias would then still reference the old method.

An example would be:

class Array
  alias :len :length

  def length
    len > 5 ? "Long" : "Short"

obj = ["1", "2", "3", "4", "5", "6"]
obj.length  #=> "Long"
obj.len     #=> 6

As you can see, we created an alias at the top of the class and then defined the aliased method later on in the class. Now when I call Array#len it returns an integer containing the length of the array but calling Array#length will return the result of the conditional in our new length definition.

This is an interesting and very important distinction. The ability to redefine a method but keep a reference to the old definition is a very valuable asset.

So lets go ahead and create an around alias. In order to create an around alias you have to follow three steps:

  1. Alias a method
  2. Redefine the method
  3. Call the old method (the alias that you created) from within the new method definition.

So, as I said at the very beginning, around aliases can help you achieve something that may otherwise prove difficult. Let me show you an example of when I have found it to be useful.

I have recently carried out a simple integration with the Shopify API through their gem. I found myself calling valid? - which is an instance method on the ShopifyAPI::Product class - multiple times. Understandably this is a predicate method, but I was using this class through the command line, so I wanted to receive an indication of the errors caused at the same time as calling valid?.

As this was a quick integration, I went ahead and redefined the method as below:

class ShopifyAPI::Product
  alias :original_valid? :valid?
  def valid?
    if !self.original_valid?
      puts "The following errors occurred: #{self.errors.full_messages.join(", ")}"

Now, by calling .valid? on an instance of ShopifyAPI::Product it will still return the expected predicate. However at the same time if the method returns false. it will also print out the errors.

So there you have it, I believe that a basic understanding on Ruby metaprogramming is a must have for every Ruby developer, however I would urge you to steer away from throwing it in at every given opportunity and suggest that you are more selective in your use of metaprogramming.

In my opinion it’s fine to add large amounts of metaprogramming if you do not expect somebody that does not know the code to touch parts of the code very often, an example of this would be a library i.e. ActiveRecord. I also see it fit to use metaprogramming in situations where you could end up in a sticky situation in order to achieve your goal.