In the previous post “Organizing Your Code With The Help Of Modules” we created our very first outline of a module (though very basic) and mix it in a class via the include method (in this case we can refer to modules as mix-ins).
If you haven’t noticed by now, the act of mixing in a module strongly resembles inheritance from a superclass. So to say that if class Second inherits from class First, then instances of class Second can call the instance methods of class First. In the same way, if for example class Third mixed in module TheModule, instances of class Third can now call the instance methods of module TheModule.
The similarities in both cases congeal in the fact that the instances of the class at a lower level (the one that inherits, in our examples being class Second for classical inheritance and class Third for mix-ins) get to call both their own methods (as defined inside the object) but also those externally defined in class First (superclass object) or TheModule (mixed-in module).
Now you might ask “what’s the point of having both if they act the same?”.
Well although they act the same, they do not impose the same limitations. You see, you can mix in more than just one module. Remember that in classical inheritance in OOP, you only ever inherit (directly) from one parent or superclass via an established class hierarchy. To be able to inherit from more than just one, you need to inherit (indirectly) from an ancestor (the superclass of the superclass). But there is a limit of how high (or how low, depending on what you focus on) you can go without incurring heavy penalties.
So want should a programmer do in case you want or need numerous extra behavior in a class? Well, you tuck all the extra bits neatly away in modules. This helps with maintenance since you break up multiple jobs in simple components that are clear and unconfusing. It also helps DRY out your code since you can (and probably should) use one module in more that one class, depending on requirements. Now each module brings something new to the table (to the class) and together they get the job done.
Now onto the next issue at hand. You know from personal experience that methods tend to share names. And also from personal experience you know that this is important because you always want to receive the method you want to call. To do this, let’s take a quick glance at how Method Lookup works?
You have an object. This object receives a message. Now this object needs to figure out some things. It needs to figure out: a) “Can I respond to this message?” and b) “If yes, How should I respond to it?”. So if our object receives a request to resolve a message into a method, it will search, in order, in these following places:
- Look for it in its own class
- Looks for Modules mixed into the class then checks inside the Modules in reverse order of inclusion
- Looks for it inside the class’s superclass
- Looks for Modules mixed into the superclass and then checks inside the Modules in reverse order of inclusion
- If all fails, it then looks inside class Object , then inside it’s mix-in Kernel and finally inside BasicObject (the final superclass)
Let’s follow that lookup path with an example
So how did the magic happen?
Well it all began when our object conveniently named obj was suddenly and abruptly woken up by receiving a message to call a method. So it jumped up and said:
“I’m an instance of my class, MyClass. Does the class I reside in contain a greet_reader method?”
=> No, it does not.
“Okie dokie, then does MyClass include any modules I can look in?”
=> No, it does not.
“Fine, then does MyClass ‘s superclass (InheritedClass) have the method definition?”
=> No, it does not.
“O.k, o.k the does InheritedClass include any Modules in it?”
=> Yes, it does indeed include the module FriendlyModule.
“And does FriendlyModule define a greet_reader method?
=> Yes, it does indeed define a greet_reader method
“Alrighty then, I’ll execute it!”
Had the obj ‘s search ended up in failure, it would have triggered method_missing which gets called as a last ditch effort in case of an “unmatched message”.
The example I gave is pueril and just at a quick glance you can see what method the object can call. But the same steps are traversed even with big Classes and Modules in huge aps. Knowing how the Lookup works can really save you allot of hassle when working with legacy code, or wore, Monolith aps.
But we’ll check out more about models and their usefulness in another post.