iknow_view_models 3.2.0 → 3.2.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 +4 -4
- data/.rubocop.yml +13 -0
- data/Appraisals +6 -6
- data/Rakefile +5 -5
- data/gemfiles/rails_5_2.gemfile +5 -5
- data/gemfiles/rails_6_0.gemfile +5 -5
- data/iknow_view_models.gemspec +40 -39
- data/lib/iknow_view_models.rb +9 -7
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model.rb +17 -14
- 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 +12 -11
- data/lib/view_model/active_record/association_data.rb +2 -1
- data/lib/view_model/active_record/association_manipulation.rb +6 -4
- data/lib/view_model/active_record/cache.rb +4 -2
- data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
- 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 +0 -1
- 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/migration/no_path_error.rb +1 -0
- data/lib/view_model/migration/one_way_error.rb +1 -0
- data/lib/view_model/migration/unspecified_version_error.rb +1 -0
- data/lib/view_model/record.rb +11 -13
- 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 +19 -14
- data/lib/view_model/traversal_context.rb +2 -1
- 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 +31 -29
- 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 +21 -20
- 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 +27 -26
- data/test/unit/view_model/active_record/cloner_test.rb +67 -63
- data/test/unit/view_model/active_record/controller_test.rb +37 -38
- 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 +13 -13
- 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 +7 -5
@@ -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
|
@@ -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
|
|
@@ -243,14 +243,14 @@ class ViewModel::ActiveRecord::Cache
|
|
243
243
|
if ids.present?
|
244
244
|
found = viewmodel_class.find(ids,
|
245
245
|
eager_include: false,
|
246
|
-
lock:
|
246
|
+
lock: 'FOR SHARE',
|
247
247
|
serialize_context: serialize_context)
|
248
248
|
viewmodels.concat(found)
|
249
249
|
end
|
250
250
|
|
251
251
|
ViewModel.preload_for_serialization(viewmodels,
|
252
252
|
include_referenced: false,
|
253
|
-
lock:
|
253
|
+
lock: 'FOR SHARE',
|
254
254
|
serialize_context: serialize_context)
|
255
255
|
|
256
256
|
viewmodels
|
@@ -264,6 +264,7 @@ class ViewModel::ActiveRecord::Cache
|
|
264
264
|
def add_refs_to_worklist(cacheable_references)
|
265
265
|
cacheable_references.each do |ref_name, (type, id)|
|
266
266
|
next if resolved_references.has_key?(ref_name)
|
267
|
+
|
267
268
|
(@worklist[type] ||= {})[id] = WorklistEntry.new(ref_name, nil)
|
268
269
|
end
|
269
270
|
end
|
@@ -271,6 +272,7 @@ class ViewModel::ActiveRecord::Cache
|
|
271
272
|
def add_viewmodels_to_worklist(referenced_viewmodels)
|
272
273
|
referenced_viewmodels.each do |ref_name, viewmodel|
|
273
274
|
next if resolved_references.has_key?(ref_name)
|
275
|
+
|
274
276
|
(@worklist[viewmodel.class.view_name] ||= {})[viewmodel.id] = WorklistEntry.new(ref_name, viewmodel)
|
275
277
|
end
|
276
278
|
end
|
@@ -47,7 +47,7 @@ module ViewModel::ActiveRecord::CollectionNestedController
|
|
47
47
|
after = parse_relative_position(:after)
|
48
48
|
|
49
49
|
if before && after
|
50
|
-
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
50
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new('Can not provide both `before` and `after` anchors for a collection append')
|
51
51
|
end
|
52
52
|
|
53
53
|
owner_view = owner_viewmodel.find(owner_viewmodel_id, eager_include: false, serialize_context: serialize_context)
|
@@ -55,8 +55,8 @@ module ViewModel::ActiveRecord::CollectionNestedController
|
|
55
55
|
assoc_view = owner_view.append_associated(association_name,
|
56
56
|
update_hash,
|
57
57
|
references: refs,
|
58
|
-
before:
|
59
|
-
after:
|
58
|
+
before: before,
|
59
|
+
after: after,
|
60
60
|
deserialize_context: deserialize_context)
|
61
61
|
ViewModel::Callbacks.wrap_serialize(owner_view, context: serialize_context) do
|
62
62
|
child_context = owner_view.context_for_child(association_name, context: serialize_context)
|
@@ -33,7 +33,7 @@ module ViewModel::ActiveRecord::ControllerBase
|
|
33
33
|
if (match = /(.*)Controller$/.match(self.name))
|
34
34
|
self.viewmodel_name = match[1].singularize
|
35
35
|
else
|
36
|
-
raise ArgumentError.new("Could not auto-determine ViewModel from Controller name '#{self.name}'")
|
36
|
+
raise ArgumentError.new("Could not auto-determine ViewModel from Controller name '#{self.name}'")
|
37
37
|
end
|
38
38
|
end
|
39
39
|
@viewmodel_class
|
@@ -43,6 +43,7 @@ module ViewModel::ActiveRecord::ControllerBase
|
|
43
43
|
unless instance_variable_defined?(:@access_control)
|
44
44
|
raise ArgumentError.new("AccessControl instance not set for Controller '#{self.name}'")
|
45
45
|
end
|
46
|
+
|
46
47
|
@access_control
|
47
48
|
end
|
48
49
|
|
@@ -64,6 +65,7 @@ module ViewModel::ActiveRecord::ControllerBase
|
|
64
65
|
unless type < ViewModel
|
65
66
|
raise ArgumentError.new("'#{type.inspect}' is not a valid ViewModel")
|
66
67
|
end
|
68
|
+
|
67
69
|
@viewmodel_class = type
|
68
70
|
end
|
69
71
|
|
@@ -75,6 +77,7 @@ module ViewModel::ActiveRecord::ControllerBase
|
|
75
77
|
unless access_control.is_a?(Class) && access_control < ViewModel::AccessControl
|
76
78
|
raise ArgumentError.new("'#{access_control.inspect}' is not a valid AccessControl")
|
77
79
|
end
|
80
|
+
|
78
81
|
@access_control = access_control
|
79
82
|
end
|
80
83
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Assembles an update operation tree from user input. Handles the interlinking
|
2
4
|
# and model of update operations, but does not handle the actual user data nor
|
3
5
|
# the mechanism by which it is applied to models.
|
@@ -66,7 +68,7 @@ class ViewModel::ActiveRecord
|
|
66
68
|
.assemble_update_tree
|
67
69
|
end
|
68
70
|
|
69
|
-
# TODO an unfortunate abstraction violation. The `append` case constructs an
|
71
|
+
# TODO: an unfortunate abstraction violation. The `append` case constructs an
|
70
72
|
# update tree and later injects the context of parent and position.
|
71
73
|
def root_updates
|
72
74
|
@root_update_operations
|
@@ -143,7 +145,7 @@ class ViewModel::ActiveRecord
|
|
143
145
|
if ref.nil?
|
144
146
|
@root_update_operations << update_op
|
145
147
|
else
|
146
|
-
# TODO make sure that referenced subtree hashes are unique and provide a decent error message
|
148
|
+
# TODO: make sure that referenced subtree hashes are unique and provide a decent error message
|
147
149
|
# not strictly necessary, but will save confusion
|
148
150
|
@referenced_update_operations[ref] = update_op
|
149
151
|
end
|
@@ -200,9 +202,9 @@ class ViewModel::ActiveRecord
|
|
200
202
|
deferred_update.build!(self)
|
201
203
|
end
|
202
204
|
|
203
|
-
dangling_references = @referenced_update_operations.reject { |
|
205
|
+
dangling_references = @referenced_update_operations.reject { |_ref, upd| upd.built? }.map { |_ref, upd| upd.viewmodel.to_reference }
|
204
206
|
if dangling_references.present?
|
205
|
-
raise ViewModel::DeserializationError::InvalidStructure.new(
|
207
|
+
raise ViewModel::DeserializationError::InvalidStructure.new('References not referred to from roots', dangling_references)
|
206
208
|
end
|
207
209
|
|
208
210
|
self
|
@@ -265,8 +267,8 @@ class ViewModel::ActiveRecord
|
|
265
267
|
# individual node that caused it, without attempting to parse Postgres'
|
266
268
|
# human-readable error details.
|
267
269
|
def check_deferred_constraints!(model_class)
|
268
|
-
if model_class.connection.adapter_name ==
|
269
|
-
model_class.connection.execute(
|
270
|
+
if model_class.connection.adapter_name == 'PostgreSQL'
|
271
|
+
model_class.connection.execute('SET CONSTRAINTS ALL IMMEDIATE')
|
270
272
|
end
|
271
273
|
rescue ::ActiveRecord::StatementInvalid => ex
|
272
274
|
raise ViewModel::DeserializationError::DatabaseConstraint.from_exception(ex)
|
@@ -24,6 +24,7 @@ class ViewModel::ActiveRecord
|
|
24
24
|
NAME = 'append'
|
25
25
|
attr_accessor :before, :after
|
26
26
|
attr_reader :contents
|
27
|
+
|
27
28
|
def initialize(contents)
|
28
29
|
@contents = contents
|
29
30
|
end
|
@@ -32,6 +33,7 @@ class ViewModel::ActiveRecord
|
|
32
33
|
class Update
|
33
34
|
NAME = 'update'
|
34
35
|
attr_reader :contents
|
36
|
+
|
35
37
|
def initialize(contents)
|
36
38
|
@contents = contents
|
37
39
|
end
|
@@ -56,6 +58,7 @@ class ViewModel::ActiveRecord
|
|
56
58
|
# associations or reference strings for root.
|
57
59
|
class Replace
|
58
60
|
attr_reader :contents
|
61
|
+
|
59
62
|
def initialize(contents)
|
60
63
|
@contents = contents
|
61
64
|
end
|
@@ -66,6 +69,7 @@ class ViewModel::ActiveRecord
|
|
66
69
|
# associations.
|
67
70
|
class Functional
|
68
71
|
attr_reader :actions
|
72
|
+
|
69
73
|
def initialize(actions)
|
70
74
|
@actions = actions
|
71
75
|
end
|
@@ -215,23 +219,23 @@ class ViewModel::ActiveRecord
|
|
215
219
|
# The contents of the actions are determined by the subclasses
|
216
220
|
|
217
221
|
def functional_update_schema # abstract
|
218
|
-
raise
|
222
|
+
raise 'abstract'
|
219
223
|
end
|
220
224
|
|
221
225
|
def append_action_schema # abstract
|
222
|
-
raise
|
226
|
+
raise 'abstract'
|
223
227
|
end
|
224
228
|
|
225
229
|
def remove_action_schema # abstract
|
226
|
-
raise
|
230
|
+
raise 'abstract'
|
227
231
|
end
|
228
232
|
|
229
233
|
def update_action_schema # abstract
|
230
|
-
raise
|
234
|
+
raise 'abstract'
|
231
235
|
end
|
232
236
|
|
233
|
-
def parse_contents(
|
234
|
-
raise
|
237
|
+
def parse_contents(_values) # abstract
|
238
|
+
raise 'abstract'
|
235
239
|
end
|
236
240
|
|
237
241
|
# Remove values are always anchors
|
@@ -255,11 +259,11 @@ class ViewModel::ActiveRecord
|
|
255
259
|
# behaviour, so we parameterise the result type as well.
|
256
260
|
|
257
261
|
def replace_update_type # abstract
|
258
|
-
raise
|
262
|
+
raise 'abstract'
|
259
263
|
end
|
260
264
|
|
261
265
|
def functional_update_type # abstract
|
262
|
-
raise
|
266
|
+
raise 'abstract'
|
263
267
|
end
|
264
268
|
end
|
265
269
|
end
|
@@ -390,6 +394,7 @@ class ViewModel::ActiveRecord
|
|
390
394
|
|
391
395
|
class UpdateData
|
392
396
|
attr_accessor :viewmodel_class, :metadata, :attributes, :associations, :referenced_associations
|
397
|
+
|
393
398
|
delegate :id, :view_name, :schema_version, to: :metadata
|
394
399
|
|
395
400
|
module Schemas
|
@@ -400,7 +405,7 @@ class ViewModel::ActiveRecord
|
|
400
405
|
'properties' => { ViewModel::TYPE_ATTRIBUTE => { 'type' => 'string' },
|
401
406
|
ViewModel::ID_ATTRIBUTE => ViewModel::Schemas::ID_SCHEMA },
|
402
407
|
'additionalProperties' => false,
|
403
|
-
'required' => [ViewModel::TYPE_ATTRIBUTE, ViewModel::ID_ATTRIBUTE]
|
408
|
+
'required' => [ViewModel::TYPE_ATTRIBUTE, ViewModel::ID_ATTRIBUTE],
|
404
409
|
}
|
405
410
|
|
406
411
|
VIEWMODEL_REFERENCE_ONLY = JsonSchema.parse!(viewmodel_reference_only)
|
@@ -409,14 +414,14 @@ class ViewModel::ActiveRecord
|
|
409
414
|
{
|
410
415
|
'description' => 'functional update',
|
411
416
|
'type' => 'object',
|
417
|
+
'required' => [ViewModel::TYPE_ATTRIBUTE, VALUES_ATTRIBUTE],
|
412
418
|
'properties' => {
|
413
419
|
ViewModel::TYPE_ATTRIBUTE => { 'enum' => [FunctionalUpdate::Append::NAME,
|
414
420
|
FunctionalUpdate::Update::NAME,
|
415
|
-
FunctionalUpdate::Remove::NAME] },
|
421
|
+
FunctionalUpdate::Remove::NAME,] },
|
416
422
|
VALUES_ATTRIBUTE => { 'type' => 'array',
|
417
|
-
'items' => value_schema }
|
423
|
+
'items' => value_schema },
|
418
424
|
},
|
419
|
-
'required' => [ViewModel::TYPE_ATTRIBUTE, VALUES_ATTRIBUTE]
|
420
425
|
}
|
421
426
|
end
|
422
427
|
|
@@ -426,7 +431,7 @@ class ViewModel::ActiveRecord
|
|
426
431
|
'properties' => {
|
427
432
|
ViewModel::TYPE_ATTRIBUTE => { 'enum' => [FunctionalUpdate::Append::NAME] },
|
428
433
|
BEFORE_ATTRIBUTE => viewmodel_reference_only,
|
429
|
-
AFTER_ATTRIBUTE => viewmodel_reference_only
|
434
|
+
AFTER_ATTRIBUTE => viewmodel_reference_only,
|
430
435
|
},
|
431
436
|
}
|
432
437
|
|
@@ -435,7 +440,7 @@ class ViewModel::ActiveRecord
|
|
435
440
|
|
436
441
|
fupdate_shared =
|
437
442
|
fupdate_base.({ 'oneOf' => [ViewModel::Schemas::VIEWMODEL_REFERENCE_SCHEMA,
|
438
|
-
viewmodel_reference_only] })
|
443
|
+
viewmodel_reference_only,] })
|
439
444
|
|
440
445
|
# Referenced updates are special:
|
441
446
|
# - Append requires `_ref` hashes
|
@@ -450,7 +455,7 @@ class ViewModel::ActiveRecord
|
|
450
455
|
'description' => 'collection update',
|
451
456
|
'additionalProperties' => false,
|
452
457
|
'properties' => {
|
453
|
-
ViewModel::TYPE_ATTRIBUTE => { 'enum' => [FunctionalUpdate::Update::NAME] }
|
458
|
+
ViewModel::TYPE_ATTRIBUTE => { 'enum' => [FunctionalUpdate::Update::NAME] },
|
454
459
|
},
|
455
460
|
}
|
456
461
|
|
@@ -471,7 +476,6 @@ class ViewModel::ActiveRecord
|
|
471
476
|
REMOVE_ACTION = JsonSchema.parse!(fupdate_owned.deep_merge(remove_mixin))
|
472
477
|
REFERENCED_REMOVE_ACTION = JsonSchema.parse!(fupdate_shared.deep_merge(remove_mixin))
|
473
478
|
|
474
|
-
|
475
479
|
collection_update = ->(base_schema) do
|
476
480
|
{
|
477
481
|
'type' => 'object',
|
@@ -480,7 +484,7 @@ class ViewModel::ActiveRecord
|
|
480
484
|
'required' => [ViewModel::TYPE_ATTRIBUTE, ACTIONS_ATTRIBUTE],
|
481
485
|
'properties' => {
|
482
486
|
ViewModel::TYPE_ATTRIBUTE => { 'enum' => [FUNCTIONAL_UPDATE_TYPE] },
|
483
|
-
ACTIONS_ATTRIBUTE => { 'type' => 'array', 'items' => base_schema }
|
487
|
+
ACTIONS_ATTRIBUTE => { 'type' => 'array', 'items' => base_schema },
|
484
488
|
# The ACTIONS_ATTRIBUTE could be accurately expressed as
|
485
489
|
#
|
486
490
|
# { 'oneOf' => [append, update, remove] }
|
@@ -488,7 +492,7 @@ class ViewModel::ActiveRecord
|
|
488
492
|
# but this produces completely unusable error messages. Instead we
|
489
493
|
# specify it must be an array, and defer checking to the code that
|
490
494
|
# can determine the schema by inspecting the type field.
|
491
|
-
}
|
495
|
+
},
|
492
496
|
}
|
493
497
|
end
|
494
498
|
|
@@ -503,7 +507,7 @@ class ViewModel::ActiveRecord
|
|
503
507
|
when :_type
|
504
508
|
viewmodel_class.view_name
|
505
509
|
else
|
506
|
-
attributes.fetch(name) { associations.fetch(name) { referenced_associations.fetch(name) }}
|
510
|
+
attributes.fetch(name) { associations.fetch(name) { referenced_associations.fetch(name) } }
|
507
511
|
end
|
508
512
|
end
|
509
513
|
|
@@ -538,7 +542,7 @@ class ViewModel::ActiveRecord
|
|
538
542
|
end
|
539
543
|
|
540
544
|
# Ensure that no root is referred to more than once
|
541
|
-
check_duplicate_updates(root_updates, type:
|
545
|
+
check_duplicate_updates(root_updates, type: 'root')
|
542
546
|
|
543
547
|
# Construct reference UpdateData
|
544
548
|
referenced_updates = referenced_subtree_hashes.transform_values do |subtree_hash|
|
@@ -549,7 +553,7 @@ class ViewModel::ActiveRecord
|
|
549
553
|
UpdateData.new(viewmodel_class, metadata, subtree_hash, valid_reference_keys)
|
550
554
|
end
|
551
555
|
|
552
|
-
check_duplicate_updates(referenced_updates.values, type:
|
556
|
+
check_duplicate_updates(referenced_updates.values, type: 'reference')
|
553
557
|
|
554
558
|
return root_updates, referenced_updates
|
555
559
|
end
|
@@ -614,7 +618,7 @@ class ViewModel::ActiveRecord
|
|
614
618
|
def preload_dependencies
|
615
619
|
deps = {}
|
616
620
|
|
617
|
-
|
621
|
+
associations.merge(referenced_associations).each do |assoc_name, reference|
|
618
622
|
association_data = self.viewmodel_class._association_data(assoc_name)
|
619
623
|
|
620
624
|
preload_specs = build_preload_specs(association_data,
|
@@ -665,7 +669,7 @@ class ViewModel::ActiveRecord
|
|
665
669
|
|
666
670
|
# handle view pre-parsing if defined
|
667
671
|
self.viewmodel_class.pre_parse(viewmodel_reference, metadata, hash_data) if self.viewmodel_class.respond_to?(:pre_parse)
|
668
|
-
hash_data.keys.each do |key|
|
672
|
+
hash_data.keys.each do |key| # rubocop:disable Style/HashEachMethods
|
669
673
|
if self.viewmodel_class.respond_to?(:"pre_parse_#{key}")
|
670
674
|
self.viewmodel_class.public_send("pre_parse_#{key}", viewmodel_reference, metadata, hash_data, hash_data.delete(key))
|
671
675
|
end
|
@@ -710,19 +714,18 @@ class ViewModel::ActiveRecord
|
|
710
714
|
referenced_associations[name] = ref
|
711
715
|
end
|
712
716
|
else
|
713
|
-
|
714
|
-
|
717
|
+
associations[name] =
|
718
|
+
if association_data.collection?
|
715
719
|
OwnedCollectionUpdate::Parser
|
716
720
|
.new(association_data, blame_reference, valid_reference_keys)
|
717
721
|
.parse(value)
|
718
|
-
|
719
|
-
|
720
|
-
if value.nil?
|
722
|
+
else # not a collection
|
723
|
+
if value.nil? # rubocop:disable Style/IfInsideElse
|
721
724
|
nil
|
722
725
|
else
|
723
726
|
self.class.parse_associated(association_data, blame_reference, valid_reference_keys, value)
|
724
727
|
end
|
725
|
-
|
728
|
+
end
|
726
729
|
end
|
727
730
|
else
|
728
731
|
raise ViewModel::DeserializationError::UnknownAttribute.new(name, blame_reference)
|
@@ -730,7 +733,6 @@ class ViewModel::ActiveRecord
|
|
730
733
|
end
|
731
734
|
end
|
732
735
|
|
733
|
-
|
734
736
|
def blame_reference
|
735
737
|
ViewModel::Reference.new(self.viewmodel_class, self.id)
|
736
738
|
end
|