Rails Action Caching for user specific records
Asked Answered
B

2

6

I am a rails newbie trying to implement caching for my app. I installed memcached and configured it in my development.rb as follows:

config.action_controller.perform_caching             = true
config.cache_store = :mem_cache_store

I have a controller ProductsController that shows user specific products when they are logged in.

class ProductsController < ApplicationController
  caches_action :index, :layout => false
  before_filter :require_user

  def index
    @user.products              
  end
end

The route for index action is: /products

The problem is that when I login as

1) User A for the first time, rails hits my controller and caches the products action.

2) I logout and login as User B, it still logs me in as User A and shows products for User A and not User B. It doesn't even hit my controller.

The key probably is the route, in my memcached console I see that it's fetching based on the same key.

20 get views/localhost:3000/products
20 sending key views/localhost:3000/products

Is action caching not what I should use? How would I cache and display user specific products?

Thanks for your help.

Berchtesgaden answered 23/2, 2011 at 19:1 Comment(0)
N
15

The first problem is that your before_filter for require_user is after the action caching, so that won't run. To fix that, use this controller code:

class ProductsController < ApplicationController
  before_filter :require_user
  caches_action :index, :layout => false

  def index
    @products = @user.products              
  end
end

Second, for action caching you are doing the exact same thing as page caching, but after filters are run, so your @user.products code won't run. There are a couple of ways to fix this.

First, if you want you can cache the action based on the parameters that are passed to the page. For example, if you pass a user_id parameter, you could cache based on that parameter like this:

caches_action :index, :layout => false, :cache_path => Proc.new { |c| c.params[:user_id] }

Second, if you want to simply cache the query and not the entire page, you should remove action caching entirely and only cache the query, like this:

def index
  @products = Rails.cache.fetch("products/#{@user.id}"){ @user.products }
end

This should help you get on your way to having a separate cache for each user.

Nedi answered 23/2, 2011 at 22:7 Comment(4)
Thanks Pan. I ended up with your last suggestion of using query cache. Any idea on how to not completely invalidate the entire cache for 'products/{#user.id}' when some attribute for one product in the list gets updated...like product(10).price?Berchtesgaden
If you want, you can cache individual products rather than all of them so that you can have more control over which cache values are invalidated. This is a double edged sword though - the more fine-grained you want to make the invalidation of cache, the more complex it gets and the more individual cache statements you need to make. I would recommend simply invalidating the entire product list cache, it's probably the simplest to do this. If you start to run into performance issues from this approach, then try to dive in further and create more detailed caches. Don't over-optimize.Nedi
Pan, thanks for this answer, specifically the bit about the before_filter needing to come before the caches_action. I've been wrestling with the consequences of having them reversed for more hours than I'd like to admit :-oBridgeboard
I am having the same issue (e.g. action-caching with an authenticated account-based application). However, similar to the OP, I do not have user-specific data in the URL (e.g. /username/posts). Also, I tried the compromise of using Rails.cache to no avail. Is there any other way to cache user-specific actions that don't require putting a user-id into the URL?Alphorn
T
0

Building on Pan Thomakos's answer, if you're inheriting from a controller which handles authentication you need to copy the before_filter from the parent class. For example:

class ApplicationController < ActionController::Base
  before_filter :authenticate
end

class ProductsController < ApplicationController
  # must be here despite inheriting from ApplicationController
  before_filter :authenticate
  caches_action :index, :layout => false
end
Thermonuclear answered 11/4, 2011 at 1:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.