ruby-on-railsherokujammit

Static asset caching on Heroku with Jammit by changing ActionController::Base#page_cache_directory


I'm attempting to use Jammit for packaging CSS and JS for a Rails app deployed on Heroku, which doesn't work out of the box due to Heroku's read only file system. Every example I've seen of how to do this recommends building all the packaged asset files in advance. Because of Heroku's Git-based deployment, this means you need to make a separate commit to your repository every time these files change, which is not an acceptable solution to me. Instead, I want to change the path that Jammit uses to write the cached packages to #{Rails.root}/tmp/assets (by changing ActionController::Base#page_cache_directory), which is writable on Heroku.

What I don't understand is how the cached files will be used without hitting the Rails stack every time, even using the default path for cached packages. Let me explain what I mean:

When you include a package using Jammit's helper, it looks something like this:

<%= include_javascripts :application %>

which generates this script tag:

<script src="/assets/application.js" type="text/javascript"></script>

When the browser requests this URL, what actually happens is that it gets routed to Jammit::Controller#package, which renders the contents of the package to the browser and then writes a cached copy to #{page_cache_directory}/assets/application.js. The idea is that this cached file is built on the first request, and subsequent requests should serve the cached file directly without hitting the Rails stack. I looked through the Jammit code and I don't see how this is supposed to happen. What prevents subsequent requests to /assets/application.js from simply routing to Jammit::Controller again and never using the cached file?

My guess is that there's a Rack middleware somewhere I'm not seeing that serves the file if it exists and forwards the request on to the controller if it doesn't. If that's the case, where is that code? And how would it work when changing ActionController::Base#page_cache_directory (effectively changing where Jammit writes cached packages)? Since #{Rails.root}/tmp is above the public document root, there's no URL that maps to that path.


Solution

  • Great question! I haven't set this up myself, but it's something I've been meaning to look into, so you've prompted me to do so. Here's what I would try (I'll give a shot myself soon, but you are probably going to beat me to it).

    config.action_controller.page_cache_directory = "#{Rails.root}/tmp/page_cache"
    

    Now change your config.ru to:

    require ::File.expand_path('../config/environment',  __FILE__)
    run Rack::URLMap.new(
       "/"       => Your::App.new,
       "/assets" => Rack::Directory.new("tmp/page_cache/assets"))
    

    Just make sure not to have anything in public/assets, since that won't ever be picked up.

    Notes: