views:

714

answers:

4

So Rails time stamping is great. I'm using it to add expires headers to all files that end in the 10 digit timestamp. Most of my images however are referenced in my CSS. Has anyone come across any method that allows for timestamps to be added to CSS referenced images, or some funky re-write rule that achieves this? I'd love for ALL images in my site, both inline and in css to have this timestamp so I can tell the browser to cache them, but refresh any time the file itself changes.

I couldn't find anything on the net regarding this and I can't believe this isn't a more frequently discussed topic.

I don't think my setup will matter because the actual expiring will hopefully happen the same way, based on the 10 digit timestamp, but I'm using apache to serve all static content if that matters

+4  A: 

I've been using asset packager, and I ended up editing the plugin's compress_css method to solve this problem. I basically just regex for images in the css, and insert the current timestamp:

timestamp = Time.now.to_s.gsub(/\D/, '')
source.gsub!(/url\((['"])(.+)(['"])\)/) do
  open, file, close = $1, $2, $3
  if file =~ /.\.(ico|css|js|gif|jpe?g|png)/
    "url(#{open}#{file}?#{timestamp}#{close})"
  else
    "url(#{open}#{file}#{close})"
  end
end

That way, whenever I deploy, the compressed css images contain timestamps appended. The downfall with this method is that each image doesn't get its own timestamp, so every time you deploy new css, all of the css images are 'expired'. Better than nothing unless you deploy css often.

Ben
Interesting, though I definitely agree, it's less than ideal having the one timestamp vs each file having it's own proper timestamp, but I suppose this IS better than nothing. Thx for the tip.
brad
+3  A: 

You can append the actual timestamp of each image file by getting the file modification time like this:

    source.gsub!(/url\((['"]*)(.+)(['"]*)\)/) do
      open, file, close = $1, $2, $3
      css_dir = File.join(RAILS_ROOT,"public/stylesheets")
      timestamp = ''
      FileUtils.cd(css_dir) do 
        if file =~ /^\// # absolute path
          f = File.new(RAILS_ROOT + "/public" + file)
        else # relative path
          f = File.new(file)
        end
        timestamp = f.mtime.to_i.to_s
      end

      if file =~ /.\.(ico|css|js|gif|jpe?g|png)/
        "url(#{open}#{file}?#{timestamp}#{close})"
      else
        "url(#{open}#{file}#{close})"
      end
    end

(There's probably a more elegant way to write this, my ruby chops are still weak!) Now the hack's getting ugly, though... you'd think there would be a more Rails-like way to do this.

Les Nightingill
Your regular expression didn't really worked for me when quotes were around. Using `/url\((['"]*)([^\n'"]+)(['"]*)\)/` works
Nicolas Viennot
This works awesome! Thank you!
CalebHC
+3  A: 

The best solution seems to be to use ERB to generate your stylesheets so you can use the Rails image_ helpers instead of direct image paths. Meaning, get rid of your public/stylesheets/application.css file and create app/views/stylesheets/application.css.erb. You'll also have to create a controller and enable caching, and setup the route.

Here is the full details: http://deaddeadgood.com/2009/9/28/far-future-expires-headers-for-css-images-in-rails

lamplighter
perfect! Exactly what I'm looking for. On a side note, I also just started reading about Jammit http://documentcloud.github.com/jammit/. Looks like a pretty good solution for asset packaging in rails as well!
brad
+2  A: 

In case anyone stumbles upon this, Jammit now supports this out of the box. I've been using jammit on a new project and I'm incredibly impressed!

brad