Ruby back to basics – Modules and more de-mystified – Josh Software
Gautam Rege 2012-05-24 15:03:58
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!