Extending Rails Through Plugins

 

By John Yerhot

While Rails is a full stack web framework, by design Rails does not aim to include every possible feature.  There are many reasons that the Rails Core Team would choose not to include a feature - too unstable, too obscure, or simply not needed in the core Rails distribution.  In fact, there have been instances where features have been removed from Rails and placed into a plugin!  For example, in_place_edit and auto_complete_for were removed from Rails in version 2.0.

To help developers who are looking to add, replace, or modify Rails' feature set, Rails has been built with a highly extensible infrastructure.  Most additions and modifications come as plugins. While many plugins extend one of the major classes in Rails, like ActionView::Base or ActiveRecord::Base, you are free to create your own classes or modules.  Moreover, plugins can have their own Rake Tasks and tests.  Essentially, plugins are self contained Rails specific libraries.


One of the best ways to use plugins is the reuse code you find yourself using from project to project.  Robby Russell has an excellent example his team at Planet Argon used in their applications, Flash Message Conductor.  Finding that Rails' flash was inadequate for their applications, they were rewriting much of it from application to application.  The team created a plugin that added helpers to add messages, errors, and notices to flash and a new render_flash_messages method to render them in a view. By using Flash Message Conductor in their applications, Planet Argon has an improved flash, a common interface, and in a very DRY fashion.


For this article, we will construct a simple plugin that will add a quote method to our models. Our goal is  very simple functionality.

a = User.new
a.quote 
=> "If it bleeds, we can kill it."

We will create a Rake task to generate a YAML file with our quotes, load that YAML file and use the data for our quotes. While this is a fairly trivial plugin, my aim is not to teach you how to write a large, complex plugin, but give you the start you need.  Lets get started!


Rails provides us with a generator for creating plugins.

script/generate plugin quote 

This will create a bare plugin in your vendor/plugin directory with the following structure:

Plugin structure

init.rb - Loaded upon Rails starting.  More often than not, it will require your plugin files in the lib directory.
install.rb - Runs when you install the plugin using Rails' script/plugin install command.
lib/ - The lib directory is automatically added to Rails' load path.  Usually your plugin code will reside here.  
MIT-LICENSE - Your plugin should include a license, MIT or otherwise.
Rakefile - The main Rake definition for your plugin.
README - You plugin's readme.  A short synopsis of your
plugin, its usage, and any other notes would go here.
tasks/ - Any custom Rake tasks can go here.  For our plugin, we will create one.
test/ - Your plugin's tests should go here.  When tests are run on a plugin, Rails is not loaded.

Before we go any further, we should create a plan for how exactly our quote plugin should work.  First, we should create a Rake task which creates our YAML file filled with quotes.  After our Rake task is completed, we will create an init.rb file that will require the our quote.rb file in the lib directory and load our quotes.yml file into an array of quotes.  We will create a new Module, Quote::ClassMethods to house our new methods, and add those methods to ActiveRecord::Base as to be available in models.


We will also assume that you have a bare application with a User model.


First, lets look at our tasks/quote_task.rake file.  We need to first create a new namespace for our plugin, and add a setup task.

namespace :quote do 
  desc "Create Quotes YAML file in the config directory" 
  task(:setup) do 
       
  end 
end 

Our task only needs to create a YAML file, so we will use Ruby's File library to create our file and the puts method to write to it.

namespace :quote do
  desc "Create Quotes YML file in the config direcory"
  task(:setup) do
    puts "Creating #{RAILS_ROOT}/config/quotes.yml" 
    quotes = File.new("#{RAILS_ROOT}/config/quotes.yml", "w") 
    quotes.puts( 
      "0: Come with me if you wanna live! \n1: If it bleeds, we can kill it.\n2: Its not a tumor!" 
    ) 
  end 
end 

Try running the task and you should see a newly generated YAML file in your config/ directory.

rake quote:setup 

Next, we will get our init.rb file in order.  As mentioned before, this is loaded upon Rails' initialization, init.rb is run.  We should do any requires and in our case, open ActiveRecord and include our new methods.

require "quote.rb" 
ActiveRecord::Base.send :include, Quote::ClassMethods 

Great.  Lets start on the fun part! Open up lib/quote.rb. Any real magic is going to happen here.  In a larger plugin, we will probably use different files for our different classes or modules, but for ours we will only need to use quote.rb for our single method.

# Quote 
module Quote 
  module ClassMethods 
    # displays a random quote frou our quotes.yml 
    def quote 
     quotes=YAML.load_file("#{RAILS_ROOT}/config/quotes.yml")
     quotes[rand(quotes.length)] 
    end 
  end 
end 

This should be fairly self explanatory.  Each time we call the quote method, our quotes.yml file is read, and a random quote is returned from that file.  Lets give it a try.

$ ruby script/console 
Loading development environment (Rails 2.2.2) 
>> a = User.new 
=> #<User id: nil, nane: nil, created_at: nil, updated_at: nil> 
>> a.quote 
=> "If it bleeds, we can kill it." 
>> a.quote
=> "Come with me if you wanna live!" 
>>  

It is working!  Finally, lets complete the README and have it displayed back upon installation.

README
Quote 
===== 
Displays back an awesome Arnold quote in your models! 
After installation, be sure to run  
rake quote:setup 
Example 
======= 
a = User.new 
a.quote => "Get to the chopper!" 
Copyright (c) 2009 John Yerhot, released under the MIT license 

And to our install.rb we will have the README displayed upon installation.

install.rb
puts IO.read(File.join(File.dirname(__FILE__), 'README')) 

There you have it. For your convenience I’ve created a repository for this plugin at Github .  Start by removing the plugin from your application by deleting its directory from vendor/plugins/. Next, install the plugin from Github.

script/plugin install git@github.com:johnyerhot/arnold_quotes.git

If all goes well, the plugin will install and you should see the contents of README displayed back.  If you have issues, make sure you have Git installed.


Plugins are a great way to add functionality to your Rails application in a concise and organized fashion.  There is still much to cover, plugin testing being an important piece. 


For further reading I suggest Advanced Rails by Brad Ediger  which has an entire chapter devoted on plugin development, including testing. Another in depth guide is available at guides.rails.info . As always, one of the best ways to see how a larger plugin is developed, try browsing the source of some of the more popular plugins such as Restful-Authentication or Paperclip.  Most can be found on Github.


Discuss this article

Published in Issue #1: The Beginning

Back