Page 1 of 1

Ruby: Modules and Mixins

#1 EdwinNameless  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 120
  • View blog
  • Posts: 710
  • Joined: 15-October 09

Posted 01 February 2010 - 04:40 AM

Introduction

Did I mention that Ruby was awesome? Oh yes, I did: that was in the tutorial regarding blocks and iterators. Blocks are one of the cool things I really like in Ruby. In this tutorial, I'll be talking about something another for loving Ruby: modules and mixins. It requires a bit of knowledge of classes in Ruby, so if you are still unsure about them, go and read this tutorial written by qbertoli3.

Now that you have some expertise in Ruby classes, let's dive into the amazing world of Ruby modules and mixins. Don't worry, it comes with a wizard.

Modules

Modules is actually very much like classes, in that it groups together some methods, constants, classes. One major difference is that they cannot be instantiated: you cannot call new on a module. They are also defined using the module keyword instead of using class:


module Camelot
  class SpellInvoker
    def self.invoke spell
      # Spells must be cast 3 times in Camelot
      3.times{ p spell }
      # And they not always work...
      if (rand(1) == 1)
        p "Spell worked!"
      else
        p "Spell failed..."
      end
    end
  end

  def Camelot.invoke_spell spell
    SpellInvoker.invoke(spell)
  end
end



When it comes to casting the spell, you can then call the module method as follows:

require 'camelot'

Camelot.invoke_spell("Ladeedah, change into a frog!!")



So, you see: not much difference with calling a static method on a class. What are modules used for, then? The first use is simple, and pretty much equivalent to packages in Java, or, closer, namespaces in C++: related elements can be grouped together for convenience and to avoid naming conflicts. The second one, more Ruby-specific is to introduce mixins which is actually the second part of this tutorial.

But first, let's see more things about modules. For the rest of this section, we will actually use the following Camelot module:

module Camelot
  class SpellInvoker
	def self.invoke spell
	  # Spells must be cast 3 times in Camelot
	  3.times{ p spell }
	  # And they not always work...
	  if (rand(1) == 1)
		p "Spell worked!"
	  else
		p "Spell failed..."
	  end
	end
  end

  def invoke_spell spell
	SpellInvoker.invoke(spell)
  end
end



Notice how invoke_spell is now defined on its own (that is invoke_spell, instead of Camelot.invoke_spell). How will we able to call it, then? This way:

require 'camelot'

class Merlin
  def cast_a_spell

    # The extend keywords brings in the methods from Camelot module
    extend Camelot
    invoke_spell("Ladeedah, change into a frog!!")
  end
end

Merlin.new.cast_a_spell



In the example above, we have defined the Camelot module, and given life to Merlin as a class called Merlin: it was pretty straightforward to show the use of a method from a module. But we would like Merlin to be a wizard capable of casting spells. So our first step will be to define our wizard class:

class Wizard
  attr_reader :name
  attr_accessor :greeting
  def initialize name
    @name = name
  end
end



And obviously, we define Merlin as an instance of this class:
merlin = Wizard.new("Merlin")
merlin.greeting="Ladeedah"



But Merlin, being the talented wizard from Camelot that he is should be able to cast spells. So we'll borrow the elements from the Camelot module to give them to Merlin:


merlin.extend Camelot
merlin.invoke_spell("#{merlin.greeting}, turn into a pangolin!")



Again, we used the extend keyword, but this time on an instance, rather than on a class. This means you can change an instance directly without having to change its class directly.

We used modules with a custom class, but it can also be done with a core class such as String:

module Leet
  LEET_MAP = { "E" => "3", "A" => "4", "L" => "1", "Z" => "2", "B" => "8", "M" => "|v|" , "T" => "7", "S" => "5" }

  def to_leet
    leet = ""
    self.to_s.each_char do 
      |c| 
      if LEET_MAP[c] != nil
        leet += LEET_MAP[c]
      else
        leet += c
      end
    end
    leet
  end
end

class String 
  def to_leet
    extend Leet
    self.to_leet
  end
end
puts "LEET WIZARDS RULE!!".to_leet



or apply the module to a single instance, rather than to the String class:

merlin_motto = "MERLIN ROCKS"
merlin_motto.extend Leet
puts merlin_motto.to_leet




Obviously, being able to modify core classes must come with great care!

Mixins

We have seen modules, and how they can be used. What are mixins, then? Well, we have pretty much seen some already: it is a class and a module that are intertwined. For example:

module Camelot
  class SpellInvoker
	def self.invoke spell
	  # Spells must be cast 3 times in Camelot
	  3.times{ p spell }
	  # And they don't always work...
	  if (rand(1) == 1)
		p "Spell worked!"
	  else
		p "Spell failed..."
	  end
	end
  end

  def invoke_spell spell
	SpellInvoker.invoke(spell)
  end
end

class Wizard
  attr_accessor :greeting
  def initialize name
	@name = name
  end
  include Camelot
end

merlin = Wizard.new("Merlin")
merlin.greeting="Ladeedah"
merlin.invoke_spell("#{merlin.greeting}, turn into a pangolin!")



Here, we just include Camelot, and this brings in Camelot methods into the Wizard class. Let's create a subclass to Wizard now:

class WelshWizard < Wizard
  def initialize name
	super(name)
	@greeting = "Ladeedah"
  end
end

merlin = WelshWizard.new("Merlin")
merlin.invoke_spell("#{merlin.greeting}, transmogrify into rough bindweed!")



At this point, you should see where I want to get: inheritance. In Ruby, there is no multiple inheritance. But mixins provide another way of bringing behaviour into a class without resorting to inheritance! So, our Welsh wizard now can speak leet:

class WelshWizard < Wizard
  require 'leet'
  def initialize name
	super(name)
	@greeting = ""
  end
  def to_s
	"#{@name.upcase}, #{self.class.to_s.upcase}"
  end
  include Leet
end

merlin = WelshWizard.new("Merlin")
merlin.invoke_spell("#{merlin.to_leet}, turn into a pangolin!")



We call require 'leet' inside the class where we are using it the module, and then just include the Leet module. This allows us to call merlin.to_leet, pretty much like we would have done, had we inherited a class Leet. This is confirmed by calling puts WelshWizard.instance_methods.sort.join(","): to_leet is part of the methods of the class!

What happens if the superclass also defines a to_leet method, different to the one imported into the subclass? The method is overridden, very much like what happens with inheritance.


Conclusion

Step 2 of our journey to Ruby awesomeness has brought us to modules and mixins, and it has given us an alternative to multiple inheritance. I am sure you are now looking forward to the next steps, and I can assure you, you won't be disappointed.

Is This A Good Question/Topic? 0
  • +

Replies To: Ruby: Modules and Mixins

#2 Guest_Adhi*


Reputation:

Posted 17 February 2010 - 02:19 AM

Hope this helps...
http://adhirajrankha...odules-in-ruby/
Was This Post Helpful? 0

#3 Guest_Adhi*


Reputation:

Posted 17 February 2010 - 02:25 AM

Do give a look here:
http://adhirajrankha...odules-in-ruby/
Was This Post Helpful? 0

Page 1 of 1