Gemify Assets for Rails

The asset pipeline, introduced with Rails 3.1, makes it simple to include versioned external assets as application dependencies. Provided those assets are packaged as Ruby gems, the process is as simple as adding the gem to your Gemfile, running bundle install and, in the case of CSS and JavaScript, adding a require to the proper manifest file.

Many popular CSS frameworks and JavaScript libraries are already available as gems. Search RubyGems to see if the assets you’re interested in are already packaged this way. What if the JavaScript library you use isn’t yet available as a gem? Packaging and publishing asset gems is simple. Here’s your chance.

External assets are made available in Rails via Rails engines. When the engine is loaded into your Rails application, the engine’s asset paths are added to your application’s load paths. This makes them available for require in your manifest files. An asset gem is just an absurdly simple engine. As an example, I’ll walk you through the process I recently took for momentjs-rails. The process is wordy, but it’s really pretty simple.

Create Gem Framework

Bundler makes it simple to create the files and folders necessary for creating a gem. Run the following command to create and initialize a Git repository along with several template files for the gem.

$ bundle gem momentjs-rails

Versioning

In this case, momentjs-rails is a gem packaged version of the Moment.js library. Its version should track the version of JavaScript library it wraps, which is currently 1.3.0. Open lib/momentjs-rails/version.rb and set the version like so:

module Momentjs
  module Rails
    VERSION = "1.3.0"
  end
end

In the event that you need to push an update to your gem that does not change the version of the assets it wraps, I would investigate adding a fourth value to the version string (1.3.0.1).

Start Your Engine

Bundler created the gem as a standard Ruby module, but we want it to be a Rails engine. Edit lib/momentjs-rails.rb to subclass Rails::Engine like so:

require "momentjs-rails/version"

module Momentjs
  module Rails
    class Engine < ::Rails::Engine
    end
  end
end

Yes, the class is empty on purpose. All we’re doing here is declaring the gem as an engine. This will cause rails to add its directories to the load path when the gem is required.

Add The Assets

Download the asset files you want to include in the gem. If availble, I prefer to use the uncompressed, non-minified versions. They’ll actually be readable if you need to dig into them and you can rely on the asset pipeline to minify them in production or on precompilation. In the case of the Moment.js library, I downloaded the release version to my desktop and ran the following commands:

$ mkdir -p vendor/assets/javascripts
$ cp ~/Downloads/moment.js vendor/assets/javascripts/moment.js

I used the vendor/assets directory here because these aren’t assets I’m maintaining directly. If they were assets I was responsible for maintaining, I would put them in app/assets though the difference is purely semantic.

Complete The Gemspec

The momentjs-rails.gemspec file contains all of the details that describe the gem. Open it up and complete any pending TODOs left by Bundler. I usually set the homepage to the GitHub repository for the gem. Additionally, I removed the gem.executables and gem.test_files lines, as this gem has neither. I also prefer to change the gem.files setting to avoid shelling out to git. I used the following pure-ruby implementation instead:

gem.files = Dir["{lib,vendor}/**/*"] + ["MIT-LICENSE", "README.md"]

Railties needs to be declared as a dependency of the gem. Our gem needs Rails 3.1 or greater, but we’ll stop short of 4.0 for now. Use the following setting:

gem.add_dependency "railties", "~> 3.1"

Build and Test

Bundler set up some default rake tasks for us that save a few characters when building. Simply run rake build to build the gem.

I haven’t included any specs with this gem. Full-featured engines usually have a dummy Rails application in the test directory that loads the engine and can be used to run tests. While this would work to test the asset gem, I haven’t set this up yet. Instead, I generally do the following:

$ rails new momentjs-test
$ cd momentjs-test
$ echo 'gem "momentjs-rails", :path => "/Absolute/Path/To/Gem/Directory"' >> Gemfile
$ bundle install
$ echo '//= require moment' >> app/assets/javascripts/application.js
$ rails server &
$ curl http://localhost:3000/assets/moment.js
$ fg
<ctrl-c>

That’s quite the little dance, but the curl command should return the contents of the moment.js file if everything is wired up correctly.

README

I included a very simple readme file with the Gem as its sole documentation. I brifely described Moment.js, supplied simple usage instructions, and a word about versioning. See the momentjs-rails readme.

Push To GitHub and RubyGems

Create a GitHub repository for your project, stage all of your commits, commit, and push the code to GitHub.

If you’ve never published a gem on RubyGems before, you’ll need to sign up for an account there. Your account settings will contain an API key that should be copied to ~/.gem/credentials. Once that’s done, publishing your gem is as simple as:

$ rake release

This will create a Git tag for this version, and push the built gem to RubyGems.