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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -0
  3. data/Appraisals +6 -6
  4. data/Rakefile +5 -5
  5. data/gemfiles/rails_5_2.gemfile +5 -5
  6. data/gemfiles/rails_6_0.gemfile +5 -5
  7. data/iknow_view_models.gemspec +40 -39
  8. data/lib/iknow_view_models.rb +9 -7
  9. data/lib/iknow_view_models/version.rb +1 -1
  10. data/lib/view_model.rb +17 -14
  11. data/lib/view_model/access_control.rb +5 -2
  12. data/lib/view_model/access_control/composed.rb +10 -9
  13. data/lib/view_model/access_control/open.rb +2 -0
  14. data/lib/view_model/access_control/read_only.rb +2 -0
  15. data/lib/view_model/access_control/tree.rb +11 -6
  16. data/lib/view_model/access_control_error.rb +4 -1
  17. data/lib/view_model/active_record.rb +12 -11
  18. data/lib/view_model/active_record/association_data.rb +2 -1
  19. data/lib/view_model/active_record/association_manipulation.rb +6 -4
  20. data/lib/view_model/active_record/cache.rb +4 -2
  21. data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
  22. data/lib/view_model/active_record/controller_base.rb +4 -1
  23. data/lib/view_model/active_record/nested_controller_base.rb +1 -0
  24. data/lib/view_model/active_record/update_context.rb +8 -6
  25. data/lib/view_model/active_record/update_data.rb +32 -30
  26. data/lib/view_model/active_record/update_operation.rb +17 -13
  27. data/lib/view_model/active_record/visitor.rb +0 -1
  28. data/lib/view_model/after_transaction_runner.rb +0 -1
  29. data/lib/view_model/callbacks.rb +3 -1
  30. data/lib/view_model/controller.rb +13 -3
  31. data/lib/view_model/deserialization_error.rb +15 -12
  32. data/lib/view_model/error.rb +12 -10
  33. data/lib/view_model/error_view.rb +3 -1
  34. data/lib/view_model/migration/no_path_error.rb +1 -0
  35. data/lib/view_model/migration/one_way_error.rb +1 -0
  36. data/lib/view_model/migration/unspecified_version_error.rb +1 -0
  37. data/lib/view_model/record.rb +11 -13
  38. data/lib/view_model/reference.rb +3 -1
  39. data/lib/view_model/references.rb +8 -5
  40. data/lib/view_model/registry.rb +1 -1
  41. data/lib/view_model/schemas.rb +9 -4
  42. data/lib/view_model/serialization_error.rb +4 -1
  43. data/lib/view_model/serialize_context.rb +4 -4
  44. data/lib/view_model/test_helpers.rb +8 -3
  45. data/lib/view_model/test_helpers/arvm_builder.rb +19 -14
  46. data/lib/view_model/traversal_context.rb +2 -1
  47. data/test/.rubocop.yml +14 -0
  48. data/test/helpers/arvm_test_models.rb +12 -9
  49. data/test/helpers/arvm_test_utilities.rb +5 -3
  50. data/test/helpers/controller_test_helpers.rb +31 -29
  51. data/test/helpers/match_enumerator.rb +1 -0
  52. data/test/helpers/query_logging.rb +2 -1
  53. data/test/helpers/test_access_control.rb +5 -3
  54. data/test/helpers/viewmodel_spec_helpers.rb +21 -20
  55. data/test/unit/view_model/access_control_test.rb +144 -144
  56. data/test/unit/view_model/active_record/alias_test.rb +15 -13
  57. data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
  58. data/test/unit/view_model/active_record/cache_test.rb +27 -26
  59. data/test/unit/view_model/active_record/cloner_test.rb +67 -63
  60. data/test/unit/view_model/active_record/controller_test.rb +37 -38
  61. data/test/unit/view_model/active_record/counter_test.rb +10 -9
  62. data/test/unit/view_model/active_record/customization_test.rb +59 -58
  63. data/test/unit/view_model/active_record/has_many_test.rb +112 -111
  64. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
  65. data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
  66. data/test/unit/view_model/active_record/has_one_test.rb +37 -36
  67. data/test/unit/view_model/active_record/migration_test.rb +13 -13
  68. data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
  69. data/test/unit/view_model/active_record/poly_test.rb +44 -45
  70. data/test/unit/view_model/active_record/shared_test.rb +30 -28
  71. data/test/unit/view_model/active_record/version_test.rb +9 -7
  72. data/test/unit/view_model/active_record_test.rb +72 -72
  73. data/test/unit/view_model/callbacks_test.rb +19 -15
  74. data/test/unit/view_model/controller_test.rb +4 -2
  75. data/test/unit/view_model/record_test.rb +92 -97
  76. data/test/unit/view_model/traversal_context_test.rb +4 -5
  77. data/test/unit/view_model_test.rb +18 -16
  78. 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 "AccessControl.Forbidden"
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 "active_support"
4
- require "active_record"
3
+ require 'active_support'
4
+ require 'active_record'
5
5
 
6
- require "view_model"
7
- require "view_model/record"
6
+ require 'view_model'
7
+ require 'view_model/record'
8
8
 
9
- require "lazily"
10
- require "concurrent"
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 = "_update"
17
- ACTIONS_ATTRIBUTE = "actions"
18
- VALUES_ATTRIBUTE = "values"
19
- BEFORE_ATTRIBUTE = "before"
20
- AFTER_ATTRIBUTE = "after"
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 = @direct_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("Polymorphic through relationships not supported yet") if association_data.polymorphic?
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("Polymorphic through relationships not supported yet") if association_data.polymorphic?
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("Internal error: append_association is not yet supported for polymorphic indirect associations")
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: "FOR SHARE",
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: "FOR SHARE",
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("Can not provide both `before` and `after` anchors for a collection append")
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: before,
59
- after: 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}'") if match.nil?
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
@@ -88,6 +88,7 @@ module ViewModel::ActiveRecord::NestedControllerBase
88
88
 
89
89
  self.owner_viewmodel = owner
90
90
  raise ArgumentError.new("Could not find owner ViewModel class '#{owner_name}'") if owner_viewmodel.nil?
91
+
91
92
  self.association_name = as
92
93
  end
93
94
  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 { |ref, upd| upd.built? }.map { |ref, upd| upd.viewmodel.to_reference }
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("References not referred to from roots", dangling_references)
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 == "PostgreSQL"
269
- model_class.connection.execute("SET CONSTRAINTS ALL IMMEDIATE")
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 "abstract"
222
+ raise 'abstract'
219
223
  end
220
224
 
221
225
  def append_action_schema # abstract
222
- raise "abstract"
226
+ raise 'abstract'
223
227
  end
224
228
 
225
229
  def remove_action_schema # abstract
226
- raise "abstract"
230
+ raise 'abstract'
227
231
  end
228
232
 
229
233
  def update_action_schema # abstract
230
- raise "abstract"
234
+ raise 'abstract'
231
235
  end
232
236
 
233
- def parse_contents(values) # abstract
234
- raise "abstract"
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 "abstract"
262
+ raise 'abstract'
259
263
  end
260
264
 
261
265
  def functional_update_type # abstract
262
- raise "abstract"
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: "root")
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: "reference")
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
- (associations.merge(referenced_associations)).each do |assoc_name, reference|
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
- if association_data.collection?
714
- associations[name] =
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
- else # not a collection
719
- associations[name] =
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
- end
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