Skip to content

Instantly share code, notes, and snippets.

@snikch
Last active December 20, 2015 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snikch/6170292 to your computer and use it in GitHub Desktop.
Save snikch/6170292 to your computer and use it in GitHub Desktop.

Serving Non-Fingerprinted Cached Assets on Heroku

Requirements:

  • Assets are accessed unfingerprinted (e.g. /assets/application.js)
  • Rack::Cache is enabled
  • Assets are gzipped
  • Assets are precompiled
  • Assets are sent to the browser when updated in source control / the filesystem

This is not as easy as it sounds. By default, when rack cache is enabled, files requested from the filesystem via Rack::Static are cached, and therefore when the file changes, those changes are not reflected in the cache, and stale assets are served up.

My solution is to provide a special store to Rack::Cache, which checks if the cached content being check matches a pattern, and sets a key prefix. I then have an incrementing version value that is checked into source control, which will change the key path, and allow for fresh content to be served up.

PrefixedStore

This store allows any proc to be passed, and the proc will be called, with the cache key, and can return a prefix to be applied.

AssetStore

This extends PrefixStore, and supplies a proc which checks if the requested key matches /assets/

require 'prefixed_store'
# If this is an asset request, prefix with the current asset version
class AssetStore < ActiveSupport::Cache::PrefixedStore
def initialize(options)
options[:prefix] = ->(key){
if !!key.match("/assets/")
# This is a constant value that can be altered and checked
# into version control.
YourApp::VERSION
else
false
end
}
super(options)
end
end
require 'asset_store'
YourApp::Application.configure do
# Cache store config for your normal cache
cache_config = [:memcached_store] # etc. Put yours here
# Use our custom cache store for assets, which version expiry
config.cache_store = AssetStore.new store: cache_config
end
require "active_support/cache"
# A cache that allows key modification based on an arbitrary proc value
module ActiveSupport
module Cache
class PrefixedStore < Store
attr_reader :passthrough_store, :prefix_proc
def initialize(options = nil, &blk)
options ||= {}
super(options)
store = options.delete(:store)
store = store.is_a?(Symbol) ? [store, options] : store
@passthrough_store = ActiveSupport::Cache.lookup_store(*store)
@prefix_proc = options.delete(:prefix)
end
protected
def read_entry(key, options) # :nodoc:
@passthrough_store.send(:read_entry, prefixed(key), options)
end
def write_entry(key, entry, options) # :nodoc:
@passthrough_store.send(:write_entry, prefixed(key), entry, options)
end
def delete_entry(key, options) # :nodoc:
@passthrough_store.send(:delete_entry, prefixed(key), options)
end
private
def prefixed(key)
prefix = @prefix_proc.call(key)
prefix ? "#{prefix}/#{key}" : key
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment