PLEASE NOTE
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.
Publicious
It’s a plugin for plugins. It allows plugin engine[http://guides.rubyonrails.org/2_3_release_notes.html] developers to supply image, javascript and stylesheet assets in a public directory inside their plugin that “just work” without having to copy those assets over to the app’s main public directory with rake tasks or installation scripts, which is stupid.
In short, it responds to “missing” asset URLs (like /stylesheets/foo.css, /images/foo.png, /javascripts/foo.js) when the web server can’t find the file in the public directory, and tries to find a matching file in your plugins and pass that back to the browser through Rails.
Example
Let’s assume you have the following plugins installed:
- RAILS_ROOT/vendor/plugins/plugin_123
- RAILS_ROOT/vendor/gems/plugin_456
Rails would establish the following view paths:
- RAILS_ROOT/app/views
- RAILS_ROOT/vendor/plugins/plugin_123/app/views
- RAILS_ROOT/vendor/gems/plugin_456/app/views
PluginAssets takes these “view paths” and builds the same thing for “public paths” (public directories inside plugins), like this:
- RAILS_ROOT/vendor/plugins/plugin_123/public
- RAILS_ROOT/vendor/gems/plugin_456/public
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:
- RAILS_ROOT/vendor/plugins/plugin_123/public/stylesheets/foo/bah.css
- RAILS_ROOT/vendor/plugins/plugin_456/public/stylesheets/foo/bah.css
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:
/images/*
/stylesheets/*
/javascripts/*
One day I’ll make it configurable for other assets, but in the meantime it’s easy to hack/fork.
Installation
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
Caveats
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!
Etc
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.