Say that for any Person we wanted to be able to display a greeting that would include the person's name and the explicit class of the person. Ruby is a single inheritance language, and all our Person classes implicitly inherit from Object, so there's no obvious parent class that we can use to provide this functionality (in principle we could implement it on Object, but we haven't talked about that yet and it would also be a rather messy solution). Mix ins to the rescue! Here's the implementation of the mixin:


module PersonUtilities
  def greeting
    "Greetings, #{name} (#{self.class.name})"
  end
end

and here's the altered implementations of our three Person classes:


class Person
  include PersonUtilities
  def name
    'Steve Hayes'
  end
end

module Distinguished
  class Person
    include PersonUtilities
    def name
      'Shakespeare'
    end
  end
end

module Very
  module Distinguished
    class Person
      include PersonUtilities
      def name
        'Einstein'
      end
    end
  end
end

Notice that the implementation of #greeting referred to a method that the module expected the including class to provide (#name), and to self, which resolves to the including class at runtime.

Here's the results of invoking #greeting on each of our three classes:


Person.new.greeting # => "Greetings, Steve Hayes (Person)"
Distinguished::Person.new.greeting # => "Greetings, Shakespeare (Distinguished::Person)"
Very::Distinguished::Person.new.greeting # => "Greetings, Einstein (Very::Distinguished::Person)"

Mix ins are a key part of very common Ruby classes. Object mixes in the Kernel module (and performs some magic to make the Kernel methods private), and most of the iterators we described in the previous column are provided by the Enumerable module. Enumerable's only expectation is that the including class will provide an implementation of #each -- all the other methods in Enumerable ultimately rely only on that. So if you implement a new class with an appropriate #each, you can mix in Enumerable and get all the other iteration methods for free.

As an example, here's a simple class that takes a brute force approach to converting a number to a set of digits -- not elegantly, but it does demonstrate a number of Ruby features:


class Digits
  attr_reader :digits
  def initialize(number)
    string = number.to_s
    @digits = (0..string.size-1).inject([]) {|digits, index| digits << string[index,1].to_i}
  end  
end

You can use this to iterate over the digits in a number by returning the digits array and iterating over that. So to get the squares of the digits, we could do this:


Digits.new(123).digits.collect{|each| each*each} # => [1, 4, 9]

However, exposing the digits attribute isn't very object-oriented; it would be much better if the Digits class handled iteration directly, and Enumerable makes this very easy:


class Digits

  include Enumerable

  def initialize(number)
    string = number.to_s
    @digits = (0..string.size-1).inject([]) {|digits, index| digits << string[index,1].to_i}
  end

  def each(&block)
    @digits.each(&block)
  end
 
end

Now we can iterate directly over the Digits object, and we're free to change the implementation any way we like:


Digits.new(123).collect{|each| each*each} # => [1, 4, 9]

In addition to modules, Ruby has one other feature that makes it very flexible and extensible -- in contrast to many other languages, all the Ruby classes are open to extension.

This means that you are free to add new methods to an existing Ruby class, and to change the behaviour of existing methods. Java projects, for example, commonly build up an extensive StringUtils class to hold all methods that the developers would like to invoke on String instances, but which cannot be implemented directly on String since in Java the class is closed.

One concrete example is the Apache Commons StringUtils class providing isBlank() to check whether a string is white space, empty, or null. The invocation is:


StringUtils.isBlank(aString)

In Ruby, we can implement this method directly on String (for simplicity we'll just look at empty or null):


class String
  def blank?
    self.strip.empty?
  end  
end

This handles the String case, but doesn't check for null. That's no problem as the Ruby equivalent, nil, is an instance of NilClass, which is also open, so we can therefore implement:


class NilClass 
  def blank?
    true
  end
end

and we get an expression that's more natural for an object-oriented language:


's'.blank? # => false
''.blank? # => true
nil.blank? # => true

As you can see, the Ruby language has a number of features that make it easy to organise code in ways that avoid duplication and makes your expressions clear and easy to understand. Ruby also has tools for introspection built in as natural parts of the language, so you're free to explore existing structures and implementations to improve your understanding of the language and help you find better ways of solving your particular problems.

Do you need help with Ruby? Gain advice from Builder AU forums

Comments

1

nona - 14/09/07

Articles and content in this section of the website are really amazing.

» Report offensive content

2

Shallu - 15/11/07

I really enjoyed my visit to this website.

» Report offensive content

2

Shallu - 15/11/07

I really enjoyed my visit to this website. ... more

1

nona - 14/09/07

Articles and content in this section of the website are really amazing. ... more

Log in


Sign up | Forgot your password?

What's on?

  • Optus Deal

    Broadband + home phone + PlayStation®3 in a single package price!