0.0
No commit activity in last 3 years
No release in over 3 years
rails: manage cache deletion and cache_keys in ActiveRecord
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 0
 Project Readme

manage_cache

Define cache_keys to be deleted before save or destroy in the model.

This is especially usefull for all cache stores other than memcached, but can help that cached content is not thrown away just because of a full memory when using memcached as well.

Another advantage is that it is not necessary to touch relations. Just define the cache_key right.

NOTE: There was a bug on cache deletion! Use version 0.0.6!

Usage

Add the gem to your Gemfile:

gem 'manage_cache', '~> 0.0.6'

In your ActiveRecord model, write e.g.:

class SomeClass < ActiveRecord::Base
  manage_cache_for some_key: { instance_eval: { some_class: :id, another_method_key: :another_method } }
end

Values used in the specified cache_key (here: some_key) are:

  • static: a hash with free static values to be included to the cache_key
  • instance_eval: a hash with keys being free and values being instance_evaled
  • class_eval: a hash with keys being free and values being class_evaled
  • regexp: a hash where keys should match keys of opts given to cache_key_for and values being the regexp-part to match against (see examples below).
  • if_changed: an Array specifying the attributes which have to be changed to delete this cache. Note: updated_at does not count here. Just don`t specify :if_changed if you want the cache to be deleted on updated_at/ on every save.

In the view you can than:

<% cache @some_class.cache_key_for(:some_key), skip_digest: true do %>
  Some Content to be cached.
<% end %>

NOTES:

  • You have to use skip_digest: true and none of the other options to the cache helper. With low-level caching (e.g. Rails.cache.fetch(@some_class.cache_key_for(:some_key) { cached content } everything works as expected.
  • It is not necessary to include updated_at in the cache_key because cache will be deleted on update. It does not need to be invalidated via an updated cache_key. This might save some db-queries.

Examples

  • The easiest case would be a simple :show -page e.g.:

    class User < ActiveRecord::Base
      manage_cache_for user_show: { instance_eval: { user: :id }
    end
    <% cache user.cache_key_for(:user_show), skip_digest: true do %>
         ....
    <% end %>

    user.cache_key_for(:user_show) in this case would be: "spec=user_show-user=someid"

  • A cache which should be deleted either on change of some attribute in one model or on change of another attribute in another model would be written, e.g.:

    class Shoe < ActiveRecord::Base
      belongs_to :user
      manage_cache_for users_shoes: { instance_eval: { user: "user.id" }, if_changed: [:color] }
    end
    
    class User < ActiveRecord::Base
      has_many :shoes
      manage_cache_for users_shoes: { instance_eval: { user: :id }, if_changed: [:name] }
    end
    <% cache @user.cache_key_for(:users_shoes), skip_digest: true do %>
      <%= @user.name %>
      <% @user.shoes.each do |shoe| %>
        <%= "#{shoe.name}: #{shoe.color}" %>
      <% end %>
    <% end %>

    So, the cache for a users` shoes would be deleted if either the color of one of his shoes or his name changes.

  • A slightly different case which can be handled with regexp on one side/model happens when you want to cache each shoe seperately with it`s user`s name, e.g.:

    class Shoe < ActiveRecord::Base
      belongs_to :user
      manage_cache_for users_shoes: { instance_eval: { user: "user.id", shoe: :id }, if_changed: [:color] }
    end
    
    class User < ActiveRecord::Base
      has_many :shoes
      manage_cache_for users_shoes: { instance_eval: { user: :id }, regexp: {shoe: "\\d+" }, if_changed: [:name] }
    end
    <% @user.shoes.each do |shoe| %>
     <% cache shoe.cache_key_for(:users_shoes), skip_digest: true do %>
      <%= "#{@user.name} has #{shoe.name}: #{shoe.color}" %>
      <% end %>
    <% end %>

    So, when the user updates his name all shoes` caches will be deleted. Whereas when a shoe changes it`s color only this shoes` cache will be deleted.

  • Regexp usage for e.g. paginated content:

    class User < ActiveRecord::Base
      manage_cache_for users_index: { regexp: { page: "\\d+" } }
    end
    

    NOTE: The quoted regexp part: "\\d+" instead of "\d+" necessary for FileStore!! Using RedisStore all the expressions will be replaced with *.

    This would delete all pages cached with the following:

    <% cache @users.first.try(:cache_key_for, :users_index, page: params[:page] || 1), skip_digest: true do %>
      ...
    <% end %>

    NOTES:

    • use @collection.first.try(:cache_key_for, :...) on collections. They might be empty!
    • use page: params[:page] || 1 because the first page is normally called without params[:page] - this content would never be deleted!
    • memcached does not implent delete_matched therefore regexp cannot be used with memcached.
    • If cache_store is redis_store patterns like \d+ will be replaced with *. Note as well that Redis has performance problems regexp-ing keys redis-store/issues/186.