Ruby back to basics – Modules and more de-mystified

I often teach Ruby at corporates, take some classes at colleges and speak at some events. Its great when I encounter aggressive and passionate programmers who question and counter question me at every step. These are some of the questions that I have often encountered regarding modules

  • How do I know from an object which all modules are included?
  • How are the methods included from a module resolved and in what order?
  • Can I cherry-pick methods from a module?
  • Can I choose which module method I want to invoke incase the same method is defined in more than one included module?

This got me searching for answers and digging deep into Ruby. And lo and behold, I learnt something new myself. Some of this stuff has been available for ages but very often just ignored or taken for granted or just treated as SEP (as Douglas Adams would put it).

Ruby crushes conventional Object Oriented Concepts

  • In Ruby, all methods (public, protected and private) are available to the inherited classes. That is how we can call include or extend in a Class even though they are defined as private methods in the class Module.
  • In Ruby, private and protected are in the context of an Object and not the Class.

The deal with private and protected is that “Inside a class, we can access the private methods of our own instance i.e. self” but “Inside a class, we can access protected methods of other objects of the same class”. And I thought protected set the method access level for inherited classes!

class Base
  protected
  def foo
    puts "Base:foo"
  end
  private
  def bar
    puts "Base:bar"
  end
end

class Derived < Base
  def test(other)
    other.foo
  end
  def test1(other)
    other.bar rescue puts "Oops.. private method"
  end
end

a1 = Derived.new
a2 = Derived.new
a1.test(a1)       # Base:foo
a1.test(a2)       # Base:foo
a1.test1(a1)      # Oops.. private method # even on self!!
a1.test1(a2)      # Oops.. private method

Ruby Object Hierarchy

This ‘chart’ is straight from the ruby-doc.org and it paints a “pretty” picture 🙂

Classes, modules, and objects are interrelated. In the diagram that follows, the vertical arrows represent inheritance, and the parentheses meta-classes. All metaclasses are instances of the class `Class’.

                         
                         +---------+             +-...
                         |         |             |
         BasicObject-----|-->(BasicObject)-------|-...
             ^           |         ^             |
             |           |         |             |
          Object---------|----->(Object)---------|-...
             ^           |         ^             |
             |           |         |             |
             +-------+   |         +--------+    |
             |       |   |         |        |    |
             |    Module-|---------|--->(Module)-|-...
             |       ^   |         |        ^    |
             |       |   |         |        |    |
             |     Class-|---------|---->(Class)-|-...
             |       ^   |         |        ^    |
             |       +---+         |        +----+
             |                     |
obj--->OtherClass---------->(OtherClass)-----------...

Basically, Class inherits from Module, Object and BasicObject and all Meta-Classes finally inherit from Class. In simple words – Modules in Ruby is multiple inheritance with a twist!

Cherry picking Modules methods

Of course we know how modules work. However, in the following example, can I choose the instance method foo from module A or module B?

module A
  def foo
    puts "A:foo"
  end
end

module B
  def foo
    puts "B:foo"
  end
end

class Base
  include A
  include B
end

class BaseAgain
  include B
  include A
end

BaseAgain.new.foo   # => A:foo
Base.new.foo        # => B:foo

Suppose I want to call the instance method foo from the A module on the object of Base, can I do so?

class Base
  include A
  include B

  def foo
    A.foo       # incorrect: Cannot call foo like a class method.
  end
end

Base.new.foo     # =>  undefined method `foo' for A:Module

This is correct, since we have included the methods – i.e. all the methods are now instance methods. This is how we can choose which Module method to invoke on an instance.

module A
  def foo
    puts "A:foo"
  end
end

module B
  def foo
    puts "B:foo"
  end
end

class Base
 include A
 include B

 def foo(module_name=A)
    module_name.instance_method(:foo).bind(self).call
 end

end

puts Base.new.is_a?(A)  # => true
puts Base.include?(A)   # => true

Base.new.foo(B)         # => B:foo
Base.new.foo(A)         # => A:foo
Base.new.foo            # => A:foo

The interesting concept unearthed here was that of Bound and Unbound methods.

def foo(module_name=A)
    module_name.instance_method(:foo).bind(self).call
end

This is awesome! instance_method returns an unbound method. These are the naked methods of the module. They cannot be called unless they are bound to an object of that module. Since the class includes that module (under the cover, its safe to say that it inherits from that module), the method can be bound to self and then called.

Now, I can cherry pick which method I want called even if I have included more than one module with the same method name in it!

Advertisements

About Gautam Rege

Rubyist, Entrepreneur and co-founder of Josh-Software - one of the leading Ruby development shops in India.
This entry was posted in Ruby, Tutorials and tagged . Bookmark the permalink.

2 Responses to Ruby back to basics – Modules and more de-mystified

  1. hanumakanth says:

    Thanks its nice to know all these..

  2. metamitya says:

    Made a visualization of the ruby object model in case anyone is interested: https://mitya777.github.io/dev/2015/06/08/ruby-object-model/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s