A rails cache implementation that began as a fork of simple cacheable and incorporated some ideas from identity cache as well.
gem generation_cacheable
class User < ActiveRecord::Base
include GenCache
has_one :profile
has_many :friends
model_cache do
with_key # => User.find_cached(1)
with_attribute :name # => User.find_cached_by_name("Pathouse")
with_method :meaning_of_life # => user_instance.cached_meaning_of_life
with_class_method :population # => User.cached_population
with_association :profile, :friends # => user_instance.cached_profile User.cached_friends
end
def self.population
all.count
end
def meaning_of_life
42
end
end
IMPORTANT: When caching associations, GenCache must be included in all of the models involved.
As stated above, this gem began as a fork of simple cacheable. The intention was to solve marshalling errors (hence the object encoding and decoding borrowed from identity cache).
While working with simple cacheable I became very dissastisifed with how association caching and expiry was handled. Generation Cacheable was written this principle in mind:
In a fetch operation, the cache is read, the value is found, or a block is executed to find that value and then it's written to the cache. That's it. Caching should neither know nor care where that value is coming from and what its relation is to the model executing the fetch request. In a similar vein, when a model goes to expire its cache, it should do only that and not have to worry about expiring its associations.
So association caches work something like this:
user_instance.posts # => post_1, post_2, post_3
Rails.cache.read( user_instance_posts_key ) # => [post_1_key, post_2_key, post_3_key]
user_instance.cached_posts # => post_1, post_2, post_3
When this method is called, this instance's posts cache is read, but instead of containing that users posts, it contains all of the cache keys for the posts in that association which are then retrieved in a multi_read. So the trade off is - expiry is vastly simplified among complex associations, but reading associations from the cache hits memcached at least twice.
The name comes from hash "generations" (also borrowed from identity cache) that are used within cache keys. These generations enable two types of "automatic" expiry.
-
Model Generations - based on the schema, these generations naturally expire when columns are added to or removed from the database. So after a migration, everything in the cache for a given model expires.
-
Instance Generations - based on an instances attributes, these generations naturally expire only when some value has changed. These expirations only affect method and association caches.
Key caches, attributes caches, and class method caches are all manually expired as part of an after_commit
hook.