Module: Juicer::CacheBuster

Defined in:
lib/juicer/cache_buster.rb

Overview

Assists in creating filenames that reflect the last change to the file. These kinds of filenames are useful when serving static content through a web server. If the filename changes everytime the file is modified, you can safely configure the web server to cache files indefinately, and know that the updated filename will cause the file to be downloaded again - only once - when it has changed.

Types of cache busters

Query string / “soft” cache busters

Soft cache busters require no web server configuration. However, it is not guaranteed to work in all settings. For example, older default configurations for popular proxy server Squid does not consider a known URL with a new query string a new URL, and thus will not download the file over.

The soft cache busters transforms /images/logo.png to /images/logo.png?cb=1232923789

Filename change / “hard” cache busters

Hard cache busters change the file name itself, and thus requires either the web server to (internally) rewrite requests for these files to the original ones, or the file names to actually change. Hard cache busters transforms /images/logo.png to /images/logo-1232923789.png

Hard cache busters are guaranteed to work, and is the recommended variant. An example configuration for the Apache web server that does not require you to actually change the filenames can be seen below.

<VirtualHost *>
    # Application/website configuration

    # Cache static resources for a year
    <FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
        ExpiresActive On
        ExpiresDefault "access plus 1 year"
    </FilesMatch>

    # Rewrite URLs like /images/logo-cb1234567890.png to /images/logo.png
    RewriteEngine On
    RewriteRule (.*)-cb\d+\.(.*)$ $1.$2 [L]
</VirtualHost>])

Consecutive calls

Consecutive calls to add a cache buster to a path will replace the existing cache buster *as long as the parameter name is the same*. Consider this:

file = Juicer::CacheBuster.hard("/home/file.png") #=> "/home/file-cb1234567890.png"
Juicer::CacheBuster.hard(file)                    #=> "/home/file-cb1234567891.png"

# Changing the parameter name breaks this
Juicer::CacheBuster.hard(file, :juicer)           #=> "/home/file-cb1234567891-juicer1234567892.png"

Avoid this type of trouble simply be cleaning the URL with the old name first:

Juicer::CacheBuster.clean(file)                   #=> "/home/file.png"
file = Juicer::CacheBuster.hard(file, :juicer)    #=> "/home/file-juicer1234567892.png"
Juicer::CacheBuster.clean(file, :juicer)          #=> "/home/file.png"
Author

Christian Johansen ([email protected])

Copyright

Copyright © 2009 Christian Johansen

License

BSD

Defined Under Namespace

Modules: Revision

Constant Summary collapse

DEFAULT_PARAMETER =
"jcb"

Class Method Summary collapse

Class Method Details

.clean(file, parameter = DEFAULT_PARAMETER) ⇒ Object

Remove cache buster from a URL for a given parameter name. Parameter name is “cb” by default.



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/juicer/cache_buster.rb', line 146

def self.clean(file, parameter = DEFAULT_PARAMETER)
  if "#{parameter}".length == 0
    return file.sub(/\?\d+$/, '')
  else
    query_param = "#{parameter}="
    new_file = file.sub(/#{query_param}\d+&?/, "").sub(/(\?|&)$/, "")
    return new_file unless new_file == file

    file.sub(/-#{parameter}\d+(\.\w+)($|\?)/, '\1\2')
  end
end

.hard(file, opts = {}) ⇒ Object

Add a hard cache buster to a filename. The parameter is an optional prefix that is added before the mtime timestamp. It results in filenames of the form: file-[parameter name][timestamp].suffix, ie images/logo-cb1234567890.png which is the case for the default parameter name “cb” (as in *c*ache *b*uster).



118
119
120
# File 'lib/juicer/cache_buster.rb', line 118

def self.hard(file, opts = {})
  self.path(file, opts.merge(:type => :hard))
end

.path(file, opts = {}) ⇒ Object

Creates a unique file name for every revision to the files contents. Raises an ArgumentError if the file can not be found.

The type indicates which type of cache buster you want, :soft or :hard. Default is :soft. If an unsupported value is specified, :soft will be used.

See #hard and #soft for explanation of the parameter argument.

Raises:

  • (ArgumentError)


81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/juicer/cache_buster.rb', line 81

def self.path(file, opts = {})
  return file if file =~ /data:.*;base64/

  type = opts[:type]
  type = [:soft, :hard, :rails].include?(type) ? type : :soft

  parameter = opts.key?(:parameter) ? opts[:parameter] : DEFAULT_PARAMETER
  parameter = nil if type == :rails

  file = self.clean(file, parameter)
  filename = file.split("?").first

  revision_type = opts[:revision_type]
  revision_type = :mtime if type == :rails
  revision_type = [:git, :mtime].include?(revision_type) ? revision_type : :mtime

  raise ArgumentError.new("#{file} could not be found") unless File.exists?(filename)

  revision = Revision.send(revision_type, filename)

  if type == :soft
    parameter = "#{parameter}=".sub(/^=$/, '')
    return "#{file}#{file.index('?') ? '&' : '?'}#{parameter}#{revision}"
  elsif type == :rails
    return "#{file}#{file.index('?') ? '' : "?#{revision}"}"
  end

  file.sub(/(\.[^\.]+$)/, "-#{parameter}#{revision}" + '\1')
end

.rails(file, opts = {}) ⇒ Object

Add a Rails-style cache buster to a filename. Results in filenames of the form: file.suffix?[timestamp], ie images/logo.png?1234567890 which is the format used by Rails’ image_tag helper.



138
139
140
# File 'lib/juicer/cache_buster.rb', line 138

def self.rails(file, opts = {})
  self.path(file, opts.merge(:type => :rails))
end

.soft(file, opts = {}) ⇒ Object

Add a soft cache buster to a filename. The parameter is an optional name for the mtime timestamp value. It results in filenames of the form: file.suffix?[parameter name]=[timestamp], ie images/logo.png?cb=1234567890 which is the case for the default parameter name “cb” (as in *c*ache *b*uster).



129
130
131
# File 'lib/juicer/cache_buster.rb', line 129

def self.soft(file, opts = {})
  self.path(file, opts.merge(:type => :soft))
end