Rails 3.1 introduces an Asset Pipeline that solves exactly this problem in a much better way. I won’t be working on this library any further, and it will eventually be removed from GitHub.
Let’s assume you have the following plugins installed:
Rails would establish the following view paths:
PluginAssets takes these “view paths” and builds the same thing for “public paths” (public directories inside plugins), like this:
Given a request for “/stylesheets/foo/bah.css”, the web server would first look for “bah.css” in “RAILS_ROOT/public/foo/bah.css”. If that resource doesn’t exist, the request would usually be passed through to Rails, which will try to route the request to a controller action and eventually respond with a 404 (file not found).
Instead, this plugin intercepts the requests for these missing assets with a piece of middleware, searching through the installed plugins. In this case, it would search for “bah.css” in the following locations:
As soon as a matching file is found, it’s passed back to the browser with the correct mime type. If none of the plugins have such a file, the request is passed onto Rails, which will handle the 404 like it usually does.
It works for plugins and gem plugins. It works for the following URLs:
One day I’ll make it configurable for other assets, but in the meantime it’s easy to hack/fork.
The gem is hosted on gemcutter, so if you haven’t already, add it as a gem source:
sudo gem sources -a http://gemcutter.org/
Then install the Publicious gem:
sudo gem install publicious
Publicious leverages the view_paths array used by Rails plugins that supply their own views. Rails plugins and gems are only added to the view_paths array automatically if there’s an app/views directory inside the plugin. If you’re supplying public assets, chances are pretty good you’re supplying views too, so that probably isn’t a big deal.
Isn’t it inefficient?
Yes, it’s probably inefficient to go looking through the filesystem and pass the asset back through a Rails request over and over, but this problem should be solved with caching and appropriate response headers, not by copying files around through rake tasks and installer scripts.
There’s Still Plenty To Do!
I wrote this on the train this morning as a proof of concept, so there’s a long list:
- It should be a gem plugin, so that other gem plugins can list it as a dependency
- I haven’t tackled caching yet (probably need to do both page caching and header response caching)
I actually want to see this in Rails core, so help me make it awesome!
Publicious is Copyright © 2009 Justin French, released under the MIT license. Your feedback, forkings and contributions are greatly welcomed. Many thanks to Daniel Neighman (hassox) for his help converting Publicious to Rack middleware rather than a regular Rails routing and controller stack.