Rails 3 caching: How do I use a sweeper with Action and Fragment caching to expire the cache?

3.1k Views Asked by At

I'm working on a page that displays a restaurant menu. I have 2 models: FoodMenu has_many :products and Product belongs_to :food_menu. I don't have controllers for either model. Instead, I am using a "pages_controller.rb" to display each FoodMenu and its Products with a "menus" action:

def menus
 @food_menus = FoodMenu.includes(:products).all
end

I want to use Action Caching for the menus page (localhost:3000/menus), which is working, but I can't get the cache to expire when I update, create, or destroy a product.

At the top of "pages_controller.rb", I have:

caches_action :menus
cache_sweeper :pages_sweeper

I tried creating separate sweepers for the Product and FoodMenu models in app/sweepers using the example code here: http://guides.rubyonrails.org/caching_with_rails.html#sweepers, but that didn't work. Then, I read in a SO entry that the sweeper is supposed to observe all the models that the controller uses, so I assumed that meant I have to create a "pages_sweeper.rb" that observes both the Product and FoodMenu models and expires the "menus" action. That didn't work either. What am I doing wrong? Here is what I have right now in "pages_sweeper.rb":

class PagesSweeper < ActionController::Caching::Sweeper
 observe Product, FoodMenu 

 # If our sweeper detects that a Product was created call this
 def after_create(product)
  expire_cache_for(product)
 end

 # If our sweeper detects that a Product was updated call this
 def after_update(product)
  expire_cache_for(product)
 end

 # If our sweeper detects that a Product was deleted call this
 def after_destroy(product)
   expire_cache_for(product)
 end

 def after_create(food_menu)
  expire_cache_for(food_menu)
 end

 # If our sweeper detects that a FoodMenu was updated call this
 def after_update(food_menu)
   expire_cache_for(food_menu)
 end

 # If our sweeper detects that a FoodMenu was deleted call this
 def after_destroy(food_menu)
   expire_cache_for(food_menu)
 end


 private
 def expire_cache_for(product)
 # Expire the menus action now that we added a new product
 expire_action(:controller => 'pages', :action => 'menus')

 # Expire a fragment
 expire_fragment('all_available_products')
 end

 def expire_cache_for(food_menu)
 # Expire the menus page now that we added a new FoodMenu
 expire_action(:controller => 'pages', :action => 'menus')

 # Expire a fragment
 expire_fragment('all_available_food_menus')
 end
end     
1

There are 1 best solutions below

1
On

I finally figured it out! I was able to get both fragment and action caching to work. From the server logs, it seems that Action Caching is a lot faster, so that's what I'm deploying with.

For Fragment Caching, I created a "food_menu_sweeper.rb" that observes both FoodMenu and Product and expires the fragment that I created in the partial, like so:

class FoodMenuSweeper < ActionController::Caching::Sweeper
 observe FoodMenu, Product

 def after_save(food_menu)
   expire_cache(food_menu)
 end

 def after_destroy(food_menu)
   expire_cache(food_menu)
 end

 def after_save(product)
   expire_cache(product)
 end

 def after_destroy(product)
   expire_cache(product)
 end

 private

 def expire_cache(food_menu)
  expire_fragment("menu items")
 end

 def expire_cache(product)
  expire_fragment("menu items")
 end

end

Here is the fragment in the partial:

<% cache("menu items") do %>
  <% for food_menu in FoodMenu.full_list %> 
    ...
<% end %>
<% end %>

FoodMenu.full_list is called within the fragment to cache the database query as well, which is defined in the food_menu.rb model:

def self.full_list
  FoodMenu.includes(:products).all
end

Then, the key here is to place the cache_sweeper in "application_controller.rb"! This crucial hint came from reading this SO entry: Rails - fragment cache not expiring

 cache_sweeper :food_menu_sweeper

This all works like a charm. When I refresh the page, the fragment is read from cache and no database calls are made. Once I update a product or a food_menu, the cache is cleared and the new data appears and is then cached.

For Action Caching, I added:

caches_action :menus

in my "pages_controller.rb", then removed the fragment cache from the partial, and replaced

expire_fragment("menu items")

in food_menu_sweeper.rb, with:

expire_action(:controller => '/pages', :action => 'menus')

The key here was the leading slash before "pages", which I found via this SO entry: rails caching: expire_action in another namespace

Without the leading slash, I was getting a "Can't convert symbol into integer" error when updating items via the ActiveAdmin interface.

Yay for determination, Google, and Stack Overflow!