Skip to content

Commit

Permalink
Add expire methods by key values
Browse files Browse the repository at this point in the history
  • Loading branch information
Hector Mendoza Jacobo committed Jun 7, 2021
1 parent b7564e7 commit d4851b8
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 1 deletion.
19 changes: 19 additions & 0 deletions lib/identity_cache/cached/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ def expire(record)
end
end

def expire_by_key_value(key_values)
key_fields.each do |key|
return unless key_values.key?(key)
end

key = cache_key_by_values(key_values)
IdentityCache.cache.delete(key)
end

def cache_key(index_key)
values_hash = IdentityCache.memcache_hash(unhashed_values_cache_key_string(index_key))
"#{model.rails_cache_key_namespace}#{cache_key_prefix}#{values_hash}"
Expand All @@ -59,6 +68,11 @@ def load_one_from_db(key)
unique ? results.first : results
end

# @abstract
def pack_values_into_hash(_values)
raise NotImplementedError
end

private

# @abstract
Expand Down Expand Up @@ -100,6 +114,11 @@ def new_cache_key(record)
cache_key_from_key_values(new_key_values)
end

def cache_key_by_values(key_values)
new_key_values = key_fields.map { |field| key_values[field] }
cache_key_from_key_values(new_key_values)
end

def old_cache_key(record)
old_key_values = key_fields.map do |field|
field_string = field.to_s
Expand Down
13 changes: 13 additions & 0 deletions lib/identity_cache/cached/attribute_by_multi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ def build
raise_if_scoped
cached_attribute.fetch(key_values)
end

model.define_singleton_method(:"expire_#{fetch_method_suffix}") do |*key_values|
raise_if_scoped
cached_attribute.expire_by_key_value(key_values)
end
end

def pack_values_into_hash(values)
key_fields.each_with_object({}).with_index do |(key_name, hash), index|
hash[key_name] = values[index]
end
end

private
Expand All @@ -31,6 +42,8 @@ def load_from_db_where_conditions(key_values)
Hash[key_fields.zip(key_values)]
end



alias_method :cache_key_from_key_values, :cache_key
end
end
Expand Down
18 changes: 17 additions & 1 deletion lib/identity_cache/cached/attribute_by_one.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# frozen_string_literal: true

module IdentityCache
module Cached
class AttributeByOne < Attribute
Expand All @@ -22,6 +21,18 @@ def build
raise_if_scoped
cached_attribute.fetch_multi(keys)
end

model.define_singleton_method(:"expire_#{fetch_method_suffix}") do |key|
raise_if_scoped
cached_attribute.expire_by_key_value(cached_attribute.pack_values_into_hash(key))
end

model.define_singleton_method(:"expire_multi_#{fetch_method_suffix}") do |keys|
raise_if_scoped
keys.each do |key|
cached_attribute.expire_by_key_value(cached_attribute.pack_values_into_hash(key))
end
end
end

def fetch_multi(keys)
Expand Down Expand Up @@ -62,6 +73,11 @@ def load_multi_from_db(keys)
def cache_encode(db_value)
db_value
end

def pack_values_into_hash(value)
{ key_field.to_sym => value }
end

alias_method :cache_decode, :cache_encode

private
Expand Down
8 changes: 8 additions & 0 deletions lib/identity_cache/query_api.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

module IdentityCache
module QueryAPI
extend ActiveSupport::Concern
Expand All @@ -19,6 +20,13 @@ def all_cached_associations # :nodoc:
cached_has_manys.merge(cached_has_ones).merge(cached_belongs_tos)
end

# Expire the cache by key values
def expire_by_key_values(key_values) # :nodoc:
cache_indexes.each do |cached_attribute|
cached_attribute.expire_by_key_value(key_values)
end
end

private

def raise_if_scoped
Expand Down
9 changes: 9 additions & 0 deletions test/attribute_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ def test_attribute_values_are_fetched_and_returned_on_cache_misses
assert(fetch.has_been_called_with?(@name_attribute_key, {}))
end

def test_attribute_expire_by_value_of_by_one_key
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
AssociatedRecord.expire_name_by_id(1)

assert_queries(1) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
end
end

def test_attribute_values_are_returned_on_cache_hits
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))

Expand Down
78 changes: 78 additions & 0 deletions test/expire_by_key_values_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require "test_helper"

class ExpireByKeyValuesTest < IdentityCache::TestCase
NAMESPACE = IdentityCache::CacheKeyGeneration::DEFAULT_NAMESPACE

def setup
super
AssociatedRecord.cache_attribute(:name)
AssociatedRecord.cache_attribute(:name, by: :item_id)
AssociatedRecord.cache_attribute(:name, by: [:id, :item_id])

@parent = Item.create!(title: "bob")
@record = @parent.associated_records.create!(name: "foo")
@name_attribute_key = "#{NAMESPACE}attr:AssociatedRecord:name:id:#{cache_hash(@record.id.to_s.inspect)}"
IdentityCache.cache.clear
end

def test_expire_by_key_values
assert_queries(3) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_item_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_id_and_item_id(1, 1))
end

assert_queries(0) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_item_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_id_and_item_id(1, 1))
end

expire_hash = {
id: 1,
item_id: 1
}
AssociatedRecord.expire_by_key_values(expire_hash)

assert_queries(3) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_item_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_id_and_item_id(1, 1))
end
end

def test_expire_by_key_values_only_expires_given_values
assert_queries(3) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_item_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_id_and_item_id(1, 1))
end

expire_hash = {
id: 1
}
AssociatedRecord.expire_by_key_values(expire_hash)

assert_queries(1) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
end

assert_queries(0) do
assert_equal("foo", AssociatedRecord.fetch_name_by_item_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_id_and_item_id(1, 1))
end

expire_hash = {
item_id: 1
}
AssociatedRecord.expire_by_key_values(expire_hash)

assert_queries(1) do
assert_equal("foo", AssociatedRecord.fetch_name_by_item_id(1))
end
assert_queries(0) do
assert_equal("foo", AssociatedRecord.fetch_name_by_id(1))
assert_equal("foo", AssociatedRecord.fetch_name_by_id_and_item_id(1, 1))
end
end
end
20 changes: 20 additions & 0 deletions test/fetch_multi_by_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,24 @@ def test_fetch_multi_attribute_by_unique_cache_key_with_unknown_key

assert_equal({ 1 => "bob", 999 => nil }, Item.fetch_multi_title_by_id([1, 999]))
end

def test_expire_multi_attribute_by_key
Item.cache_attribute(:title, by: :id)

@bob.save!
@bertha.save!

# We hydrate the cache
Item.fetch_multi_title_by_id([1,2])
# Expire the cache
Item.expire_multi_title_by_id([1,2])

assert_queries(1) do
result = {
1 => "bob",
2 => "bertha"
}
assert_equal(result, Item.fetch_multi_title_by_id([1,2]))
end
end
end

0 comments on commit d4851b8

Please sign in to comment.