Skip to content

Instantly share code, notes, and snippets.

@mlangenberg
Last active August 29, 2015 13:58
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mlangenberg/10382475 to your computer and use it in GitHub Desktop.
Save mlangenberg/10382475 to your computer and use it in GitHub Desktop.
One approach to cache ActiveModelSerializer representations with ActiveRecord objects
class User < ActiveRecord::Base; end;
class Post < ActiveRecord::Base; end;
class Comment < ActiveRecord::Base; end;
class AuthorSerializer < ActiveModel::Serializer
attributes :id, :name :created_at, :updated_at
end
class PostSerializer < ActiveModel::Serializer
attributes :id, :body :created_at, :updated_at
has_one :author
has_many :comments
end
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body :created_at, :updated_at
has_one :author
end
class PostsController
# returns timeline for user
def index
post_ids = user.posts.order('created_at').select('id, updated_at') # minimal data for cache key, but correct ordering.
render json: DeferredModelLoader.new(post_ids).as_json
end
end
class DeferredModelLoader
def initialize(model_ids)
@model_ids = model_ids
end
# Given a list of record<id,updated_at>, try to fetch cached representations for all at once.
# e.g. Redis MGET
# Execute a single database query for all cache MISS, serialize, cache store.
# Return serialized records in the correct order.
def as_json
cached_models = fetch_cached_models
db_models = load_from_db_and_cache @model_ids.select { |model| !cached_model.has_key?(cache_key(model)) }
@model_ids.map { |model| cached_models.merge(db_models)[cache_key(model)] } # Return in original order.
end
# Use cache.read_multi and return a hash with key = cache_key and value = serialized attributes for model
def fetch_cached_models
Hash[*Rails.cache.read_multi(*cache_keys(@model_ids)).map { |k,v| [k,v.value] }.flatten]
end
# Use database to find load all missing records for the given ids. Eager load all data used for serialization.
# Serialize ActiveRecord objects and store in Rails cache.
# Returns a hash key = cache_key and value = serialized attributes for model
def load_from_db_and_cache(model_ids)
return {} if model_ids.empty?
models = model_ids.first.class.where(id: model_ids).with_eager_loaded_associations.map do |model|
model_as_json = model.active_model_serializer.new(model).attributes.as_json # Removes reference to serialized object
Rails.cache.write(cache_key(model), model_as_json)
[ cache_key(model), model_as_json ]
end
Hash[*models.flatten]
end
def cache_keys(model_ids)
@model_ids.map { |t| cache_key(t) }
end
def cache_key(model_id)
ActiveSupport::Cache.expand_cache_key([model_id, 'attributes'])
end
end
@maxguzenski
Copy link

well done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment