iknow_view_models 3.10.0 → 3.10.1

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: 85aa5f3dd882b989e6a4d1fa8d5d89be347f5f3c215af86851f1ef086d583472
4
- data.tar.gz: d202810ca019d425b0de503b7a077d164d5919dc9206cf069e93b37ddf432334
3
+ metadata.gz: 417eb3615156fa48cf438ead03e2ea33e323c5baa7d411b1e0fafcea5050acb1
4
+ data.tar.gz: cdede3ad865389a0d006725819595aec6da1ea417e9d061452ef3198a39af3d8
5
5
  SHA512:
6
- metadata.gz: 3005ecb2044fd83003ccd444fb2b22d9787b74c929f157573baa084d4d3dff1b2f24d86e30f1ee9bd1e03604a4a0f9a57ec7431cadaea28faa45eda235dbcd28
7
- data.tar.gz: 82971047b73442cbccaf9e3e80f1386dc373329b91f94260f2daeeb5794f99b3502cc47b911a62da38f7d0932b9fea49ae72319de9fb2d79f8be6035e0a34110
6
+ metadata.gz: ada26778b54ffb60a825d408fe40f05c8a4aa93cbac7b5519b2338b5afb4cadfda0d1eb7b359b65584f88fbb9cadea4a35a7d2ba0aad9fb8e4d8ec9d5f894e03
7
+ data.tar.gz: b70d2b42d4a599c3d284dbb96c4506c8ff7b6104108ebcb7126c554fcd671eb0bad04dc4df5a1006f76af272a71c5ca46f54736bf4961821dd7f22160d46fada
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IknowViewModels
4
- VERSION = '3.10.0'
4
+ VERSION = '3.10.1'
5
5
  end
@@ -30,8 +30,21 @@ class ViewModel::ActiveRecord::Cache
30
30
  end
31
31
 
32
32
  def render_from_cache(viewmodel_class, ids, initial_viewmodels: nil, migration_versions: {}, locked: false, serialize_context: viewmodel_class.new_serialize_context)
33
- worker = CacheWorker.new(migration_versions: migration_versions, serialize_context: serialize_context)
34
- worker.render_from_cache(viewmodel_class, ids, initial_viewmodels: initial_viewmodels, locked: locked)
33
+ ignore_existing = false
34
+ begin
35
+ worker = CacheWorker.new(migration_versions: migration_versions, serialize_context: serialize_context, ignore_existing: ignore_existing)
36
+ worker.render_from_cache(viewmodel_class, ids, initial_viewmodels: initial_viewmodels, locked: locked)
37
+ rescue StaleCachedReference
38
+ # If the cache contents contained a unresolvable stale reference, retry
39
+ # while ignoring existing cached values (thereby updating the cache). This
40
+ # will have the side-effect of duplicate Before/AfterVisit callbacks.
41
+ if ignore_existing
42
+ raise
43
+ else
44
+ ignore_existing = true
45
+ retry
46
+ end
47
+ end
35
48
  end
36
49
  end
37
50
 
@@ -76,18 +89,25 @@ class ViewModel::ActiveRecord::Cache
76
89
  migration_versions: migration_versions, serialize_context: serialize_context)
77
90
  end
78
91
 
92
+ class StaleCachedReference < StandardError
93
+ def initialize(error)
94
+ super("Cached value contained stale reference: #{error.message}")
95
+ end
96
+ end
97
+
79
98
  class CacheWorker
80
99
  SENTINEL = Object.new
81
100
  WorklistEntry = Struct.new(:ref_name, :viewmodel)
82
101
 
83
102
  attr_reader :migration_versions, :serialize_context, :resolved_references
84
103
 
85
- def initialize(migration_versions:, serialize_context:)
104
+ def initialize(migration_versions:, serialize_context:, ignore_existing: false)
86
105
  @worklist = {} # Hash[type_name, Hash[id, WorklistEntry]]
87
106
  @resolved_references = {} # Hash[refname, json]
88
107
  @migration_versions = migration_versions
89
108
  @migrated_cache_versions = {}
90
109
  @serialize_context = serialize_context
110
+ @ignore_existing = ignore_existing
91
111
  end
92
112
 
93
113
  def render_from_cache(viewmodel_class, ids, initial_viewmodels: nil, locked: false)
@@ -104,7 +124,7 @@ class ViewModel::ActiveRecord::Cache
104
124
 
105
125
  ids_to_render = ids.to_set
106
126
 
107
- if viewmodel_class < CacheableView
127
+ if viewmodel_class < CacheableView && !@ignore_existing
108
128
  # Load existing serializations from the cache
109
129
  cached_serializations = load_from_cache(viewmodel_class.viewmodel_cache, ids)
110
130
  cached_serializations.each do |id, data|
@@ -168,7 +188,7 @@ class ViewModel::ActiveRecord::Cache
168
188
  @resolved_references[entry.ref_name] = SENTINEL
169
189
  end
170
190
 
171
- if viewmodel_class < CacheableView
191
+ if viewmodel_class < CacheableView && !@ignore_existing
172
192
  cached_serializations = load_from_cache(viewmodel_class.viewmodel_cache, required_entries.keys)
173
193
  cached_serializations.each do |id, data|
174
194
  ref_name = required_entries.delete(id).ref_name
@@ -181,8 +201,18 @@ class ViewModel::ActiveRecord::Cache
181
201
  h[id] = entry.viewmodel if entry.viewmodel
182
202
  end
183
203
 
184
- viewmodels = find_and_preload_viewmodels(viewmodel_class, required_entries.keys,
185
- available_viewmodels: available_viewmodels)
204
+ viewmodels =
205
+ begin
206
+ find_and_preload_viewmodels(viewmodel_class, required_entries.keys,
207
+ available_viewmodels: available_viewmodels)
208
+ rescue ViewModel::DeserializationError::NotFound => e
209
+ # We encountered a reference to an entity that does not exist.
210
+ # If this reference was potentially found in cached data, it
211
+ # could be stale: we can retry without using the cache.
212
+ # If the reference was obtained directly, it indicates invalid
213
+ # data such as an invalid foreign key, and we cannot recover.
214
+ raise StaleCachedReference.new(e)
215
+ end
186
216
 
187
217
  loaded_serializations = serialize_and_cache(viewmodels)
188
218
  loaded_serializations.each do |id, data|
@@ -209,11 +209,27 @@ class ViewModel::ActiveRecord
209
209
  end
210
210
 
211
211
  it 'returns the right serialization with provided locked initial viewmodel' do
212
- locked_root_view = viewmodel_class.new(model_class.lock("FOR SHARE").find(root.id))
212
+ locked_root_view = viewmodel_class.new(model_class.lock('FOR SHARE').find(root.id))
213
213
  fetched_result = parse_result(fetch_with_cache(initial_viewmodels: [locked_root_view], locked: true))
214
214
 
215
215
  value(fetched_result).must_equal(serialize_from_database)
216
216
  end
217
+
218
+ it 'handles a stale cached reference' do
219
+ # Fetch to populate the cache
220
+ initial_result = parse_result(fetch_with_cache)
221
+ value(initial_result).must_equal(serialize_from_database)
222
+
223
+ # Destroy the shared child and its cache without invalidating the cached parent cache
224
+ root.update_columns(shared_id: nil)
225
+ shared.destroy!
226
+ shared_viewmodel_class.viewmodel_cache.clear
227
+
228
+ fetched_result = parse_result(fetch_with_cache)
229
+ value(fetched_result).must_equal(serialize_from_database)
230
+ fetched_view = fetched_result.first.first
231
+ value(fetched_view['shared']).must_be_nil
232
+ end
217
233
  end
218
234
 
219
235
  describe 'with migrations' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iknow_view_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - iKnow Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-23 00:00:00.000000000 Z
11
+ date: 2024-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack