identity_cache 1.6.1 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +3 -3
- data/Rakefile +24 -0
- data/dev.yml +10 -6
- data/lib/identity_cache/cache_fetcher.rb +5 -0
- data/lib/identity_cache/cached/attribute.rb +15 -3
- data/lib/identity_cache/cached/primary_index.rb +5 -1
- data/lib/identity_cache/memoized_cache_proxy.rb +10 -0
- data/lib/identity_cache/parent_model_expiration.rb +0 -4
- data/lib/identity_cache/version.rb +1 -1
- data/lib/identity_cache.rb +27 -18
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 463112ddb217364a26a8232ef1958c45bdb24fc7dcd891be3e7d4db4ca4d29a4
|
4
|
+
data.tar.gz: 13ea742aca4c33f710a6f8a229f6cf0531dba50291c1ec7c67bce735028ce527
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4c3cae9c3c4a6444f62d84242326da8901e0aa66895c8b6d2916930a5824fe402299d5e7c6270982e37fa9cd97991310756320aea0fb93b64a1f515a27f1c1f
|
7
|
+
data.tar.gz: 5512f9b845a4bc9ea8821d6e8563bc4307ba46a32fe32f1f5d2a708b624be3a5ed72c1b64fe2a9ade21f52ffa8d6c3220e23bc3bb433249bceb4d6eacdf7ba67
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -8,7 +8,7 @@ GIT
|
|
8
8
|
PATH
|
9
9
|
remote: .
|
10
10
|
specs:
|
11
|
-
identity_cache (1.6.
|
11
|
+
identity_cache (1.6.2)
|
12
12
|
activerecord (>= 7.0)
|
13
13
|
ar_transaction_changes (~> 1.1)
|
14
14
|
|
@@ -64,8 +64,8 @@ GEM
|
|
64
64
|
rainbow (3.1.1)
|
65
65
|
rake (13.1.0)
|
66
66
|
regexp_parser (2.9.0)
|
67
|
-
rexml (3.
|
68
|
-
strscan
|
67
|
+
rexml (3.3.6)
|
68
|
+
strscan
|
69
69
|
rubocop (1.61.0)
|
70
70
|
json (~> 2.3)
|
71
71
|
language_server-protocol (>= 3.17.0)
|
data/Rakefile
CHANGED
@@ -47,3 +47,27 @@ namespace :profile do
|
|
47
47
|
ruby "./performance/profile.rb"
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
namespace :db do
|
52
|
+
desc "Create the identity_cache_test database"
|
53
|
+
task :create do
|
54
|
+
require "mysql2"
|
55
|
+
|
56
|
+
config = {
|
57
|
+
host: ENV.fetch("MYSQL_HOST") || "localhost",
|
58
|
+
port: ENV.fetch("MYSQL_PORT") || 1037,
|
59
|
+
username: ENV.fetch("MYSQL_USER") || "root",
|
60
|
+
password: ENV.fetch("MYSQL_PASSWORD") || "",
|
61
|
+
}
|
62
|
+
|
63
|
+
begin
|
64
|
+
client = Mysql2::Client.new(config)
|
65
|
+
client.query("CREATE DATABASE IF NOT EXISTS identity_cache_test")
|
66
|
+
puts "Database 'identity_cache_test' created successfully. host: #{config[:host]}, port: #{config[:port]}"
|
67
|
+
rescue Mysql2::Error => e
|
68
|
+
puts "Error creating database: #{e.message}"
|
69
|
+
ensure
|
70
|
+
client&.close
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/dev.yml
CHANGED
@@ -5,6 +5,10 @@ up:
|
|
5
5
|
- bundler
|
6
6
|
- memcached
|
7
7
|
- mysql
|
8
|
+
- custom:
|
9
|
+
name: create database identity_cache_test
|
10
|
+
met?: mysql -u root -h 127.0.0.1 -P 1037 -e "SHOW DATABASES;" | grep identity_cache_test
|
11
|
+
meet: bundle exec rake db:create
|
8
12
|
|
9
13
|
commands:
|
10
14
|
test:
|
@@ -12,7 +16,7 @@ commands:
|
|
12
16
|
optional:
|
13
17
|
argument: file
|
14
18
|
optional: args...
|
15
|
-
desc:
|
19
|
+
desc: "Run tests"
|
16
20
|
run: |
|
17
21
|
if [[ $# -eq 0 ]]; then
|
18
22
|
bundle exec rake test
|
@@ -21,21 +25,21 @@ commands:
|
|
21
25
|
fi
|
22
26
|
|
23
27
|
style:
|
24
|
-
desc:
|
28
|
+
desc: "Run rubocop checks"
|
25
29
|
run: bundle exec rubocop "$@"
|
26
30
|
|
27
31
|
check:
|
28
|
-
desc:
|
32
|
+
desc: "Run tests and style checks"
|
29
33
|
run: bundle exec rake test && bundle exec rubocop
|
30
34
|
|
31
35
|
benchmark-cpu:
|
32
|
-
desc:
|
36
|
+
desc: "Run the identity cache CPU benchmark"
|
33
37
|
run: bundle exec rake benchmark:cpu
|
34
38
|
|
35
39
|
profile:
|
36
|
-
desc:
|
40
|
+
desc: "Profile IDC code"
|
37
41
|
run: bundle exec rake profile:run
|
38
42
|
|
39
43
|
update-serialization-format:
|
40
|
-
desc:
|
44
|
+
desc: "Update serialization format test fixture"
|
41
45
|
run: bundle exec rake update_serialization_format
|
@@ -60,6 +60,11 @@ 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
|
@@ -39,12 +39,24 @@ module IdentityCache
|
|
39
39
|
|
40
40
|
unless record.send(:was_new_record?)
|
41
41
|
old_key = old_cache_key(record)
|
42
|
-
|
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
|
43
50
|
end
|
44
51
|
unless record.destroyed?
|
45
52
|
new_key = new_cache_key(record)
|
46
53
|
if new_key != old_key
|
47
|
-
|
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
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
@@ -152,9 +164,9 @@ module IdentityCache
|
|
152
164
|
end
|
153
165
|
|
154
166
|
def old_cache_key(record)
|
167
|
+
changes = record.transaction_changed_attributes
|
155
168
|
old_key_values = key_fields.map do |field|
|
156
169
|
field_string = field.to_s
|
157
|
-
changes = record.transaction_changed_attributes
|
158
170
|
if record.destroyed? && changes.key?(field_string)
|
159
171
|
changes[field_string]
|
160
172
|
elsif record.persisted? && changes.key?(field_string)
|
@@ -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)
|
@@ -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
|
@@ -47,10 +47,6 @@ module IdentityCache
|
|
47
47
|
add_parents_to_cache_expiry_set(parents_to_expire)
|
48
48
|
parents_to_expire.select! { |parent| parent.class.primary_cache_index_enabled }
|
49
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
50
|
parent.expire_primary_index && all_expired
|
55
51
|
end
|
56
52
|
end
|
data/lib/identity_cache.rb
CHANGED
@@ -60,7 +60,7 @@ module IdentityCache
|
|
60
60
|
|
61
61
|
class InverseAssociationError < StandardError; end
|
62
62
|
|
63
|
-
class
|
63
|
+
class NestedDeferredCacheExpirationBlockError < StandardError; end
|
64
64
|
|
65
65
|
class UnsupportedScopeError < StandardError; end
|
66
66
|
|
@@ -202,42 +202,51 @@ module IdentityCache
|
|
202
202
|
result
|
203
203
|
end
|
204
204
|
|
205
|
-
# Executes a block with deferred
|
206
|
-
#
|
207
|
-
# completes, it
|
208
|
-
#
|
209
|
-
# is already active on the current thread.
|
205
|
+
# Executes a block with deferred cache expiration, ensuring that the records' (parent,
|
206
|
+
# children and attributes) cache expiration is deferred until the block completes. When
|
207
|
+
# the block completes, it issues delete_multi calls for all the records and attributes
|
208
|
+
# that were marked for expiration.
|
210
209
|
#
|
211
210
|
# == Parameters:
|
212
211
|
# No parameters.
|
213
212
|
#
|
214
213
|
# == Raises:
|
215
|
-
#
|
214
|
+
# NestedDeferredCacheExpirationBlockError if a deferred cache expiration block is already active.
|
216
215
|
#
|
217
216
|
# == Yield:
|
218
|
-
# Runs the provided block with deferred
|
217
|
+
# Runs the provided block with deferred cache expiration.
|
219
218
|
#
|
220
219
|
# == Returns:
|
221
220
|
# The result of executing the provided block.
|
222
221
|
#
|
223
222
|
# == Ensures:
|
224
|
-
# Cleans up thread-local variables related to deferred
|
223
|
+
# Cleans up thread-local variables related to deferred cache expiration regardless
|
225
224
|
# of whether the block raises an exception.
|
226
|
-
def
|
227
|
-
raise
|
225
|
+
def with_deferred_expiration
|
226
|
+
raise NestedDeferredCacheExpirationBlockError if Thread.current[:idc_deferred_expiration]
|
228
227
|
|
229
|
-
Thread.current[:
|
230
|
-
Thread.current[:
|
228
|
+
Thread.current[:idc_deferred_expiration] = true
|
229
|
+
Thread.current[:idc_records_to_expire] = Set.new
|
230
|
+
Thread.current[:idc_attributes_to_expire] = Set.new
|
231
231
|
|
232
232
|
result = yield
|
233
233
|
|
234
|
-
Thread.current[:
|
235
|
-
Thread.current[:
|
236
|
-
|
234
|
+
Thread.current[:idc_deferred_expiration] = nil
|
235
|
+
if Thread.current[:idc_records_to_expire].any?
|
236
|
+
IdentityCache.cache.delete_multi(
|
237
|
+
Thread.current[:idc_records_to_expire]
|
238
|
+
)
|
239
|
+
end
|
240
|
+
if Thread.current[:idc_attributes_to_expire].any?
|
241
|
+
IdentityCache.cache.delete_multi(
|
242
|
+
Thread.current[:idc_attributes_to_expire]
|
243
|
+
)
|
244
|
+
end
|
237
245
|
result
|
238
246
|
ensure
|
239
|
-
Thread.current[:
|
240
|
-
Thread.current[:
|
247
|
+
Thread.current[:idc_deferred_expiration] = nil
|
248
|
+
Thread.current[:idc_records_to_expire].clear
|
249
|
+
Thread.current[:idc_attributes_to_expire].clear
|
241
250
|
end
|
242
251
|
|
243
252
|
def with_fetch_read_only_records(value = true)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: identity_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.6.
|
4
|
+
version: 1.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Camilo Lopez
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date: 2024-
|
17
|
+
date: 2024-10-10 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activerecord
|
@@ -191,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
191
|
- !ruby/object:Gem::Version
|
192
192
|
version: '0'
|
193
193
|
requirements: []
|
194
|
-
rubygems_version: 3.5.
|
194
|
+
rubygems_version: 3.5.21
|
195
195
|
signing_key:
|
196
196
|
specification_version: 4
|
197
197
|
summary: IdentityCache lets you specify how you want to cache your model objects,
|