lib/juicer/cache_buster.rb in juicer-0.2.6 vs lib/juicer/cache_buster.rb in juicer-1.0.0
- old
+ new
@@ -1,45 +1,130 @@
+# -*- coding: utf-8 -*-
+
module Juicer
#
- # Tool that assists in creating filenames that update everytime the file
- # contents change. There's two ways of generating filenames, soft and hard.
- # The point of all this is to facilitate configuring web servers to send
- # static assets with a far future expires header - improving end user
- # performance through caching.
+ # 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.
#
- # Soft cache busters require no web server configuration, but will not work
- # as intended with older default configurations for popular proxy server
- # Squid. The soft busters use query parameters to create unique file names,
- # and these may not force an update in some cases. The soft cache busters
- # transforms /images/logo.png to /images/logo.png?cb=1232923789
+ # = 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
+ # <tt>/images/logo.png</tt> to <tt>/images/logo.png?cb=1232923789</tt>
+ #
+ # == 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
+ # transforms <tt>/images/logo.png</tt> to <tt>/images/logo-1232923789.png</tt>
#
+ # 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 (christian@cjohansen.no)
+ # Copyright:: Copyright (c) 2009 Christian Johansen
+ # License:: BSD
+ #
module CacheBuster
+ DEFAULT_PARAMETER = "jcb"
+
#
# Creates a unique file name for every revision to the files contents.
- # Default parameter name for soft cache busters is cb (ie ?cb=<timestamp>)
- # while default parameter names for hard cache busters is none (ie
- # file-<timestamp>.png).
+ # Raises an <tt>ArgumentError</tt> if the file can not be found.
#
- def self.path(file, type = :soft, param = :undef)
- param = (type == :soft ? "jcb" : nil) if param == :undef
- f = File.new(file.split("?").first)
- mtime = f.mtime.to_i
- f.close
+ # The type indicates which type of cache buster you want, <tt>:soft</tt>
+ # or <tt>:hard</tt>. Default is <tt>:soft</tt>. If an unsupported value
+ # is specified, <tt>:soft</tt> will be used.
+ #
+ # See <tt>#hard</tt> and <tt>#soft</tt> for explanation of the parameter
+ # argument.
+ #
+ def self.path(file, type = :soft, parameter = DEFAULT_PARAMETER)
+ file = self.clean(file, parameter)
+ filename = file.split("?").first
+ raise ArgumentError.new("#{file} could not be found") unless File.exists?(filename)
+ mtime = File.mtime(filename).to_i
+ type = [:soft, :hard].include?(type) ? type : :soft
if type == :soft
- param = "#{param}".length == 0 ? "" : "#{param}="
- file = file.sub(/#{param}\d+/, "").sub(/(\?|\&)$/, "")
- "#{file}#{file.index('?') ? '&' : '?'}#{param}#{mtime}"
- else
- parts = file.split(".")
- suffix = parts.pop
- file = parts.join.sub(/-#{param}\d+/, "")
- "#{parts.join('.')}-#{param}#{mtime}.#{suffix}"
+ parameter = "#{parameter}=".sub(/^=$/, '')
+ return "#{file}#{file.index('?') ? '&' : '?'}#{parameter}#{mtime}"
end
+
+ file.sub(/(\.[^\.]+$)/, "-#{parameter}#{mtime}" + '\1')
+ end
+
+ #
+ # 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:
+ # <tt>file-[parameter name][timestamp].suffix</tt>, ie
+ # <tt>images/logo-cb1234567890.png</tt> which is the case for the default
+ # parameter name "cb" (as in *c*ache *b*uster).
+ #
+ def self.hard(file, parameter = DEFAULT_PARAMETER)
+ self.path(file, :hard, parameter)
+ end
+
+ #
+ # 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:
+ # <tt>file.suffix?[parameter name]=[timestamp]</tt>, ie
+ # <tt>images/logo.png?cb=1234567890</tt> which is the case for the default
+ # parameter name "cb" (as in *c*ache *b*uster).
+ #
+ def self.soft(file, parameter = DEFAULT_PARAMETER)
+ self.path(file, :soft, parameter)
+ end
+
+ #
+ # Remove cache buster from a URL for a given parameter name. Parameter name is
+ # "cb" by default.
+ #
+ def self.clean(file, parameter = DEFAULT_PARAMETER)
+ query_param = "#{parameter}".length == 0 ? "" : "#{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
end