Howto: Write a plug-in

    Myles Eftos

    In my previous post, I listed 6 things that you should try in Rails. I also promised some example code to get you started. Since I have already covered installing and upgrading rails, the next cab off the rank is writing a plug-in.

    Plug-ins are fantastic – they allow you to abstract away common code into nice little bundles that you can re-use on other projects. Rails even has a built-in system for downloading other peoples plug-ins straight from their SVN repository. With the change over to Rails 2.0, some used-to-be-core functionality has been moved into plug-ins, to clean up the core tree and to allow other developers to release new versions of the plug-ins outside of the regular Rails release cycles.

    In this (very brief) tutorial, we will create a plug-in called acts_as_blabbermouth that will print out random quotes . Obviously this plug-in is of little use in the real world, but it should act as a nice demonstration of how plug-ins work.

    It’s really easy to generate the boilerplate code thanks to the generate script. To start your plug-in, run the following command in the root of your Rails app:

    script/generate plugin acts_as_blabbermouth

    You should see an output similar to this:

    create  vendor/plugins/acts_as_blabbermouth/lib
    create  vendor/plugins/acts_as_blabbermouth/tasks
    create  vendor/plugins/acts_as_blabbermouth/test
    create  vendor/plugins/acts_as_blabbermouth/README
    create  vendor/plugins/acts_as_blabbermouth/MIT-LICENSE
    create  vendor/plugins/acts_as_blabbermouth/Rakefile
    create  vendor/plugins/acts_as_blabbermouth/init.rb
    create  vendor/plugins/acts_as_blabbermouth/install.rb
    create  vendor/plugins/acts_as_blabbermouth/uninstall.rb
    create  vendor/plugins/acts_as_blabbermouth/lib/acts_as_blabbermouth.rb
    create  vendor/plugins/acts_as_blabbermouth/tasks/acts_as_blabbermouth_tasks.rake
    create  vendor/plugins/acts_as_blabbermouth/test/acts_as_blabbermouth_test.rb

    As you can see, all of the boilerplate code has been created in the vendor/plugins/acts_as_blabbermouth directory.

    The README and MIT-LICENSE are just generic text files that you should fill out – generally, the README file is the first place a new user will look for instructions on the plug-in.

    The lib directory will hold the guts of your plugiin. The tasks directory is a place where you can store any rake tasks that your plug-in might need. The tests directory is, well, pretty self explanatory – just as you can test your Rails app, you can test your plug-in too. The install.rb and uninstall.rb files are called when installing and uninstalling your plug-in – you can place code here to initialize your environment and to cleanup after yourself. Finally, init.rb is the file that Rails actually calls to load your plug-in.

    The main file we have to worry about is /lib/acts_as_blabbermouth.rb – this is where our main code will go.

    Firstly, we need to include the acts_as_blabbermouth.rb file into the environment. This is done by adding the following line to the init.rb file:

    require File.dirname(__FILE__) + '/lib/acts_as_blabbermouth'

    Next, we add the following code to lib/acts_as_blabbermouth.rb

    # ActsAsBlabbermouth
    module ActiveRecord #:nodoc:
      module Acts #:nodoc:
        module Blabbermouth #:nodoc:
          def self.included(base)
          module ClassMethods
            def acts_as_blabbermouth
              include ActiveRecord::Acts::Blabbermouth::InstanceMethods
              extend ActiveRecord::Acts::Blabbermouth::SingletonMethods
          module SingletonMethods
            def quote_me
              quotes = [
                "When you come to a fork in the road, take it. -Yogi Berra",
                "Every child is an artist. The problem is how to remain an artist once he grows up. -Pablo Picasso",
                "What we anticipate seldom occurs; what we least expected generally happens. -Benjamin Disraeli",
                "Drive-in banks were established so most of the cars today could see their real owners. -E. Joseph Cossman",
                "The greatest pleasure in life is doing what people say you cannot do. -Walter Bagehot"
          module InstanceMethods
            def quote_me
    ActiveRecord::Base.send(:include, ActiveRecord::Acts::Blabbermouth)

    Now, create a model file called quote.rb by running

    script/generate model quote

    and add the following line after the class declaration and before the end declaration


    Fire up the script/console and type:


    Voila! If all went to plan, you should see one of the random quotes. Congratulations! You just wrote a plug-in!

    So how did we do it?

    1. We drilled down in to the ActiveRecord::Acts module and mixed in a module called Blabbermouth. This acts like a namespace in other languages, so we can create our own set of classes and methods without stomping on other peoples plug-ins.
    2. We override the included class method, which gets called when the plug-in gets mixed in to another module. Here, we include the ClassMethods module, which exposes the acts_as_blabbermouth method to the model class
    3. We define the acts_as_blabbermouth method. All this method does is include the InstanceMethods and SingletonMethods modules. The InstanceMethods module contains all of the methods available on an instantiated object and SingletonMethods contains all the methods available to the un-instantiated class.
    4. We create a SingletonMethod called quote_me, which returns the random quote. This can be called by calling Quote.quote_me. We also create a method called quote_me in the InstanceMethods module, which calls the SingletonMethod – this way both the class and the object can call the quote_me method.
    5. Finally, we call ActiveRecord::Base.send(:include, ActiveRecord::Acts::Blabbermouth) which tells the ActiveRecord::Base module to include the code we have written.

    For those of you playing at home, I’ve attached the plug-in source in a tarball, so you can get a better idea of how it all fits together. So off you go, go and create a plug-in yourself!