identity_cache 1.6.1 → 1.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cb45582de9a181e4b32595a183630023ba9af3a84ed5123308eada240420ab8
4
- data.tar.gz: 46ce8b2795adbd03ca67d69ba9ddd725ac1b7bcbf56ac85344ee220380495ad4
3
+ metadata.gz: 463112ddb217364a26a8232ef1958c45bdb24fc7dcd891be3e7d4db4ca4d29a4
4
+ data.tar.gz: 13ea742aca4c33f710a6f8a229f6cf0531dba50291c1ec7c67bce735028ce527
5
5
  SHA512:
6
- metadata.gz: 5a4053d1a93c89a058593fc998dad0c77ac32aae4d691db3087bd88743bae2aa76193112262e0ac536468c3ddc14ceef5fd34e2d770d7e8d3129d83df65d604d
7
- data.tar.gz: e397cb05b4d7bc5a3a6304fc4346ac63318c043c44dee032730af30c23a19d42168f0390039d6986aeef33dfda28e99dda831ca23dca8ea217a356d736dccd6e
6
+ metadata.gz: d4c3cae9c3c4a6444f62d84242326da8901e0aa66895c8b6d2916930a5824fe402299d5e7c6270982e37fa9cd97991310756320aea0fb93b64a1f515a27f1c1f
7
+ data.tar.gz: 5512f9b845a4bc9ea8821d6e8563bc4307ba46a32fe32f1f5d2a708b624be3a5ed72c1b64fe2a9ade21f52ffa8d6c3220e23bc3bb433249bceb4d6eacdf7ba67
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.6.2
6
+
7
+ - Support deferred expiry of associations and attributes. Add a rake task to create test database.
8
+
5
9
  ## 1.6.1
6
10
 
7
11
  - Fix deprecation warnings on Active Record 7.2. (#575)
data/Gemfile.lock CHANGED
@@ -8,7 +8,7 @@ GIT
8
8
  PATH
9
9
  remote: .
10
10
  specs:
11
- identity_cache (1.6.1)
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.2.8)
68
- strscan (>= 3.0.9)
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: 'Run tests'
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: 'Run rubocop checks'
28
+ desc: "Run rubocop checks"
25
29
  run: bundle exec rubocop "$@"
26
30
 
27
31
  check:
28
- desc: 'Run tests and style checks'
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: 'Run the identity cache CPU benchmark'
36
+ desc: "Run the identity cache CPU benchmark"
33
37
  run: bundle exec rake benchmark:cpu
34
38
 
35
39
  profile:
36
- desc: 'Profile IDC code'
40
+ desc: "Profile IDC code"
37
41
  run: bundle exec rake profile:run
38
42
 
39
43
  update-serialization-format:
40
- desc: 'Update serialization format test fixture'
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
- all_deleted = IdentityCache.cache.delete(old_key)
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
- all_deleted = IdentityCache.cache.delete(new_key) && all_deleted
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
- IdentityCache.cache.delete(cache_key(id))
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IdentityCache
4
- VERSION = "1.6.1"
4
+ VERSION = "1.6.2"
5
5
  CACHE_VERSION = 8
6
6
  end
@@ -60,7 +60,7 @@ module IdentityCache
60
60
 
61
61
  class InverseAssociationError < StandardError; end
62
62
 
63
- class NestedDeferredParentBlockError < StandardError; end
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 parent expiration, ensuring that the parent
206
- # records' cache expiration is deferred until the block completes. When the block
207
- # completes, it triggers expiration of the primary index for the parent records.
208
- # Raises a NestedDeferredParentBlockError if a deferred parent expiration block
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
- # NestedDeferredParentBlockError if a deferred parent expiration block is already active.
214
+ # NestedDeferredCacheExpirationBlockError if a deferred cache expiration block is already active.
216
215
  #
217
216
  # == Yield:
218
- # Runs the provided block with deferred parent expiration.
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 parent expiration regardless
223
+ # Cleans up thread-local variables related to deferred cache expiration regardless
225
224
  # of whether the block raises an exception.
226
- def with_deferred_parent_expiration
227
- raise NestedDeferredParentBlockError if Thread.current[:idc_deferred_parent_expiration]
225
+ def with_deferred_expiration
226
+ raise NestedDeferredCacheExpirationBlockError if Thread.current[:idc_deferred_expiration]
228
227
 
229
- Thread.current[:idc_deferred_parent_expiration] = true
230
- Thread.current[:idc_parent_records_for_cache_expiry] = Set.new
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[:idc_deferred_parent_expiration] = nil
235
- Thread.current[:idc_parent_records_for_cache_expiry].each(&:expire_primary_index)
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[:idc_deferred_parent_expiration] = nil
240
- Thread.current[:idc_parent_records_for_cache_expiry].clear
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.1
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-08-21 00:00:00.000000000 Z
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.17
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,