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 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,