Skip to content

Instantly share code, notes, and snippets.

@AMekss
Last active September 23, 2023 07:25
Show Gist options
  • Save AMekss/ceb528f97638c45ad6c7 to your computer and use it in GitHub Desktop.
Save AMekss/ceb528f97638c45ad6c7 to your computer and use it in GitHub Desktop.
Convinient and simple way how to do instance level caching.
# Source: https://gist.github.com/ceb528f97638c45ad6c7
# Replacement for direct @var ||= solution but this one correctly memoize non-true values (nil, false, etc) and is simple..
# Much simpler than ActiveSupport::Memoize which is deprecated (in Rails 3.2) by the way
module InstanceCaching
def remember(*args)
caller_name = caller(1..1).first[/`(.*)'/, 1]
caller_args_digest = Digest::MD5.hexdigest(args.to_s)
cache = @instance_caching_memoizations_aw5zdgf ||= {}
cache_key = "#{caller_name}_#{caller_args_digest}"
cache.key?(cache_key) ? cache[cache_key] : cache[cache_key] = yield
end
def reload(*)
@instance_caching_memoizations_aw5zdgf = {}
super if defined?(super)
end
end
require "rails_helper"
RSpec.describe InstanceCaching do
describe "#remember" do
let(:test_class) do
Struct.new(:test_phrase) do
include InstanceCaching
def cache_this
remember do
test_phrase
end
end
def cache_this_with_args(*args)
remember(*args) do
"#{test_phrase}_#{args}"
end
end
def cache_bool?
remember do
!test_phrase
end
end
def cache_nil!
remember do
test_phrase
nil
end
end
end
end
let(:instance1) { test_class.new(instance1_text) }
let(:instance1_text) { "this is test" }
let(:instance2) { test_class.new(instance2_text) }
let(:instance2_text) { "this is another test" }
before do
allow(instance1).to receive(:test_phrase).once.and_call_original
allow(instance2).to receive(:test_phrase).once.and_call_original
end
it "invokes code for instance method only once per instance" do
expect(instance1.cache_this).to eq instance1_text
expect(instance1.cache_this).to eq instance1_text
expect(instance2.cache_this).to eq instance2_text
expect(instance2.cache_this).to eq instance2_text
expect(instance2.cache_this).to eq instance2_text
end
it "is not confused by the bang! method returning nil" do
expect(instance1.cache_nil!).to eq instance1.cache_nil!
end
it "is not confused by the query? method returning boolean" do
expect(instance1.cache_bool?).to eq instance1.cache_bool?
end
it "invokes code for instance method with argumnents only once per instance" do
args = ["a", "b"]
expect(instance1.cache_this_with_args(*args)).to eq instance1.cache_this_with_args(*args)
expect(instance2.cache_this_with_args(*args)).to eq instance2.cache_this_with_args(*args)
end
context "#reload" do
before do
expect(instance1).to receive(:test_phrase).twice.and_call_original
end
it "resets state" do
expect(instance1.cache_this).to eq instance1_text
expect(instance1.cache_this).to eq instance1_text
instance1.reload
expect(instance1.cache_this).to eq instance1_text
expect(instance1.cache_this).to eq instance1_text
end
end
context "when args changing" do
before do
expect(instance1).to receive(:test_phrase).twice.and_call_original
end
it "invokes code for instance method only once per params set" do
expect(instance1.cache_this_with_args("foo")).to eq instance1.cache_this_with_args("foo")
expect(instance1.cache_this_with_args("bar")).to eq instance1.cache_this_with_args("bar")
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment