Garbage Burrito

Ruby Mixins: the Magic Behind Plugins

Ruby Mixins: the Magic Behind Plugins
Ben Kittrell - 12/20/2006 13:45:00
Comments: 1
Last Comment: 10/11/2007 07:24:43

Ruby on Rails has given me a passion for elegant code.   I guess it's always been there, but not so much attainable with other languages or frameworks.  A large part of Rails' elegance comes from it's extensive use of Ruby's mixins, which allows you to inherit methods from multiple sources.  

One of the ways I've kept doodlekit elegant, or at least attempted to, is through the use of Rails plugins.  At first I was a little scared of the thought of writing plugins.  "I don't have time to learn another architecture" I told myself.  Then I realized, "Wait a minute, this is Rails, it can't be that bad".  Plugins are very simple which is what makes them so wonderful.  At their core, they're a directory with an init.rb file and a lib directory with ruby scripts.  The lib directory usually contains Modules aka Mixins and the init.rb will be automatically run and dynamically apply the mixins to other classes.

There are already plenty of good tutorials on plugins, so I'd like to concentrate on the real magic of plugins, mixins.  There are most certainly very good tutorials on mixins as well, but I think their simplicity and elegance should be underscored.

Mixins come from Modules, which have two purposes. from pickaxe...

   1. Modules provide a namespace and prevent name clashes.
   2. Modules implement the mixin facility.

   
So when do you need a mixin?  Anytime you have functionality that's used in multiple places, you can extract into a Module, and use it as a Mixin.  koz warns against premature extraction on The Rails Way, but there are many times where it makes perfect sense.  Many people ask, where do I put utility methods like date parsers and such.  Well if it can't go in a Helper(which is just a mixin with more magic), then put it in a mixin.  For some basic examples, see pickaxe.

A simple example of mixin functionality can be seen in my CP1252 to UTF-8 convertor.  Breaking it down to its simplest form it looks something like this.

  module CharacterEncoding
    CP_MAP = {
      "\x80" => "U+20AC",    # EURO SIGN
      "\x82" => "U+201A",    # SINGLE LOW-9 QUOTATION MARK
      "\x83" => "U+0192",    # LATIN SMALL LETTER F WITH HOOK
    ...
    }
    
    CP1252 = CP_MAP.keys.join
    UTF = CP_MAP.values.join
    
    def to_unicode(column)
      define_method "#{column}_to_unicode" do
        self.send(column).tr!(CP1252,u(UTF))
            unless self.send(column).nil?

      end    

      before_save "#{column}_to_unicode".to_sym
    end
  end

This particular implementation is irrelevant.  The only thing that's really important is that we have a module with a method in it.  Without using plugins, you would simply put this in a file called character_encoding.rb in the rails lib directory.  Then you'd use it with the following code.

  class Entry < ActiveRecord::Base
    extend CharacterEncoding

    to_unicode :description
        
What this will do is create an instance method on the object where the name is the name of the column suffixed with "_to_unicode".  Then we define a before_save callback that calls that method.  

You'll notice that I'm using "extend", instead of "include".  The difference is whether you using class or instance methods respectively.  Even though to_unicode creates an instance method, it itself is a class method.

So whether or not you're writing plugins, you'd be doing yourself a disservice by ignoring the power of mixins.  

Comments: 1
Last Comment: 10/11/2007 07:24:43

Comments

1. Milan Jaric   |   10/11/2007 07:24:43

Nice!!!

Post a Comment


Are you human? Please enter the word below.
M3nhaw50lmpwzzeynjgznzc0mty=


powered by Doodlekit™ Free Website Builder by Doodlebit™ Website Company