GemPlugins: A Brief Introduction to the Future of Rails Plugins

Posted by on June 11th, 2008.

The new Gem Dependencies of Rails 2.1 give developers an easier-than-ever ability to keep track of and maintain the various library dependencies inherent with any project. However, a much-overlooked additional feature of the Gem Dependencies is the ability to package traditional Rails plugins as a gem and have them hooked in properly. This article is designed as an introduction to how to write and use plugins as gems in Rails projects.

The Basics

The basic method by which this is achievable is that any plugin included through a config.gem command will automatically have the gem-packed file rails/init.rb run upon Rails’s initialization. All it takes is a little bit of effort, and any Rails plugin can be packaged as a gem and easily depended upon through gem dependencies.

You may be wondering why this is a “big deal.” Plugins are already dead simple to install in Rails (and you can even script/plugin install straight from Git now!), why do we need GemPlugins? It’s simple, really: RubyGems are a rock-solid established way of easily distributing versioned reusable bits of code. Using gems for plugins allows for a greater standardization of the way in which plugins are maintained and distributed, as well as a simple path for version-locking to ensure compatibility with legacy code etc.

Another reason that GemPlugins are important is that they provide a level of abstraction from Rails: by releasing a gem rails/init.rb you could also use the same exact code to release a Merb plugin or any other framework that supports gemified add-ons. I think you will begin to see a number of cross-framework plugins be developed as Rails gets some company and shares alike.

Using a GemPlugin

First, let’s go through the process required to use an existing gem plugin. I’m going to be using my Acts As Taggable On plugin as an example throughout because I just recently went through the process of making it available as a gem.

First, you will need to include the dependency in your environment.rb file. I’m assuming here that most plugins are going to be hosted on GitHub, but the same should be true for any gem source.

# in environment.rb

config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com", :lib => "acts-as-taggable-on"

This is the standard usage of gem dependencies, and for more info on this you can see Ryan Daigle’s post or watch the RailsCast on the subject. Now assuming that you don’t already have the gem in question installed, it’s simple to grab it:

rake gems:install

This will automatically install any gem dependencies in your project, and will tell you what’s happening the same as if you had run gem install from the command line.

That’s it! Once you have successfully installed the necessary gem, you can simply start up your Rails server and the plugin will be loaded and initialized as though it were living in your vendor/plugins directory.

Now that you know how to use a GemPlugin, I’ll show you how you can take an existing plugin and gemify it quickly and painlessly.

Making a GemPlugin

Let’s say I have a plugin called awesome_fu that lives on GitHub at mbleigh/awesome-fu. I’ve already released this plugin, it works great, and now I want to make it compatible with GemPlugins.

First, let’s create a gemspec called awesome-fu.gemspec in line with the requirements for the GitHub Gem Repository. In order to make the file list, I usually find it’s easiest to “find **” in the plugin directory, then copy it into TextMate, make the modifications I need for manifest (using a regular expression to quote each of the files), and saving it in the spec. If you have only a few files in your plugin, it may be easier just to add them by hand.

Next we need to add rails/init.rb. This is a little bit troublesome, because we still want our plugin to work if installed through the traditional method, so we also need init.rb to run the same code (this is automatically fine in edge Rails). What I did for my plugin is copy all of my init.rb code into rails/init.rb and then change init.rb to the following:

require File.dirname(__FILE__) + "/rails/init"

Now they both run the same code without any kind of replication, great! This means that now I have set up my plugin to work equally as a GemPlugin or a traditional plugin with just a couple minutes of work.

All that’s left to do is switch on the RubyGem setting for my GitHub project, update the README, and push! Now anyone will be able to easily require the plugin as a gem dependency and you will get all of the accolades associated with releasing your plugin the “new and hip” way.

Caveat Coder

The one problem with GemPlugins that I have run into is that if you unpack your gems using “rake gems:unpack” the rails/init.rb file is not run on initialization. This is a known issue that is supposed (?) to be resolved but I have still experienced this problem in my experiments. Hopefully this issue will be fully resolved in edge Rails soon and the glorious future of GemPlugins can begin.

Share:

6 Responses to “GemPlugins: A Brief Introduction to the Future of Rails Plugins”

  1. Ben

    FYI, there are at least two other problems with gems-as-plugins. First, there's no provision for something like install.rb - which means that plugins that do things (copy files, make directories, etc.) on install may need to be tweaked before gemification. Second, rake tasks defined in gems are not by default available to your Rails app - so a gem version of Ultrasphinx wouldn't work (no rake access to the indexer or daemon). I've submitted a patch for the latter issue (http://rails.lighthouseapp.com/projects/8994/tickets/59) - feel free to +1 it!
  2. Jacek Becela

    require File.dirname(__FILE__) + "/rails/init" - you don't have to keep both inits, that in rails/init.rb is enough, about 2 weeks ago a pach, which boots classic plugins from rails/init.rb, was merged. This is the commit: http://github.com/rails/rails/commit/86a042ddd9dba8f62e7328c7258a798aef73d57f
  3. Michael Bleigh

    @Ben: You're right, I hadn't considered the implications of plugins with rake tasks or install.rb issues. I'm sure some conventions will be emerging (and some patches) to deal with these issues over the course of the coming weeks. @Jacek: Thanks, I actually did know about the patch but since many people work of of gem versions instead of Edge, I thought it would be wise to keep compatibility before the patch until it's in the latest released version.
  4. Brian Burridge

    Interesting that Rails is moving in this direction. I have been trying to move in the complete opposite. There's nothing more frustrating than deploying your application and trying to start it only to be reminded upon failure that you forgot to install the necessary gems. I come from the J2EE world where deployment is a nightmare, even though we were told years ago it would be as easy as dropping an EAR or a WAR on a server. Didn't end up that way, and I'm so happy that with Rails its as easy as cap deploy. But, if you have to also install gems, its now more complicated. So for now, I continue to choose those packages that come as a plugin and try to pass on those that come as a gem.
  5. Michael Bleigh

    @Brian: You definitely have a point, but the great thing is that (once some of the kinks are worked out) "rake gems:freeze" should take care of the "vendor everything" you desire. Of course, since GemPlugins can still be installed as traditional plugins (as per the methods described above), it's not much of an issue anyway! I'm not sure that GemPlugins are ready to be the mainline of Rails plugins just yet, but I do believe that's where we're headed and just wanted to address something that I think is new and interesting.
  6. Luca Guidi

    I created a gem which mantains a local repository with your favourite plugins. It allows offline installation in your Rails apps, and the automatic update of your plugins repo. If you wish, check more infos about the usage at: http://lucaguidi.com/pages/sashimi


Leave a Reply