identity_cache 1.2.0 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +8 -7
- data/.gitignore +1 -1
- data/.rubocop.yml +0 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +84 -2
- data/Gemfile +4 -4
- data/Gemfile.lock +118 -0
- data/README.md +28 -5
- data/Rakefile +98 -6
- data/dev.yml +17 -17
- data/gemfiles/Gemfile.latest-release +1 -0
- data/gemfiles/Gemfile.min-supported +5 -5
- data/gemfiles/Gemfile.rails-edge +1 -0
- data/identity_cache.gemspec +3 -3
- data/lib/identity_cache/belongs_to_caching.rb +1 -1
- data/lib/identity_cache/cache_fetcher.rb +12 -3
- data/lib/identity_cache/cache_key_loader.rb +1 -1
- data/lib/identity_cache/cached/attribute.rb +66 -3
- data/lib/identity_cache/cached/attribute_by_multi.rb +94 -9
- data/lib/identity_cache/cached/attribute_by_one.rb +4 -40
- data/lib/identity_cache/cached/belongs_to.rb +14 -10
- data/lib/identity_cache/cached/primary_index.rb +10 -2
- data/lib/identity_cache/cached/recursive/association.rb +1 -1
- data/lib/identity_cache/cached/reference/has_many.rb +1 -1
- data/lib/identity_cache/configuration_dsl.rb +1 -1
- data/lib/identity_cache/encoder.rb +1 -0
- data/lib/identity_cache/fallback_fetcher.rb +5 -1
- data/lib/identity_cache/memoized_cache_proxy.rb +10 -0
- data/lib/identity_cache/parent_model_expiration.rb +8 -3
- data/lib/identity_cache/query_api.rb +12 -6
- data/lib/identity_cache/should_use_cache.rb +10 -0
- data/lib/identity_cache/version.rb +1 -1
- data/lib/identity_cache/with_primary_index.rb +27 -14
- data/lib/identity_cache.rb +131 -21
- data/shipit.rubygems.yml +4 -1
- metadata +13 -12
- data/isogun.yml +0 -11
@@ -60,14 +60,23 @@ module IdentityCache
|
|
60
60
|
@cache_backend.write(key, IdentityCache::DELETED, expires_in: IdentityCache::DELETED_TTL.seconds)
|
61
61
|
end
|
62
62
|
|
63
|
+
def delete_multi(keys)
|
64
|
+
key_values = keys.map { |key| [key, IdentityCache::DELETED] }.to_h
|
65
|
+
@cache_backend.write_multi(key_values, expires_in: IdentityCache::DELETED_TTL.seconds)
|
66
|
+
end
|
67
|
+
|
63
68
|
def clear
|
64
69
|
@cache_backend.clear
|
65
70
|
end
|
66
71
|
|
67
72
|
def fetch_multi(keys, &block)
|
68
|
-
|
69
|
-
|
70
|
-
|
73
|
+
if IdentityCache.should_use_cache?
|
74
|
+
results = cas_multi(keys, &block)
|
75
|
+
results = add_multi(keys, &block) if results.nil?
|
76
|
+
results
|
77
|
+
else
|
78
|
+
{}
|
79
|
+
end
|
71
80
|
end
|
72
81
|
|
73
82
|
def fetch(key, fill_lock_duration: nil, lock_wait_tries: 2, &block)
|
@@ -44,7 +44,7 @@ module IdentityCache
|
|
44
44
|
# @param db_key [Array] Reference to what to load from the database.
|
45
45
|
# @return [Hash] A hash mapping each database key to its corresponding value
|
46
46
|
def load_multi(cache_fetcher, db_keys)
|
47
|
-
load_batch(cache_fetcher => db_keys).fetch(cache_fetcher)
|
47
|
+
load_batch({ cache_fetcher => db_keys }).fetch(cache_fetcher)
|
48
48
|
end
|
49
49
|
|
50
50
|
# Load multiple keys for multiple cache fetchers
|
@@ -35,16 +35,32 @@ module IdentityCache
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def expire(record)
|
38
|
+
all_deleted = true
|
39
|
+
|
38
40
|
unless record.send(:was_new_record?)
|
39
41
|
old_key = old_cache_key(record)
|
40
|
-
|
42
|
+
|
43
|
+
if Thread.current[:idc_deferred_expiration]
|
44
|
+
Thread.current[:idc_attributes_to_expire] << old_key
|
45
|
+
# defer the deletion, and don't block the following deletion
|
46
|
+
all_deleted = true
|
47
|
+
else
|
48
|
+
all_deleted = IdentityCache.cache.delete(old_key)
|
49
|
+
end
|
41
50
|
end
|
42
51
|
unless record.destroyed?
|
43
52
|
new_key = new_cache_key(record)
|
44
53
|
if new_key != old_key
|
45
|
-
|
54
|
+
if Thread.current[:idc_deferred_expiration]
|
55
|
+
Thread.current[:idc_attributes_to_expire] << new_key
|
56
|
+
all_deleted = true
|
57
|
+
else
|
58
|
+
all_deleted = IdentityCache.cache.delete(new_key) && all_deleted
|
59
|
+
end
|
46
60
|
end
|
47
61
|
end
|
62
|
+
|
63
|
+
all_deleted
|
48
64
|
end
|
49
65
|
|
50
66
|
def cache_key(index_key)
|
@@ -59,6 +75,48 @@ module IdentityCache
|
|
59
75
|
unique ? results.first : results
|
60
76
|
end
|
61
77
|
|
78
|
+
def fetch_multi(keys)
|
79
|
+
keys = keys.map { |key| cast_db_key(key) }
|
80
|
+
|
81
|
+
unless model.should_use_cache?
|
82
|
+
return load_multi_from_db(keys)
|
83
|
+
end
|
84
|
+
|
85
|
+
unordered_hash = CacheKeyLoader.load_multi(self, keys)
|
86
|
+
|
87
|
+
# Calling `values` on the result is expected to return the values in the same order as their
|
88
|
+
# corresponding keys. The fetch_multi_by_#{field_list} generated methods depend on this.
|
89
|
+
keys.each_with_object({}) do |key, ordered_hash|
|
90
|
+
ordered_hash[key] = unordered_hash.fetch(key)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def load_multi_from_db(keys)
|
95
|
+
result = {}
|
96
|
+
return result if keys.empty?
|
97
|
+
|
98
|
+
rows = load_multi_rows(keys)
|
99
|
+
default = unique ? nil : []
|
100
|
+
keys.each do |index_value|
|
101
|
+
result[index_value] = default.try!(:dup)
|
102
|
+
end
|
103
|
+
if unique
|
104
|
+
rows.each do |index_value, attribute_value|
|
105
|
+
result[index_value] = attribute_value
|
106
|
+
end
|
107
|
+
else
|
108
|
+
rows.each do |index_value, attribute_value|
|
109
|
+
result[index_value] << attribute_value
|
110
|
+
end
|
111
|
+
end
|
112
|
+
result
|
113
|
+
end
|
114
|
+
|
115
|
+
def cache_encode(db_value)
|
116
|
+
db_value
|
117
|
+
end
|
118
|
+
alias_method :cache_decode, :cache_encode
|
119
|
+
|
62
120
|
private
|
63
121
|
|
64
122
|
# @abstract
|
@@ -76,6 +134,11 @@ module IdentityCache
|
|
76
134
|
raise NotImplementedError
|
77
135
|
end
|
78
136
|
|
137
|
+
# @abstract
|
138
|
+
def load_multi_rows(_index_keys)
|
139
|
+
raise NotImplementedError
|
140
|
+
end
|
141
|
+
|
79
142
|
# @abstract
|
80
143
|
def cache_key_from_key_values(_key_values)
|
81
144
|
raise NotImplementedError
|
@@ -101,9 +164,9 @@ module IdentityCache
|
|
101
164
|
end
|
102
165
|
|
103
166
|
def old_cache_key(record)
|
167
|
+
changes = record.transaction_changed_attributes
|
104
168
|
old_key_values = key_fields.map do |field|
|
105
169
|
field_string = field.to_s
|
106
|
-
changes = record.transaction_changed_attributes
|
107
170
|
if record.destroyed? && changes.key?(field_string)
|
108
171
|
changes[field_string]
|
109
172
|
elsif record.persisted? && changes.key?(field_string)
|
@@ -6,9 +6,14 @@ module IdentityCache
|
|
6
6
|
def build
|
7
7
|
cached_attribute = self
|
8
8
|
|
9
|
-
model.define_singleton_method(:"fetch_#{fetch_method_suffix}") do |*
|
9
|
+
model.define_singleton_method(:"fetch_#{fetch_method_suffix}") do |*keys|
|
10
10
|
raise_if_scoped
|
11
|
-
cached_attribute.fetch(
|
11
|
+
cached_attribute.fetch(keys)
|
12
|
+
end
|
13
|
+
|
14
|
+
model.define_singleton_method(:"fetch_multi_#{fetch_method_suffix}") do |keys|
|
15
|
+
raise_if_scoped
|
16
|
+
cached_attribute.fetch_multi(keys)
|
12
17
|
end
|
13
18
|
end
|
14
19
|
|
@@ -16,22 +21,102 @@ module IdentityCache
|
|
16
21
|
|
17
22
|
# Attribute method overrides
|
18
23
|
|
19
|
-
def cast_db_key(
|
24
|
+
def cast_db_key(keys)
|
20
25
|
field_types.each_with_index do |type, i|
|
21
|
-
|
26
|
+
keys[i] = type.cast(keys[i])
|
22
27
|
end
|
23
|
-
|
28
|
+
keys
|
24
29
|
end
|
25
30
|
|
26
|
-
def unhashed_values_cache_key_string(
|
27
|
-
|
31
|
+
def unhashed_values_cache_key_string(keys)
|
32
|
+
keys.map { |v| v.try!(:to_s).inspect }.join("/")
|
28
33
|
end
|
29
34
|
|
30
|
-
def load_from_db_where_conditions(
|
31
|
-
Hash[key_fields.zip(
|
35
|
+
def load_from_db_where_conditions(keys)
|
36
|
+
Hash[key_fields.zip(keys)]
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_multi_rows(keys)
|
40
|
+
query = load_multi_rows_query(keys)
|
41
|
+
fields = key_fields
|
42
|
+
if (attribute_index = key_fields.index(attribute))
|
43
|
+
fields = fields.dup
|
44
|
+
fields.delete(attribute)
|
45
|
+
end
|
46
|
+
|
47
|
+
query.pluck(attribute, *fields).map do |attribute, *key_values|
|
48
|
+
key_values.insert(attribute_index, attribute) if attribute_index
|
49
|
+
[key_values, attribute]
|
50
|
+
end
|
32
51
|
end
|
33
52
|
|
34
53
|
alias_method :cache_key_from_key_values, :cache_key
|
54
|
+
|
55
|
+
# Helper methods
|
56
|
+
|
57
|
+
def load_multi_rows_query(keys)
|
58
|
+
# Find fields with a common value for the below common_query optimization
|
59
|
+
common_conditions = {}
|
60
|
+
other_field_indexes = []
|
61
|
+
key_fields.each_with_index do |field, i|
|
62
|
+
first_value = keys.first[i]
|
63
|
+
is_unique = keys.all? { |key_values| first_value == key_values[i] }
|
64
|
+
|
65
|
+
if is_unique
|
66
|
+
common_conditions[field] = first_value
|
67
|
+
else
|
68
|
+
other_field_indexes << i
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
common_query = if common_conditions.any?
|
73
|
+
# Optimization for the case of fields in which the key being searched
|
74
|
+
# for is always the same. This results in simple equality conditions
|
75
|
+
# being produced for these fields (e.g. "WHERE field = value").
|
76
|
+
unsorted_model.where(common_conditions)
|
77
|
+
end
|
78
|
+
|
79
|
+
case other_field_indexes.size
|
80
|
+
when 0
|
81
|
+
common_query
|
82
|
+
when 1
|
83
|
+
# Micro-optimization for the case of a single unique field.
|
84
|
+
# This results in a single "WHERE field IN (values)" statement being
|
85
|
+
# produced from a single query.
|
86
|
+
field_idx = other_field_indexes.first
|
87
|
+
field_name = key_fields[field_idx]
|
88
|
+
field_values = keys.map { |key| key[field_idx] }
|
89
|
+
(common_query || unsorted_model).where(field_name => field_values)
|
90
|
+
else
|
91
|
+
# More than one unique field, so we need to generate a query for each
|
92
|
+
# set of values for each unique field.
|
93
|
+
#
|
94
|
+
# This results in multiple
|
95
|
+
# "WHERE field = value AND field_2 = value_2 OR ..."
|
96
|
+
# statements being produced from an object like
|
97
|
+
# [{ field: value, field_2: value_2 }, ...]
|
98
|
+
query = keys.reduce(nil) do |query, key|
|
99
|
+
condition = {}
|
100
|
+
other_field_indexes.each do |field_idx|
|
101
|
+
field = key_fields[field_idx]
|
102
|
+
condition[field] = key[field_idx]
|
103
|
+
end
|
104
|
+
subquery = unsorted_model.where(condition)
|
105
|
+
|
106
|
+
query ? query.or(subquery) : subquery
|
107
|
+
end
|
108
|
+
|
109
|
+
if common_query
|
110
|
+
common_query.merge(query)
|
111
|
+
else
|
112
|
+
query
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def unsorted_model
|
118
|
+
model.reorder(nil)
|
119
|
+
end
|
35
120
|
end
|
36
121
|
end
|
37
122
|
end
|
@@ -24,46 +24,6 @@ module IdentityCache
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def fetch_multi(keys)
|
28
|
-
keys = keys.map { |key| cast_db_key(key) }
|
29
|
-
|
30
|
-
unless model.should_use_cache?
|
31
|
-
return load_multi_from_db(keys)
|
32
|
-
end
|
33
|
-
|
34
|
-
unordered_hash = CacheKeyLoader.load_multi(self, keys)
|
35
|
-
|
36
|
-
# Calling `values` on the result is expected to return the values in the same order as their
|
37
|
-
# corresponding keys. The fetch_multi_by_#{field_list} generated methods depend on this.
|
38
|
-
ordered_hash = {}
|
39
|
-
keys.each { |key| ordered_hash[key] = unordered_hash.fetch(key) }
|
40
|
-
ordered_hash
|
41
|
-
end
|
42
|
-
|
43
|
-
def load_multi_from_db(keys)
|
44
|
-
rows = model.reorder(nil).where(load_from_db_where_conditions(keys)).pluck(key_field, attribute)
|
45
|
-
result = {}
|
46
|
-
default = unique ? nil : []
|
47
|
-
keys.each do |index_value|
|
48
|
-
result[index_value] = default.try!(:dup)
|
49
|
-
end
|
50
|
-
if unique
|
51
|
-
rows.each do |index_value, attribute_value|
|
52
|
-
result[index_value] = attribute_value
|
53
|
-
end
|
54
|
-
else
|
55
|
-
rows.each do |index_value, attribute_value|
|
56
|
-
result[index_value] << attribute_value
|
57
|
-
end
|
58
|
-
end
|
59
|
-
result
|
60
|
-
end
|
61
|
-
|
62
|
-
def cache_encode(db_value)
|
63
|
-
db_value
|
64
|
-
end
|
65
|
-
alias_method :cache_decode, :cache_encode
|
66
|
-
|
67
27
|
private
|
68
28
|
|
69
29
|
# Attribute method overrides
|
@@ -80,6 +40,10 @@ module IdentityCache
|
|
80
40
|
{ key_field => key_values }
|
81
41
|
end
|
82
42
|
|
43
|
+
def load_multi_rows(keys)
|
44
|
+
model.reorder(nil).where(load_from_db_where_conditions(keys)).pluck(key_field, attribute)
|
45
|
+
end
|
46
|
+
|
83
47
|
def cache_key_from_key_values(key_values)
|
84
48
|
cache_key(key_values.first)
|
85
49
|
end
|
@@ -9,7 +9,7 @@ module IdentityCache
|
|
9
9
|
reflection.active_record.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
10
10
|
def #{cached_accessor_name}
|
11
11
|
association_klass = association(:#{name}).klass
|
12
|
-
if
|
12
|
+
if #{reflection.foreign_key}.present? && !association(:#{name}).loaded? && (loaded_by_idc? || association_klass.should_use_cache?)
|
13
13
|
if defined?(#{records_variable_name})
|
14
14
|
#{records_variable_name}
|
15
15
|
else
|
@@ -77,16 +77,20 @@ module IdentityCache
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
if ids_to_owner_record.any?
|
81
|
+
load_strategy.load_multi(
|
82
|
+
reflection.klass.cached_primary_index,
|
83
|
+
ids_to_owner_record.keys
|
84
|
+
) do |associated_records_by_id|
|
85
|
+
associated_records_by_id.each do |id, associated_record|
|
86
|
+
owner_record = ids_to_owner_record.fetch(id)
|
87
|
+
write(owner_record, associated_record)
|
88
|
+
end
|
88
89
|
|
89
|
-
|
90
|
+
yield associated_records_by_id.values.compact
|
91
|
+
end
|
92
|
+
else
|
93
|
+
yield records.filter_map { |record| record.instance_variable_get(records_variable_name) }
|
90
94
|
end
|
91
95
|
end
|
92
96
|
end
|
@@ -45,7 +45,11 @@ module IdentityCache
|
|
45
45
|
|
46
46
|
def expire(id)
|
47
47
|
id = cast_id(id)
|
48
|
-
|
48
|
+
if Thread.current[:idc_deferred_expiration]
|
49
|
+
Thread.current[:idc_records_to_expire] << cache_key(id)
|
50
|
+
else
|
51
|
+
IdentityCache.cache.delete(cache_key(id))
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
def cache_key(id)
|
@@ -54,7 +58,10 @@ module IdentityCache
|
|
54
58
|
|
55
59
|
def load_one_from_db(id)
|
56
60
|
record = build_query(id).take
|
57
|
-
|
61
|
+
if record
|
62
|
+
model.send(:setup_embedded_associations_on_miss, [record])
|
63
|
+
record.send(:mark_as_loaded_by_idc)
|
64
|
+
end
|
58
65
|
record
|
59
66
|
end
|
60
67
|
|
@@ -63,6 +70,7 @@ module IdentityCache
|
|
63
70
|
|
64
71
|
records = build_query(ids).to_a
|
65
72
|
model.send(:setup_embedded_associations_on_miss, records)
|
73
|
+
records.each { |record| record.send(:mark_as_loaded_by_idc) }
|
66
74
|
records.index_by(&:id)
|
67
75
|
end
|
68
76
|
|
@@ -25,7 +25,7 @@ module IdentityCache
|
|
25
25
|
def read(record)
|
26
26
|
assoc = record.association(name)
|
27
27
|
|
28
|
-
if assoc.
|
28
|
+
if !assoc.loaded? && assoc.target.blank? && (record.send(:loaded_by_idc?) || assoc.klass.should_use_cache?)
|
29
29
|
if record.instance_variable_defined?(records_variable_name)
|
30
30
|
record.instance_variable_get(records_variable_name)
|
31
31
|
elsif record.instance_variable_defined?(dehydrated_variable_name)
|
@@ -22,7 +22,7 @@ module IdentityCache
|
|
22
22
|
|
23
23
|
def #{cached_accessor_name}
|
24
24
|
assoc = association(:#{name})
|
25
|
-
if assoc.klass.should_use_cache? && !assoc.loaded? && assoc.target.blank?
|
25
|
+
if (loaded_by_idc? || assoc.klass.should_use_cache?) && !assoc.loaded? && assoc.target.blank?
|
26
26
|
#{records_variable_name} ||= #{reflection.class_name}.fetch_multi(#{cached_ids_name})
|
27
27
|
else
|
28
28
|
#{name}.to_a
|
@@ -143,7 +143,7 @@ module IdentityCache
|
|
143
143
|
|
144
144
|
def check_association_for_caching(association)
|
145
145
|
unless (association_reflection = reflect_on_association(association))
|
146
|
-
raise AssociationError, "Association named '#{association}' was not found on #{self
|
146
|
+
raise AssociationError, "Association named '#{association}' was not found on #{self}"
|
147
147
|
end
|
148
148
|
if association_reflection.options[:through]
|
149
149
|
raise UnsupportedAssociationError, "caching through associations isn't supported"
|
@@ -21,7 +21,11 @@ module IdentityCache
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def fetch_multi(keys)
|
24
|
-
results =
|
24
|
+
results = if IdentityCache.should_use_cache?
|
25
|
+
@cache_backend.read_multi(*keys)
|
26
|
+
else
|
27
|
+
{}
|
28
|
+
end
|
25
29
|
missed_keys = keys - results.keys
|
26
30
|
unless missed_keys.empty?
|
27
31
|
replacement_results = yield missed_keys
|
@@ -70,6 +70,16 @@ module IdentityCache
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
def delete_multi(keys)
|
74
|
+
memoizing = memoizing?
|
75
|
+
ActiveSupport::Notifications.instrument("cache_delete_multi.identity_cache", memoizing: memoizing) do
|
76
|
+
if memoizing
|
77
|
+
keys.each { |key| memoized_key_values.delete(key) }
|
78
|
+
end
|
79
|
+
@cache_fetcher.delete_multi(keys)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
73
83
|
def fetch(key, cache_fetcher_options = {}, &block)
|
74
84
|
memo_misses = 0
|
75
85
|
cache_misses = 0
|
@@ -45,8 +45,13 @@ module IdentityCache
|
|
45
45
|
def expire_parent_caches
|
46
46
|
parents_to_expire = Set.new
|
47
47
|
add_parents_to_cache_expiry_set(parents_to_expire)
|
48
|
-
parents_to_expire.
|
49
|
-
|
48
|
+
parents_to_expire.select! { |parent| parent.class.primary_cache_index_enabled }
|
49
|
+
parents_to_expire.reduce(true) do |all_expired, parent|
|
50
|
+
if Thread.current[:idc_deferred_parent_expiration]
|
51
|
+
Thread.current[:idc_parent_records_for_cache_expiry] << parent
|
52
|
+
next parent
|
53
|
+
end
|
54
|
+
parent.expire_primary_index && all_expired
|
50
55
|
end
|
51
56
|
end
|
52
57
|
|
@@ -65,7 +70,7 @@ module IdentityCache
|
|
65
70
|
|
66
71
|
def parents_to_expire_on_changes(parents_to_expire, association_name, cached_associations)
|
67
72
|
parent_association = self.class.reflect_on_association(association_name)
|
68
|
-
foreign_key = parent_association.
|
73
|
+
foreign_key = parent_association.foreign_key
|
69
74
|
|
70
75
|
new_parent = send(association_name)
|
71
76
|
|
@@ -70,6 +70,8 @@ module IdentityCache
|
|
70
70
|
readonly: IdentityCache.fetch_read_only_records && should_use_cache?)
|
71
71
|
return if records.empty?
|
72
72
|
|
73
|
+
return unless should_use_cache?
|
74
|
+
|
73
75
|
records.each(&:readonly!) if readonly
|
74
76
|
each_id_embedded_association do |cached_association|
|
75
77
|
preload_id_embedded_association(records, cached_association)
|
@@ -166,15 +168,17 @@ module IdentityCache
|
|
166
168
|
def _run_commit_callbacks
|
167
169
|
if destroyed? || transaction_changed_attributes.present?
|
168
170
|
expire_cache
|
169
|
-
expire_parent_caches
|
170
171
|
end
|
171
172
|
super
|
172
173
|
end
|
173
174
|
|
174
|
-
# Invalidate the cache data associated with the record.
|
175
|
+
# Invalidate the cache data associated with the record. Returns `true` on success,
|
176
|
+
# `false` otherwise.
|
175
177
|
def expire_cache
|
176
|
-
|
177
|
-
|
178
|
+
expired_parent_caches = expire_parent_caches
|
179
|
+
expired_attribute_indexes = expire_attribute_indexes
|
180
|
+
|
181
|
+
expired_parent_caches && expired_attribute_indexes
|
178
182
|
end
|
179
183
|
|
180
184
|
# @api private
|
@@ -185,9 +189,11 @@ module IdentityCache
|
|
185
189
|
|
186
190
|
private
|
187
191
|
|
192
|
+
# Even if we have problems with some attributes, carry over the results and expire
|
193
|
+
# all possible attributes without array allocation.
|
188
194
|
def expire_attribute_indexes # :nodoc:
|
189
|
-
cache_indexes.
|
190
|
-
cached_attribute.expire(self)
|
195
|
+
cache_indexes.reduce(true) do |all_expired, cached_attribute|
|
196
|
+
cached_attribute.expire(self) && all_expired
|
191
197
|
end
|
192
198
|
end
|
193
199
|
end
|
@@ -7,8 +7,9 @@ module IdentityCache
|
|
7
7
|
include WithoutPrimaryIndex
|
8
8
|
|
9
9
|
def expire_cache
|
10
|
-
expire_primary_index
|
11
|
-
|
10
|
+
expired_primary_index = expire_primary_index
|
11
|
+
|
12
|
+
super && expired_primary_index
|
12
13
|
end
|
13
14
|
|
14
15
|
# @api private
|
@@ -34,8 +35,8 @@ module IdentityCache
|
|
34
35
|
# Declares a new index in the cache for the class where IdentityCache was
|
35
36
|
# included.
|
36
37
|
#
|
37
|
-
# IdentityCache will add a fetch_by_field1_and_field2_and_...field
|
38
|
-
# index.
|
38
|
+
# IdentityCache will add a fetch_by_field1_and_field2_and_...field and
|
39
|
+
# fetch_multi_by_field1_and_field2_and_...field for every index.
|
39
40
|
#
|
40
41
|
# == Example:
|
41
42
|
#
|
@@ -44,7 +45,10 @@ module IdentityCache
|
|
44
45
|
# cache_index :name, :vendor
|
45
46
|
# end
|
46
47
|
#
|
47
|
-
# Will add
|
48
|
+
# Will add:
|
49
|
+
#
|
50
|
+
# Product.fetch_by_name_and_vendor
|
51
|
+
# Product.fetch_multi_by_name_and_vendor
|
48
52
|
#
|
49
53
|
# == Parameters
|
50
54
|
#
|
@@ -81,15 +85,13 @@ module IdentityCache
|
|
81
85
|
CODE
|
82
86
|
end
|
83
87
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
CODE
|
92
|
-
end
|
88
|
+
instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
|
89
|
+
def fetch_multi_by_#{field_list}(index_values, includes: nil)
|
90
|
+
ids = fetch_multi_id_by_#{field_list}(index_values).values.flatten(1)
|
91
|
+
return ids if ids.empty?
|
92
|
+
fetch_multi(ids, includes: includes)
|
93
|
+
end
|
94
|
+
CODE
|
93
95
|
end
|
94
96
|
|
95
97
|
# Similar to ActiveRecord::Base#exists? will return true if the id can be
|
@@ -149,6 +151,8 @@ module IdentityCache
|
|
149
151
|
ensure_base_model
|
150
152
|
raise_if_scoped
|
151
153
|
ids.flatten!(1)
|
154
|
+
return [] if ids.none?
|
155
|
+
|
152
156
|
records = cached_primary_index.fetch_multi(ids)
|
153
157
|
prefetch_associations(includes, records) if includes
|
154
158
|
records
|
@@ -158,6 +162,15 @@ module IdentityCache
|
|
158
162
|
def expire_primary_key_cache_index(id)
|
159
163
|
cached_primary_index.expire(id)
|
160
164
|
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def inherited(subclass)
|
169
|
+
super
|
170
|
+
subclass.class_eval do
|
171
|
+
@cached_primary_index = nil
|
172
|
+
end
|
173
|
+
end
|
161
174
|
end
|
162
175
|
end
|
163
176
|
end
|