inspired by [https://sourcediving.com/this-rails-cache-is-not-your-friend-512871c138aa]
Let ActiveRecord take record instance directly from memory (cached), instead of instantiate from result set, for reducing memory allocation times and time.
It is very simple implementation of Identity Map.
It is suitable for reading is much more frequent than updating, like hot posts (preferably non editable).
Better to use with ActiveRecord::QueryCache
# lib/memory_take.rb
module MemoryTake
extend ActiveSupport::Concern
class_methods do
attr_accessor :memory_take_store
# @todo
# - locking
# - freeze?
# - expire when updated
# - enable (block)
# @example
# User.find(1).object_id # => abc
# User.find(1).object_id # => abc (same object)
#
# User.find(1).posts
# User.find(1).posts # `.posts` without SQL ~
# User.find(1).posts.hidden # scope will make SQL
def memory_take_by(identity_key = :id, expires_in: 30.seconds)
self.memory_take_store = MemoryTake::Store.new(expires_in:)
identity_key = identity_key.to_s
define_singleton_method(:instantiate_instance_of) do |klass, attributes, column_types = { }, &block|
identifier = attributes[identity_key]
cached = klass.memory_take_store.read(identifier)
return cached if cached.present?
instantiated = super(klass, attributes, column_types, &block)
klass.memory_take_store.write(identifier, instantiated)
instantiated
end
end
end
end
# lib/memory_take/store.rb
class MemoryTake::Store < ActiveSupport::Cache::MemoryStore
module NotDupCoder
module_function
def dump(entry) = entry
def dump_compressed(entry, _threshold) = entry
def load(entry) = entry
end
private def default_coder = NotDupCoder
end
also see: