PullMonkey Blog

11 Oct

Cached Assets


I love the CachedAssets plugin for rails. This plugin bundles all your css files into one and all your js files into one which helps make fewer http requests, ultimately making your site respond faster.

The cached assets plugin does a great job of combining the assets, but the one flaw is that the resulting file is simply the MD5 Sum of the file names of the assets to be joined together. No matter the contents or the time-stamp of the asset files, the resulting name never changes. Browsers tend to cache these files, which is good, but not when you made some recent changes. I made a few adjustments to the cached assets plugin to name the resulting file based on the MD5 Sum of the asset files' last modified times. Now the name is guaranteed to be different if something has changed.

Here is a diff of what the plugin gives us and what changes I have made:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

[pullmonkey]$ diff -ru cached_assets/lib/cached_assets.rb cached_assets_pullmonkey/lib/cached_assets.rb
--- cached_assets/lib/cached_assets.rb  2007-04-08 11:52:36.000000000 -0700
+++ cached_assets_pullmonkey/lib/cached_assets.rb       2007-10-09 11:30:48.000000000 -0700
@@ -21,13 +21,13 @@

       private
         def cached_asset(sources, type)
-          cached_name = Digest::MD5.hexdigest(sources.join(',')) + (type == :js ? '.js' : '.css')
-          cached_source = compute_public_cached_path(cached_name)
-          cached_path = compute_store_path(cached_name)
-
           sources = js_defaults(sources) if type == :js && sources.include?(:defaults)

-          assets = sources.map {|s| (type == :js ? javascript_path(s) : stylesheet_path(s)).split('?') }
+          assets = sources.map {|s| (type == :js ? javascript_path2(s) : stylesheet_path2(s)).split('?') }
+
+          cached_name = Digest::MD5.hexdigest(assets.map{|a| File.stat("#{ASSET_CACHE_STORE}/#{a}").mtime}.join(',')) + (type == :js ? '.js' : '.css')
+          cached_source = compute_public_cached_path(cached_name)
+          cached_path = compute_store_path(cached_name)

           if !FileTest.exist?(cached_path) || rails_asset_id(cached_source).to_i < assets.map {|a| a.last.to_i }.max
             FileUtils.makedirs(File.dirname(cached_path))
@@ -38,6 +38,7 @@
             }
           end

+
           cached_source
         end

@@ -53,7 +54,7 @@
         end

         def asset_path(src, type)
-          "#{RAILS_ROOT}/public#{(type == :js ? javascript_path(src) : stylesheet_path(src)).split('?').first}"
+          "#{RAILS_ROOT}/public#{(type == :js ? javascript_path2(src) : stylesheet_path2(src)).split('?').first}"
         end

         def compute_public_cached_path(src)
@@ -63,6 +64,14 @@
         def compute_store_path(src)
           "#{ASSET_CACHE_STORE}#{compute_public_cached_path(src)}"
         end
+
+        def javascript_path2(src)
+          "/javascripts/#{src}.js"
+        end
+
+        def stylesheet_path2(src)
+          "/stylesheets/#{src}.css"
+        end
     end
   end
 end



I am not sure the plugin's intent, but I am sure it was designed to simply combine all the assets together, not to worry about browser caching. I am pretty sure that this plugin in combination with Asset Timestamping plugin would maybe do the trick, in the sense that the filename (resulting from the cached assets plugin) would never change and the asset timestamping plugin would append a timestamp to the end of the asset name, I.e. ?1147898838. There is a problem with this though and it is discussed in great detail in the Optimizing Page Load Time Article.
This article states that in order for the browser to ALWAYS cache the file (which we really, really want until we change the name) then we need to stay away from the use of question mark timestamping. So I believe I have presented the solution that best meets my requirements of having one css file, one js file and a way to avoid the stale browser cache when modifications have been made.



Filed under: Home, development, rails, ruby


One Response to “Cached Assets”

  1. By Eliot Sykes on May 6, 2010 | Reply

    Shameless plug for a plugin that could help people trying to achieve the same thing, hope it is of interest:

    http://blog.eliotsykes.com/2010/05/06/why-rails-asset-caching-is-broken/

Sorry, comments for this entry are closed at this time.