iknow_view_models 3.1.6 → 3.2.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 +4 -4
- data/.circleci/config.yml +6 -6
- data/.rubocop.yml +18 -0
- data/Appraisals +6 -6
- data/Gemfile +6 -2
- data/Rakefile +5 -5
- data/gemfiles/rails_5_2.gemfile +5 -5
- data/gemfiles/rails_6_0.gemfile +9 -0
- data/iknow_view_models.gemspec +40 -38
- data/lib/iknow_view_models.rb +9 -7
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model.rb +31 -17
- data/lib/view_model/access_control.rb +5 -2
- data/lib/view_model/access_control/composed.rb +10 -9
- data/lib/view_model/access_control/open.rb +2 -0
- data/lib/view_model/access_control/read_only.rb +2 -0
- data/lib/view_model/access_control/tree.rb +11 -6
- data/lib/view_model/access_control_error.rb +4 -1
- data/lib/view_model/active_record.rb +13 -12
- data/lib/view_model/active_record/association_data.rb +3 -2
- data/lib/view_model/active_record/association_manipulation.rb +6 -4
- data/lib/view_model/active_record/cache.rb +114 -34
- data/lib/view_model/active_record/cache/cacheable_view.rb +2 -2
- data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
- data/lib/view_model/active_record/controller.rb +68 -1
- data/lib/view_model/active_record/controller_base.rb +4 -1
- data/lib/view_model/active_record/nested_controller_base.rb +1 -0
- data/lib/view_model/active_record/update_context.rb +8 -6
- data/lib/view_model/active_record/update_data.rb +32 -30
- data/lib/view_model/active_record/update_operation.rb +17 -13
- data/lib/view_model/active_record/visitor.rb +0 -1
- data/lib/view_model/after_transaction_runner.rb +2 -2
- data/lib/view_model/callbacks.rb +3 -1
- data/lib/view_model/controller.rb +13 -3
- data/lib/view_model/deserialization_error.rb +15 -12
- data/lib/view_model/error.rb +12 -10
- data/lib/view_model/error_view.rb +3 -1
- data/lib/view_model/migratable_view.rb +78 -0
- data/lib/view_model/migration.rb +48 -0
- data/lib/view_model/migration/no_path_error.rb +26 -0
- data/lib/view_model/migration/one_way_error.rb +24 -0
- data/lib/view_model/migration/unspecified_version_error.rb +24 -0
- data/lib/view_model/migrator.rb +108 -0
- data/lib/view_model/record.rb +15 -14
- data/lib/view_model/reference.rb +3 -1
- data/lib/view_model/references.rb +8 -5
- data/lib/view_model/registry.rb +1 -1
- data/lib/view_model/schemas.rb +9 -4
- data/lib/view_model/serialization_error.rb +4 -1
- data/lib/view_model/serialize_context.rb +4 -4
- data/lib/view_model/test_helpers.rb +8 -3
- data/lib/view_model/test_helpers/arvm_builder.rb +21 -15
- data/lib/view_model/traversal_context.rb +2 -1
- data/nix/dependencies.nix +5 -0
- data/nix/gem/generate.rb +2 -1
- data/shell.nix +8 -3
- data/test/.rubocop.yml +14 -0
- data/test/helpers/arvm_test_models.rb +12 -9
- data/test/helpers/arvm_test_utilities.rb +5 -3
- data/test/helpers/controller_test_helpers.rb +55 -32
- data/test/helpers/match_enumerator.rb +1 -0
- data/test/helpers/query_logging.rb +2 -1
- data/test/helpers/test_access_control.rb +5 -3
- data/test/helpers/viewmodel_spec_helpers.rb +88 -22
- data/test/unit/view_model/access_control_test.rb +144 -144
- data/test/unit/view_model/active_record/alias_test.rb +15 -13
- data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
- data/test/unit/view_model/active_record/cache_test.rb +68 -31
- data/test/unit/view_model/active_record/cloner_test.rb +67 -63
- data/test/unit/view_model/active_record/controller_test.rb +113 -65
- data/test/unit/view_model/active_record/counter_test.rb +10 -9
- data/test/unit/view_model/active_record/customization_test.rb +59 -58
- data/test/unit/view_model/active_record/has_many_test.rb +112 -111
- data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
- data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
- data/test/unit/view_model/active_record/has_one_test.rb +37 -36
- data/test/unit/view_model/active_record/migration_test.rb +161 -0
- data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
- data/test/unit/view_model/active_record/poly_test.rb +44 -45
- data/test/unit/view_model/active_record/shared_test.rb +30 -28
- data/test/unit/view_model/active_record/version_test.rb +9 -7
- data/test/unit/view_model/active_record_test.rb +72 -72
- data/test/unit/view_model/callbacks_test.rb +19 -15
- data/test/unit/view_model/controller_test.rb +4 -2
- data/test/unit/view_model/record_test.rb +92 -97
- data/test/unit/view_model/traversal_context_test.rb +4 -5
- data/test/unit/view_model_test.rb +18 -16
- metadata +36 -12
- data/.travis.yml +0 -31
- data/appveyor.yml +0 -22
- data/gemfiles/rails_6_0_beta.gemfile +0 -9
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
## Defines an access control discipline for a given action against a tree of
|
2
4
|
## viewmodels.
|
3
5
|
##
|
@@ -78,7 +80,7 @@ class ViewModel::AccessControl::Tree < ViewModel::AccessControl
|
|
78
80
|
def initialize
|
79
81
|
super()
|
80
82
|
@always_policy_instance = self.class::AlwaysPolicy.new(self)
|
81
|
-
@view_policy_instances = self.class.view_policies.
|
83
|
+
@view_policy_instances = self.class.view_policies.transform_values { |policy| policy.new(self) }
|
82
84
|
@root_visibility_store = {}
|
83
85
|
@root_editability_store = {}
|
84
86
|
end
|
@@ -98,27 +100,29 @@ class ViewModel::AccessControl::Tree < ViewModel::AccessControl
|
|
98
100
|
|
99
101
|
def store_descendent_editability(view, descendent_editability)
|
100
102
|
if @root_editability_store.has_key?(view.object_id)
|
101
|
-
raise RuntimeError.new(
|
103
|
+
raise RuntimeError.new('Root access control data already saved for root')
|
102
104
|
end
|
105
|
+
|
103
106
|
@root_editability_store[view.object_id] = descendent_editability
|
104
107
|
end
|
105
108
|
|
106
109
|
def fetch_descendent_editability(view)
|
107
110
|
@root_editability_store.fetch(view.object_id) do
|
108
|
-
raise RuntimeError.new(
|
111
|
+
raise RuntimeError.new('No root access control data recorded for root')
|
109
112
|
end
|
110
113
|
end
|
111
114
|
|
112
115
|
def store_descendent_visibility(view, descendent_visibility)
|
113
116
|
if @root_visibility_store.has_key?(view.object_id)
|
114
|
-
raise RuntimeError.new(
|
117
|
+
raise RuntimeError.new('Root access control data already saved for root')
|
115
118
|
end
|
119
|
+
|
116
120
|
@root_visibility_store[view.object_id] = descendent_visibility
|
117
121
|
end
|
118
122
|
|
119
123
|
def fetch_descendent_visibility(view)
|
120
124
|
@root_visibility_store.fetch(view.object_id) do
|
121
|
-
raise RuntimeError.new(
|
125
|
+
raise RuntimeError.new('No root access control data recorded for root')
|
122
126
|
end
|
123
127
|
end
|
124
128
|
|
@@ -152,6 +156,7 @@ class ViewModel::AccessControl::Tree < ViewModel::AccessControl
|
|
152
156
|
|
153
157
|
def initialize_as_node
|
154
158
|
@root = false
|
159
|
+
|
155
160
|
@root_children_editable_ifs = []
|
156
161
|
@root_children_editable_unlesses = []
|
157
162
|
@root_children_visible_ifs = []
|
@@ -187,7 +192,7 @@ class ViewModel::AccessControl::Tree < ViewModel::AccessControl
|
|
187
192
|
def inspect_checks
|
188
193
|
checks = super
|
189
194
|
if root?
|
190
|
-
checks <<
|
195
|
+
checks << 'no root checks'
|
191
196
|
else
|
192
197
|
checks << "root_children_visible_if: #{root_children_visible_ifs.map(&:reason)}" if root_children_visible_ifs.present?
|
193
198
|
checks << "root_children_visible_unless: #{root_children_visible_unlesses.map(&:reason)}" if root_children_visible_unlesses.present?
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ViewModel::AccessControlError < ViewModel::AbstractErrorWithBlame
|
2
4
|
attr_reader :detail
|
5
|
+
|
3
6
|
status 403
|
4
|
-
code
|
7
|
+
code 'AccessControl.Forbidden'
|
5
8
|
|
6
9
|
def initialize(detail, nodes = [])
|
7
10
|
@detail = detail
|
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
6
|
+
require 'view_model'
|
7
|
+
require 'view_model/record'
|
8
8
|
|
9
|
-
require
|
10
|
-
require
|
9
|
+
require 'lazily'
|
10
|
+
require 'concurrent'
|
11
11
|
|
12
12
|
class ViewModel::ActiveRecord < ViewModel::Record
|
13
13
|
# Defined before requiring components so components can refer to them at parse time
|
14
14
|
|
15
15
|
# for functional updates
|
16
|
-
FUNCTIONAL_UPDATE_TYPE =
|
17
|
-
ACTIONS_ATTRIBUTE =
|
18
|
-
VALUES_ATTRIBUTE =
|
19
|
-
BEFORE_ATTRIBUTE =
|
20
|
-
AFTER_ATTRIBUTE =
|
16
|
+
FUNCTIONAL_UPDATE_TYPE = '_update'
|
17
|
+
ACTIONS_ATTRIBUTE = 'actions'
|
18
|
+
VALUES_ATTRIBUTE = 'values'
|
19
|
+
BEFORE_ATTRIBUTE = 'before'
|
20
|
+
AFTER_ATTRIBUTE = 'after'
|
21
21
|
|
22
22
|
require 'view_model/utils/collections'
|
23
23
|
require 'view_model/active_record/association_data'
|
@@ -232,6 +232,7 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
232
232
|
_members.each_value do |data|
|
233
233
|
next unless data.is_a?(AssociationData)
|
234
234
|
next unless include_referenced || !data.referenced?
|
235
|
+
|
235
236
|
data.viewmodel_classes.each do |vm|
|
236
237
|
vm.dependent_viewmodels(seen, include_referenced: include_referenced)
|
237
238
|
end
|
@@ -245,7 +246,7 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
245
246
|
begin
|
246
247
|
dependent_viewmodels(include_referenced: include_referenced).each_with_object({}) do |view, h|
|
247
248
|
h[view.view_name] = view.schema_version
|
248
|
-
end
|
249
|
+
end.freeze
|
249
250
|
end
|
250
251
|
end
|
251
252
|
|
@@ -17,7 +17,7 @@ class ViewModel::ActiveRecord::AssociationData
|
|
17
17
|
|
18
18
|
@direct_reflection = owner.model_class.reflect_on_association(direct_association_name)
|
19
19
|
if @direct_reflection.nil?
|
20
|
-
raise InvalidAssociation.new("Association '#{direct_association_name}' not found in model '#{model_class.name}'")
|
20
|
+
raise InvalidAssociation.new("Association '#{direct_association_name}' not found in model '#{owner.model_class.name}'")
|
21
21
|
end
|
22
22
|
|
23
23
|
@indirect_association_name = indirect_association_name
|
@@ -42,7 +42,7 @@ class ViewModel::ActiveRecord::AssociationData
|
|
42
42
|
@indirect_reflection = load_indirect_reflection(intermediate_model, @indirect_association_name)
|
43
43
|
target_reflection = @indirect_reflection
|
44
44
|
else
|
45
|
-
target_reflection
|
45
|
+
target_reflection = @direct_reflection
|
46
46
|
end
|
47
47
|
|
48
48
|
@viewmodel_classes =
|
@@ -202,6 +202,7 @@ class ViewModel::ActiveRecord::AssociationData
|
|
202
202
|
|
203
203
|
def direct_viewmodel
|
204
204
|
raise ArgumentError.new('not a through association') unless through?
|
205
|
+
|
205
206
|
lazy_initialize! unless @initialized
|
206
207
|
@direct_viewmodel
|
207
208
|
end
|
@@ -11,7 +11,8 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
11
11
|
association_scope = association.scope
|
12
12
|
|
13
13
|
if association_data.through?
|
14
|
-
raise ArgumentError.new(
|
14
|
+
raise ArgumentError.new('Polymorphic through relationships not supported yet') if association_data.polymorphic?
|
15
|
+
|
15
16
|
associated_viewmodel = association_data.viewmodel_class
|
16
17
|
direct_viewmodel = association_data.direct_viewmodel
|
17
18
|
else
|
@@ -44,6 +45,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
44
45
|
if vms.size > 1
|
45
46
|
raise ViewModel::DeserializationError::Internal.new("Internal error: encountered multiple records for single association #{association_name}", self.blame_reference)
|
46
47
|
end
|
48
|
+
|
47
49
|
vms.first
|
48
50
|
end
|
49
51
|
end
|
@@ -112,7 +114,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
112
114
|
deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeValidate, self)
|
113
115
|
|
114
116
|
if association_data.through?
|
115
|
-
raise ArgumentError.new(
|
117
|
+
raise ArgumentError.new('Polymorphic through relationships not supported yet') if association_data.polymorphic?
|
116
118
|
|
117
119
|
direct_viewmodel_class = association_data.direct_viewmodel
|
118
120
|
root_update_data, referenced_update_data = construct_indirect_append_updates(association_data, subtree_hashes, references)
|
@@ -317,7 +319,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
317
319
|
# TODO: this won't handle polymorphic associations! In the case of polymorphism,
|
318
320
|
# need to join on (type, id) pairs instead.
|
319
321
|
if association_data.polymorphic?
|
320
|
-
raise ArgumentError.new(
|
322
|
+
raise ArgumentError.new('Internal error: append_association is not yet supported for polymorphic indirect associations')
|
321
323
|
end
|
322
324
|
|
323
325
|
existing_indirect_associates = indirect_update_data.map { |upd| upd.id unless upd.new? }.compact
|
@@ -340,7 +342,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
340
342
|
ViewModel::ActiveRecord::UpdateData.new(
|
341
343
|
direct_viewmodel_class,
|
342
344
|
metadata,
|
343
|
-
{ indirect_reflection.name.to_s => { ViewModel::REFERENCE_ATTRIBUTE => ref_name }},
|
345
|
+
{ indirect_reflection.name.to_s => { ViewModel::REFERENCE_ATTRIBUTE => ref_name } },
|
344
346
|
[ref_name])
|
345
347
|
end
|
346
348
|
|
@@ -5,6 +5,7 @@ require 'iknow_cache'
|
|
5
5
|
# Cache for ViewModels that wrap ActiveRecord models.
|
6
6
|
class ViewModel::ActiveRecord::Cache
|
7
7
|
require 'view_model/active_record/cache/cacheable_view'
|
8
|
+
require 'view_model/migrator'
|
8
9
|
|
9
10
|
class UncacheableViewModelError < RuntimeError; end
|
10
11
|
|
@@ -13,13 +14,20 @@ class ViewModel::ActiveRecord::Cache
|
|
13
14
|
# If cache_group: is specified, it must be a group of a single key: `:id`
|
14
15
|
def initialize(viewmodel_class, cache_group: nil)
|
15
16
|
@viewmodel_class = viewmodel_class
|
16
|
-
|
17
|
+
|
18
|
+
@cache_group = cache_group || create_default_cache_group
|
19
|
+
@migrated_cache_group = @cache_group.register_child_group(:migrated, :version)
|
20
|
+
|
21
|
+
# /viewname/:id/viewname-currentversion
|
17
22
|
@cache = @cache_group.register_cache(cache_name)
|
23
|
+
|
24
|
+
# /viewname/:id/migrated/:oldversion/viewname-currentversion
|
25
|
+
@migrated_cache = @migrated_cache_group.register_cache(cache_name)
|
18
26
|
end
|
19
27
|
|
20
28
|
def delete(*ids)
|
21
29
|
ids.each do |id|
|
22
|
-
@cache_group.delete_all(
|
30
|
+
@cache_group.delete_all(@cache.key.new(id))
|
23
31
|
end
|
24
32
|
end
|
25
33
|
|
@@ -27,14 +35,14 @@ class ViewModel::ActiveRecord::Cache
|
|
27
35
|
@cache_group.invalidate_cache_group
|
28
36
|
end
|
29
37
|
|
30
|
-
def fetch_by_viewmodel(viewmodels, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
|
38
|
+
def fetch_by_viewmodel(viewmodels, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
|
31
39
|
ids = viewmodels.map(&:id)
|
32
|
-
fetch(ids, initial_viewmodels: viewmodels, locked:
|
40
|
+
fetch(ids, initial_viewmodels: viewmodels, migration_versions: migration_versions, locked: locked, serialize_context: serialize_context)
|
33
41
|
end
|
34
42
|
|
35
|
-
def fetch(ids, initial_viewmodels: nil, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
|
43
|
+
def fetch(ids, initial_viewmodels: nil, migration_versions: {}, locked: false, serialize_context: @viewmodel_class.new_serialize_context)
|
36
44
|
data_serializations = Array.new(ids.size)
|
37
|
-
worker = CacheWorker.new(serialize_context: serialize_context)
|
45
|
+
worker = CacheWorker.new(migration_versions: migration_versions, serialize_context: serialize_context)
|
38
46
|
|
39
47
|
# If initial root viewmodels were provided, visit them to ensure that they
|
40
48
|
# are visible. Other than this, no traversal callbacks are performed, as a
|
@@ -98,12 +106,14 @@ class ViewModel::ActiveRecord::Cache
|
|
98
106
|
SENTINEL = Object.new
|
99
107
|
WorklistEntry = Struct.new(:ref_name, :viewmodel)
|
100
108
|
|
101
|
-
attr_reader :serialize_context, :resolved_references
|
109
|
+
attr_reader :migration_versions, :serialize_context, :resolved_references
|
102
110
|
|
103
|
-
def initialize(serialize_context:)
|
104
|
-
@worklist
|
105
|
-
@resolved_references
|
106
|
-
@
|
111
|
+
def initialize(migration_versions:, serialize_context:)
|
112
|
+
@worklist = {}
|
113
|
+
@resolved_references = {}
|
114
|
+
@migration_versions = migration_versions
|
115
|
+
@migrated_cache_versions = {}
|
116
|
+
@serialize_context = serialize_context
|
107
117
|
end
|
108
118
|
|
109
119
|
def resolve_references!
|
@@ -141,11 +151,15 @@ class ViewModel::ActiveRecord::Cache
|
|
141
151
|
end
|
142
152
|
end
|
143
153
|
|
154
|
+
def migrated_cache_version(viewmodel_cache)
|
155
|
+
@migrated_cache_versions[viewmodel_cache] ||= viewmodel_cache.migrated_cache_version(migration_versions)
|
156
|
+
end
|
157
|
+
|
144
158
|
# Loads the specified entities from the cache and returns a hash of
|
145
159
|
# {id=>serialized_view}. Any references encountered are added to the
|
146
160
|
# worklist.
|
147
161
|
def load_from_cache(viewmodel_cache, ids)
|
148
|
-
cached_serializations = viewmodel_cache.load(ids, serialize_context: serialize_context)
|
162
|
+
cached_serializations = viewmodel_cache.load(ids, migrated_cache_version(viewmodel_cache), serialize_context: serialize_context)
|
149
163
|
|
150
164
|
cached_serializations.each_with_object({}) do |(id, cached_serialization), result|
|
151
165
|
add_refs_to_worklist(cached_serialization[:ref_cache])
|
@@ -159,17 +173,54 @@ class ViewModel::ActiveRecord::Cache
|
|
159
173
|
# added to the worklist.
|
160
174
|
def serialize_and_cache(viewmodels)
|
161
175
|
viewmodels.each_with_object({}) do |viewmodel, result|
|
162
|
-
|
176
|
+
builder = Jbuilder.new do |json|
|
163
177
|
ViewModel.serialize(viewmodel, json, serialize_context: serialize_context)
|
164
178
|
end
|
165
179
|
|
166
180
|
referenced_viewmodels = serialize_context.extract_referenced_views!
|
181
|
+
|
182
|
+
if migration_versions.present?
|
183
|
+
migrator = ViewModel::DownMigrator.new(migration_versions)
|
184
|
+
|
185
|
+
# This migration isn't given the chance to inspect/alter the contents
|
186
|
+
# of referenced views, only their presence: it's strictly less
|
187
|
+
# powerful than migrations on a fully serialized tree, as the only
|
188
|
+
# possible action on a referenced child is to delete it. The effect of
|
189
|
+
# this is that for sufficiently complex migrations where a parent view
|
190
|
+
# must introduce children or alter the contents of its referenced
|
191
|
+
# children, we may have to avoid caching while the migration is in
|
192
|
+
# use.
|
193
|
+
dummy_references = referenced_viewmodels.transform_values do |ref_vm|
|
194
|
+
{
|
195
|
+
ViewModel::TYPE_ATTRIBUTE => ref_vm.class.view_name,
|
196
|
+
ViewModel::VERSION_ATTRIBUTE => ref_vm.class.schema_version,
|
197
|
+
ViewModel::ID_ATTRIBUTE => ref_vm.id,
|
198
|
+
}.freeze
|
199
|
+
end
|
200
|
+
|
201
|
+
migrator.migrate!(builder.attributes!, references: dummy_references)
|
202
|
+
|
203
|
+
# Removed dummy references can be removed from referenced_viewmodels.
|
204
|
+
referenced_viewmodels.keep_if { |k, _| dummy_references.has_key?(k) }
|
205
|
+
|
206
|
+
# Introduced dummy references cannot be handled.
|
207
|
+
if dummy_references.keys != referenced_viewmodels.keys
|
208
|
+
version = migration_versions[viewmodel.class]
|
209
|
+
raise ViewModel::Error.new(
|
210
|
+
status: 500,
|
211
|
+
detail: "Down-migration for cacheable view #{viewmodel.class} to v#{version} must not introduce new shared references")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
data_serialization = builder.target!
|
216
|
+
|
167
217
|
add_viewmodels_to_worklist(referenced_viewmodels)
|
168
218
|
|
169
219
|
if viewmodel.class < CacheableView
|
170
220
|
cacheable_references = referenced_viewmodels.transform_values { |vm| cacheable_reference(vm) }
|
171
|
-
viewmodel.class.viewmodel_cache
|
172
|
-
|
221
|
+
target_cache = viewmodel.class.viewmodel_cache
|
222
|
+
target_cache.store(viewmodel.id, migrated_cache_version(target_cache), data_serialization, cacheable_references,
|
223
|
+
serialize_context: serialize_context)
|
173
224
|
end
|
174
225
|
|
175
226
|
result[viewmodel.id] = data_serialization
|
@@ -192,14 +243,14 @@ class ViewModel::ActiveRecord::Cache
|
|
192
243
|
if ids.present?
|
193
244
|
found = viewmodel_class.find(ids,
|
194
245
|
eager_include: false,
|
195
|
-
lock:
|
246
|
+
lock: 'FOR SHARE',
|
196
247
|
serialize_context: serialize_context)
|
197
248
|
viewmodels.concat(found)
|
198
249
|
end
|
199
250
|
|
200
251
|
ViewModel.preload_for_serialization(viewmodels,
|
201
252
|
include_referenced: false,
|
202
|
-
lock:
|
253
|
+
lock: 'FOR SHARE',
|
203
254
|
serialize_context: serialize_context)
|
204
255
|
|
205
256
|
viewmodels
|
@@ -213,6 +264,7 @@ class ViewModel::ActiveRecord::Cache
|
|
213
264
|
def add_refs_to_worklist(cacheable_references)
|
214
265
|
cacheable_references.each do |ref_name, (type, id)|
|
215
266
|
next if resolved_references.has_key?(ref_name)
|
267
|
+
|
216
268
|
(@worklist[type] ||= {})[id] = WorklistEntry.new(ref_name, nil)
|
217
269
|
end
|
218
270
|
end
|
@@ -220,13 +272,26 @@ class ViewModel::ActiveRecord::Cache
|
|
220
272
|
def add_viewmodels_to_worklist(referenced_viewmodels)
|
221
273
|
referenced_viewmodels.each do |ref_name, viewmodel|
|
222
274
|
next if resolved_references.has_key?(ref_name)
|
275
|
+
|
223
276
|
(@worklist[viewmodel.class.view_name] ||= {})[viewmodel.id] = WorklistEntry.new(ref_name, viewmodel)
|
224
277
|
end
|
225
278
|
end
|
226
279
|
end
|
227
280
|
|
228
|
-
def
|
229
|
-
|
281
|
+
def cache_for(migration_version)
|
282
|
+
if migration_version
|
283
|
+
@migrated_cache
|
284
|
+
else
|
285
|
+
@cache
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def key_for(id, migration_version)
|
290
|
+
if migration_version
|
291
|
+
@migrated_cache.key.new(id, migration_version)
|
292
|
+
else
|
293
|
+
@cache.key.new(id)
|
294
|
+
end
|
230
295
|
end
|
231
296
|
|
232
297
|
def id_for(key)
|
@@ -234,32 +299,47 @@ class ViewModel::ActiveRecord::Cache
|
|
234
299
|
end
|
235
300
|
|
236
301
|
# Save the provided serialization and reference data in the cache
|
237
|
-
def store(id, data_serialization, ref_cache, serialize_context:)
|
238
|
-
|
302
|
+
def store(id, migration_version, data_serialization, ref_cache, serialize_context:)
|
303
|
+
key = key_for(id, migration_version)
|
304
|
+
cache_for(migration_version).write(key, { data: data_serialization, ref_cache: ref_cache })
|
239
305
|
end
|
240
306
|
|
241
|
-
def load(ids, serialize_context:)
|
242
|
-
keys = ids.map { |id| key_for(id) }
|
243
|
-
results =
|
307
|
+
def load(ids, migration_version, serialize_context:)
|
308
|
+
keys = ids.map { |id| key_for(id, migration_version) }
|
309
|
+
results = cache_for(migration_version).read_multi(keys)
|
244
310
|
results.transform_keys! { |key| id_for(key) }
|
245
311
|
end
|
246
312
|
|
247
|
-
|
313
|
+
def cache_version
|
314
|
+
@cache_version ||=
|
315
|
+
begin
|
316
|
+
versions = @viewmodel_class.deep_schema_version(include_referenced: false)
|
317
|
+
ViewModel.schema_hash(versions)
|
318
|
+
end
|
319
|
+
end
|
248
320
|
|
249
|
-
|
321
|
+
def migrated_cache_version(migration_versions)
|
322
|
+
versions = ViewModel::Migrator.migrated_deep_schema_version(viewmodel_class, migration_versions, include_referenced: false)
|
323
|
+
version_hash = ViewModel.schema_hash(versions)
|
324
|
+
|
325
|
+
if version_hash == cache_version
|
326
|
+
# no migrations affect this view
|
327
|
+
nil
|
328
|
+
else
|
329
|
+
version_hash
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
250
334
|
|
251
335
|
def create_default_cache_group
|
252
336
|
IknowCache.register_group(@viewmodel_class.name, :id)
|
253
337
|
end
|
254
338
|
|
255
|
-
# Statically version the
|
256
|
-
# the constituent viewmodels, so that viewmodel changes force
|
339
|
+
# Statically version the cache name based on the (current) deep schema
|
340
|
+
# versions of the constituent viewmodels, so that viewmodel changes force
|
341
|
+
# invalidation.
|
257
342
|
def cache_name
|
258
|
-
"#{
|
259
|
-
end
|
260
|
-
|
261
|
-
def cache_version
|
262
|
-
version_string = @viewmodel_class.deep_schema_version(include_referenced: false).to_a.sort.join(',')
|
263
|
-
Base64.urlsafe_encode64(Digest::MD5.digest(version_string))
|
343
|
+
"vmcache_#{cache_version}"
|
264
344
|
end
|
265
345
|
end
|