Ruby Idioms, Part 7

Date: January 24, 2007 Last modified: January 24, 2007

This idiom I have seen a little more in Rails than I have seen in Ruby, but I am putting it in this Ruby Idioms series anyway.

First off, most of you will know by know that you can "transparently" provide a method in Ruby a Hash. What do I mean by "transparently"? Well have a look at the code example below:

   user = User.find(:first, :include => :preferences)

The last argument is actually a Hash even though you do not see the curly braces at all.

In my opinion it makes for more readable code. So let us look behind the scenes at what the User.find method might do if it wasn't a magic method from ActiveRecord:

  class User
    # ... some stuff here
    class << self
      def find(type, params = {})
        options = @@DEFAULTS.merge(params)
        case type
        when :first
          options[:limit] = 1
        when :all
          # do stuff with options and query database
          # bla bla bla
        end
        # rest of implementation
      end
    end
    # ... some stuff here
  end

This is quite useful, but one of the idioms in the same vain that I really appreciate is the following usage:

  validates_length_of :name, :title, :company, :in => 2..128

This provides another example of supplying a transparent Hash arguments at the end of a list of arguments, the length of which is unknown. For example we may decide that the code needs to actually be more like:

    validates_length_of :name, :company, :in => 2..64
    validates_length_of :title, :in => 2..128, :allow_nil => true # to cater for those ridiculously long job titles out there or those that do not have a job title at all

Now you will notice that the validates_length_of method accepts a list of variable size for the list of attribute symbols to check and then at the end an option hash to specify length checking specifics. How does the method know there is an option Hash at the end to use for these things and/or how long the list of attributes is?

The secret is in the sauce. If we were to look in the source code (though I am not, because I have seen it so many times before), we would see something like:

  def validates_length_of(*attrs)
    options = @@DEFAULT_OPTIONS.merge(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
    # then continue with rest of implementation.
  end

All it is doing here is in the signature, specifying that there is a list of unknown length to be supplied to the method. Then it pops the last item off that list (Array) if and only if the last element of the list is a Hash for use as the options that the implementation will use.

Until the next time enjoy Ruby's "transparent" Hash idioms.