Skip to content

Commit 1e59ad2

Browse files
committed
[Untested] Add expire_cache_for_{insert,update,delete} methods
1 parent c5038e5 commit 1e59ad2

File tree

4 files changed

+86
-4
lines changed

4 files changed

+86
-4
lines changed

lib/identity_cache/cached/attribute.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ def expire_for_save(record)
4747
end
4848
end
4949

50+
def expire_for_values(values_hash)
51+
key_values = key_fields.map { |name| values_hash.fetch(name) }
52+
IdentityCache.cache.delete(cache_key_from_key_values(key_values))
53+
end
54+
55+
def expire_for_update(old_values_hash, changes)
56+
expire_for_values(old_values_hash, changes)
57+
58+
if key_fields.any? { |name| changes.key?(name) }
59+
key_values = key_fields.map { |name| changes.fetch(name) { old_values_hash.fetch(name) } }
60+
IdentityCache.cache.delete(cache_key_from_key_values(key_values))
61+
end
62+
end
63+
5064
def cache_key(index_key)
5165
values_hash = IdentityCache.memcache_hash(unhashed_values_cache_key_string(index_key))
5266
"#{model.rails_cache_key_namespace}#{cache_key_prefix}#{values_hash}"

lib/identity_cache/configuration_dsl.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def cache_attribute_by_alias(attribute_or_proc, alias_name:, by:, unique:)
129129
cached_attribute = klass.new(self, attribute_or_proc, alias_name, fields, unique)
130130
cached_attribute.build
131131
cache_indexes.push(cached_attribute)
132+
@cache_indexed_columns = nil
132133
end
133134

134135
def ensure_base_model

lib/identity_cache/parent_model_expiration.rb

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22
module IdentityCache
3-
module ParentModelExpiration # :nodoc:
3+
# @api private
4+
module ParentModelExpiration
45
extend ActiveSupport::Concern
56
include ArTransactionChanges
67

@@ -35,9 +36,27 @@ def lazy_hooks
3536
end
3637
end
3738

39+
module ClassMethods
40+
def parent_expiration_entries
41+
ParentModelExpiration.install_pending_parent_expiry_hooks(cached_model)
42+
_parent_expiration_entries
43+
end
44+
45+
def check_for_unsupported_parent_expiration_entries
46+
return unless parent_expiration_entries.any?
47+
msg = "Unsupported manual expiration of record embedded in parent associations:\n"
48+
parent_expiration_entries.each do |association_name, cached_associations|
49+
cached_associations.each do |parent_class, _only_on_foreign_key_change|
50+
msg << "- #{parent_class}\##{association_name}"
51+
end
52+
end
53+
raise msg
54+
end
55+
end
56+
3857
included do
39-
class_attribute(:parent_expiration_entries)
40-
self.parent_expiration_entries = Hash.new { |hash, key| hash[key] = [] }
58+
class_attribute(:_parent_expiration_entries)
59+
self._parent_expiration_entries = Hash.new { |hash, key| hash[key] = [] }
4160
end
4261

4362
def expire_parent_caches
@@ -49,7 +68,6 @@ def expire_parent_caches
4968
end
5069

5170
def add_parents_to_cache_expiry_set(parents_to_expire)
52-
ParentModelExpiration.install_pending_parent_expiry_hooks(cached_model)
5371
self.class.parent_expiration_entries.each do |association_name, cached_associations|
5472
parents_to_expire_on_changes(parents_to_expire, association_name, cached_associations)
5573
end

lib/identity_cache/query_api.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,55 @@ def prefetch_associations(includes, records)
99
Cached::Prefetcher.prefetch(self, includes, records)
1010
end
1111

12+
# Get only the columns whose values are needed to manually expire caches
13+
# after updating or deleting rows without triggering after_commit callbacks.
14+
#
15+
# 1. Pass the returned columns into Active Record's `select` or `pluck` query
16+
# method on the scope that will be used to modify the database in order to
17+
# query original for these rows that will be modified.
18+
# 2. Update or delete the rows
19+
# 3. Use {expire_cache_for_update} or {expire_cache_for_delete} to expires the
20+
# caches, passing in the values from the query in step 1 as the indexed_values.
21+
#
22+
# @return [Array<Symbol>] the array of column names
23+
def cache_indexed_columns
24+
@cache_indexed_columns ||= begin
25+
check_for_unsupported_parent_expiration_entries
26+
columns = Set.new
27+
columns << primary_key.to_sym if primary_cache_index_enabled
28+
cache_indexes.each do |cached_attribute|
29+
columns.merge(cached_attribute.key_fields)
30+
end
31+
columns.to_a.freeze
32+
end
33+
end
34+
35+
def expire_cache_for_update(old_indexed_values, changes)
36+
if primary_cache_index_enabled
37+
id = old_indexed_values.fetch(primary_key.to_sym)
38+
expire_primary_key_cache_index(id)
39+
end
40+
cache_indexes.each do |cached_attribute|
41+
cached_attribute.expire_for_update(old_indexed_values, changes)
42+
end
43+
check_for_unsupported_parent_expiration_entries
44+
end
45+
46+
private def expire_cache_for_insert_or_delete(indexed_values)
47+
if primary_cache_index_enabled
48+
id = indexed_values.fetch(primary_key.to_sym)
49+
expire_primary_key_cache_index(id)
50+
end
51+
cache_indexes.each do |cached_attribute|
52+
cached_attribute.expire_for_values(indexed_values)
53+
end
54+
check_for_unsupported_parent_expiration_entries
55+
end
56+
57+
alias_method :expire_cache_for_insert, :expire_cache_for_insert_or_delete
58+
59+
alias_method :expire_cache_for_delete, :expire_cache_for_insert_or_delete
60+
1261
# @api private
1362
def cached_association(name) # :nodoc:
1463
cached_has_manys[name] || cached_has_ones[name] || cached_belongs_tos.fetch(name)

0 commit comments

Comments
 (0)