Ruby do not support multiple inheritance directly, but implement it through Mixin . Mixin in ruby is achieved with include, prepend and extend. prepend is introduced with Ruby 2.0. Lets see, how they work and differ from each other.
Lets define few module and classes. We will experiment with them on irb or Rails console and try to understand their behaviour
module Introduction def introduce puts "Iam Arun" end end module Welcome def greet puts "Hi..How are you" end end module Host def serve_food puts "chiken biryani" end end module SeeOff def bye puts "Byee..." end end class Person prepend Introduction include Welcome include Host extend SeeOff end class Celebrity end
Copy paste above code in irb and keep experimenting as you proceed below :
1 => include and prepend add the module method as instance method, while extend add it as class method.
Calling as class method :
2.2.1 :058 > Person.introduce
NoMethodError: undefined method `introduce’ for Person:Class
2.2.1 :059 > Person.greet
NoMethodError: undefined method `greet’ for Person:Class
2.2.1 :060 > Person.bye
You can see that, only bye method of module SeeOff worked as it is extended , so added as class method
Calling as instance method
2.2.1 :061 > Person.new.introduce
2.2.1 :062 > Person.new.greet
Hi..How are you
2.2.1 :063 > Person.new.bye
NoMethodError: undefined method `bye’ for #<Person:0x8edec04>
Here include and prepend added the module method as instance method to the class, so it worked, but bye method of SeeOff module failed as it is added as a class method so can’t be call on the instance of Person.
Now considering the above behaviour, you may think when to use mixin as class method(using extend) and when to use as instance method(using include or prepened). It depended on the feature a module is providing and how you want to reuse it in your class. Infact in a ordinary class also, you may use the below thumb rule to define a method as a instance method or a class method.
Define a method as class method if you say want to use it within different methods of a class. say prity_print can be a class method
as it can be used in any method to well format the content before retuning it.
Second scenario is that the method behaviour not depend on the instance variable, for example it is not good idea to make full_name as class method as it depend on user’s first_name and last_name which differ from user to user, But say active_users can be a class method as it do not depend on any individual user
2. Adding module method at runtime : use extend to add method only to a specific instantiated object and use include or prepend to add instance methods to all the instance .
Above, the Celebrity class do not have any method, nor it has included or extended any of the module. Now say we want to add bye method at run time to Celebrity.
2.2.1 :065 > Celebrity.bye
NoMethodError: undefined method `bye’ for Celebrity:Class
> Celebrity.extend SeeOff
2.2.1 :067 > Celebrity.bye
Now say. we have Katrina and Deepika as two celebrity.
2.2.1 :074 > katrina = Celebrity.new
2.2.1 :075 > deepika = Celebrity.new
But we want to greet katrina only
2.2.1 :076 > katrina.extend Welcome
2.2.1 :077 > katrina.greet
Hi..How are you
2.2.1 :078 > deepika.greet
NoMethodError: undefined method `greet’ for #<Celebrity:0xe3161f0>
So you can see that, when extend act on katrina i,e an object already instantiated it add greet method of Welcome as instance method to it(remember it add module method as class method to person). Basically, extend can add a module method as class Method or instance method depending on who called it at the runtime. You can check that , we have extended SeeOff as the class method above, so it will not available to katrina or deepika who are basically a instance of Celebrity class.
NoMethodError: undefined method `bye’ for #<Celebrity:0xe29b964>
Now say, you want to introduce yourself to any celebrity, thus the introduce method must be available to all its instance. In this case we have to use include.
2.2.1 :116 >Celebrity.include Introduction
2.2.1 :116 > salman = Celebrity.new
2.2.1 :117 > salman.introduce
2.2.1 :118 >katrina.introduce
Notice that, Introduction module is mixed to Celebrity class after creation of katrina object, but its introduce method also get available to it, this is because in ruby methods are accessed by reference, any object just contain its own variable and refer for any method in its class, so if some method is added later on then also it will be accessible to a object.
3 => Just like all classes are instances of Ruby’s Class, all modules in Ruby are instances of Module.Module is the superclass of Class, so this means that all classes are also modules, and can be used as such.
You can easily check it by calling ancestors method on Person class
2.2.1 :068 > Person.ancestors
=> [Introduction, Person, Host, Welcome, Object, PP::ObjectMixin, Delayed::MessageSending, ActiveSupport::Dependencies::Loadable, V8::Conversion::Object, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject]
2.2.1 :069 >
You can note down below from the above result
- extend do not add the module to the ancestors chain
- prepend add the module as child of the class in which it is called , see that Person become ancestor of Introduction
- include add the ancestors in the reverse order of their call. Host include in last, so it become first ancestor of Person
As you know we can override any method in ancestors class in its child class, and also use super to keep behaviour of ancestor intact and at the same time adding our own behaviour. Let rewrite our initial code to explain this.
module Introduction def introduce puts "Iam Arun" super end end module Welcome def greet puts "Hi..How are you" end end module Host def serve_food puts "chiken biryani" end end module SeeOff def bye puts "Byee..." end end class Person prepend Introduction include Welcome include Host extend SeeOff def serve_food super puts "try lassi" end def introduce puts "Happy to meet you" end end
Now you can get below result when call different methods of the Person class.
2.2.1 :110 > person = Person.new
2.2.1 :111 > person.serve_food
2.2.1 :112 > person.introduce
Happy to meet you
And finally, let’s see an alternative way of extending a class. I do not use it myself as I believe in keeping the things simpler. But if you go through, gems and different library, you can see the use of self.included(base) .
Basically included is a callback , which get triggered whenever a module is included into another module or class. The argument base passed to it is basically the target module or class i,e that in which the module is included. Don’t get confused as initially I was. See the example below:
module Welcome def self.included(base) base.extend SeeOff end def greet puts "Hi..How are you" end end module SeeOff def bye puts "Byee..." end end class Person include Welcome end
Note that, In Person class, we have only included the Welcome module, but in the Welcome module we have defined included method within which we have extended the SeeOff class with the line base.extend SeeOff , since Welcome module is included in Person class, when included callback get fired, Person will be become base class for it.So base.extend SeeOff will evaluated as Person.extend SeeOff .
You can check the result in console now:
2.2.1 :037 > Person.bye
So you can see that bye method of SeeOff class get added as class method to Person class.
That’s all about mixin in ruby..🙂