As discussed in my previous post, I worked out a way to use Page caching for the ‘public’ view of my Rails app, while rendering the same pages through Rails if the user is logged in. I implemented my idea today, so here’s a bit more technical detail.
First, I add a cookie that indicates the user is logged in:
#in login code... cookies[:logged_in] = 'yes' #in logout code... cookies.delete :logged_in
The actual user details are kept in the session as before, this is a separate flag.
Then the web server needs to check the cookie when deciding whether to use the page cache. In Apache, this means adding a condition to the rewrite rule in .htaccess, so the block looks like this:
RewriteRule ^$ index.html [QSA] RewriteCond %{HTTP_COOKIE} !^.*logged_in=yes.*$ RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
(I’ve added the line with ‘HTTP_COOKIE’ in it – a RewriteCond applies to the RewriteRule below it.)
Update: see my followup article for some discussion of problems and solutions with HTTP caching.
For development, I implemented the same behaviour in Webrick. I copied webrick_server.rb into my app’s lib directory so I could edit it, and added the cookie check in handle_file:
def handle_file(req, res) #:nodoc: begin req = req.dup path = req.path.dup if path != '/' && (%r{(^|/)[^./]+$} =~ path) if(req.cookies.find{|c| c.name == "logged_in" && c.value == 'yes'}) return false end # Add .html if the last path piece has no . in it path << '.html' end path.gsub!('+', ' ') # Unescape + since FileHandler doesn't do so. req.instance_variable_set(:@path_info, path) # Set the modified path... @file_handler.send(:service, req, res) return true rescue HTTPStatus::PartialContent, HTTPStatus::NotModified => err res.set_error(err) return true rescue => err return false end end
These are the web servers I’m using, so I haven’t looked at how it would be implemented on any others.
I wrote my own cache setup method in my application.rb:
def self.caches_public_page(*actions) return unless perform_caching actions.each do |action| class_eval "after_filter { |c| c.cache_page if !c.loggedin? && c.action_name == '#{action}' }" end end
This is basically the same as caches_page, but with a check so it only caches if there isn’t a logged in user. Other than using this the caching is set up and expired in the usual kind of way.
Update 2: this code uses a loggedin? method which you may need to define: see my next followup article for some discussion of how to do that.

3 Comments
wow, how did you get TripAdvisor photos into your page??
This caching solution is very elegant.
I work for TripAdvisor now, and so heard about the photo widget which is in ‘soft launch’.
3 Trackbacks
[…] The Junction » conditional use of page cache in Rails (tags: rails caching ruby ror performance rubyonrails) […]
[…] The Junction » conditional use of page cache in Rails […]
[…] does not work properly when switched over to Passenger though. The problem is that my caching setup relies on a custom rewrite rule, but Passenger disables use of mod_rewrite for web applications it […]