Skip to content

Instantly share code, notes, and snippets.

@Startouf
Created March 21, 2018 20:31
Show Gist options
  • Save Startouf/0f659959c1890fb2a4aa0ecb2c094ca3 to your computer and use it in GitHub Desktop.
Save Startouf/0f659959c1890fb2a4aa0ecb2c094ca3 to your computer and use it in GitHub Desktop.
Mongoid Database cacheable
# Concern to include in mongoid models that need database cache
#
#
module MongoidCacheable
extend ActiveSupport::Concern
class_methods do
# Create database fields to cache an attribute
# => Cache field, (Optional Cached_at DateTime field)
#
# == Params
# +attr_name+ - Name of field to be cached (eg points_for_xxx)
# +type+ - MongoDB Type for the cache field
# +recache_procedure+ - block to be executed to recompute the value.
# => Instance `self` sent as block param
#
# == Define methods
# +[attr_name]_cache+ - The last cached value for the guy
# +[attr_name]_recache+ - Recache the latest value (using $set)
# +[attr_name]_clear_cache+ - To wipe the cache clean
#
# == Options
# +:expires_in:+ - Expiration date for the cache.
# => Will add an extra database field _cached_at to store cache time
#
# == Examples
#
# cache_attribute(:points_for_contest, type: Integer, expires_in: 5.hours) do |i|
# MyPointService.new(i).total_value
# end
#
# my_model.points_for_contest_cache # => cached_value or recomputed value if expired
#
def cache_attribute(attr_name, type:, expires_in: nil, &recache_procedure)
cached_at_field_name = :"#{attr_name}_cached_at"
cache_field_name = :"#{attr_name}_cache"
field cache_field_name, type: type
field(cached_at_field_name, type: DateTime, default: nil)
# @Override the _cache method to read the attribute or recompute it
define_method(:"#{attr_name}_cache") do
# Avoid updates if app is in readonly mode
# return super() if Rails.configuration.db_readonly
if mongoid_cache_expired?(cached_at_field_name, expires_in: expires_in)
send(:"#{attr_name}_recache")
else
super() # Need to write () explicitely
end
end
# @Override
# Patch attr= method to update cached_at field at the same time
define_method(:"#{attr_name}_cache=") do |new_val|
send("#{cached_at_field_name}=", DateTime.now)
super(new_val) # Need to write () explicitely
end
# Recache method will call &recache_procedure
# ...and update the cached_at timestamp
# Performs a $set on the database and returns the cached value
define_method(:"#{attr_name}_recache") do
value = recache_procedure.call(self)
set(
cache_field_name => value,
cached_at_field_name => DateTime.now
)
value
end
# Clear the cached value and cached_at timestamp
define_method(:"#{attr_name}_clear_cache") do
set(
cache_field_name => nil,
cached_at_field_name => nil
)
end
end
end
included do
# Returns true if the mongoid cache has expired
# +cached_at_name+ - Mongoid field _cached_at
# +:expires_in:+ - Expiration time for the cache
def mongoid_cache_expired?(cached_at_name, expires_in: nil)
cached_at = send(cached_at_name)
return true if cached_at.blank? # Always cache at least once
return false if expires_in.blank? # Never auto-recache if expiration is nil
DateTime.now > (cached_at + expires_in)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment