iknow_view_models 3.1.7 → 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +6 -6
  3. data/.rubocop.yml +18 -0
  4. data/Appraisals +6 -6
  5. data/Gemfile +6 -2
  6. data/Rakefile +5 -5
  7. data/gemfiles/rails_5_2.gemfile +5 -5
  8. data/gemfiles/rails_6_0.gemfile +9 -0
  9. data/iknow_view_models.gemspec +40 -38
  10. data/lib/iknow_view_models.rb +9 -7
  11. data/lib/iknow_view_models/version.rb +1 -1
  12. data/lib/view_model.rb +31 -17
  13. data/lib/view_model/access_control.rb +5 -2
  14. data/lib/view_model/access_control/composed.rb +10 -9
  15. data/lib/view_model/access_control/open.rb +2 -0
  16. data/lib/view_model/access_control/read_only.rb +2 -0
  17. data/lib/view_model/access_control/tree.rb +11 -6
  18. data/lib/view_model/access_control_error.rb +4 -1
  19. data/lib/view_model/active_record.rb +17 -15
  20. data/lib/view_model/active_record/association_data.rb +2 -1
  21. data/lib/view_model/active_record/association_manipulation.rb +6 -4
  22. data/lib/view_model/active_record/cache.rb +114 -34
  23. data/lib/view_model/active_record/cache/cacheable_view.rb +2 -2
  24. data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
  25. data/lib/view_model/active_record/controller.rb +68 -1
  26. data/lib/view_model/active_record/controller_base.rb +4 -1
  27. data/lib/view_model/active_record/nested_controller_base.rb +1 -0
  28. data/lib/view_model/active_record/update_context.rb +8 -6
  29. data/lib/view_model/active_record/update_data.rb +32 -30
  30. data/lib/view_model/active_record/update_operation.rb +17 -13
  31. data/lib/view_model/active_record/visitor.rb +0 -1
  32. data/lib/view_model/after_transaction_runner.rb +2 -2
  33. data/lib/view_model/callbacks.rb +3 -1
  34. data/lib/view_model/controller.rb +13 -3
  35. data/lib/view_model/deserialization_error.rb +15 -12
  36. data/lib/view_model/error.rb +12 -10
  37. data/lib/view_model/error_view.rb +3 -1
  38. data/lib/view_model/migratable_view.rb +78 -0
  39. data/lib/view_model/migration.rb +48 -0
  40. data/lib/view_model/migration/no_path_error.rb +26 -0
  41. data/lib/view_model/migration/one_way_error.rb +24 -0
  42. data/lib/view_model/migration/unspecified_version_error.rb +24 -0
  43. data/lib/view_model/migrator.rb +108 -0
  44. data/lib/view_model/record.rb +15 -14
  45. data/lib/view_model/reference.rb +3 -1
  46. data/lib/view_model/references.rb +8 -5
  47. data/lib/view_model/registry.rb +1 -1
  48. data/lib/view_model/schemas.rb +9 -4
  49. data/lib/view_model/serialization_error.rb +4 -1
  50. data/lib/view_model/serialize_context.rb +4 -4
  51. data/lib/view_model/test_helpers.rb +8 -3
  52. data/lib/view_model/test_helpers/arvm_builder.rb +21 -15
  53. data/lib/view_model/traversal_context.rb +2 -1
  54. data/nix/dependencies.nix +5 -0
  55. data/nix/gem/generate.rb +2 -1
  56. data/shell.nix +8 -3
  57. data/test/.rubocop.yml +14 -0
  58. data/test/helpers/arvm_test_models.rb +12 -9
  59. data/test/helpers/arvm_test_utilities.rb +5 -3
  60. data/test/helpers/controller_test_helpers.rb +55 -32
  61. data/test/helpers/match_enumerator.rb +1 -0
  62. data/test/helpers/query_logging.rb +2 -1
  63. data/test/helpers/test_access_control.rb +5 -3
  64. data/test/helpers/viewmodel_spec_helpers.rb +88 -22
  65. data/test/unit/view_model/access_control_test.rb +144 -144
  66. data/test/unit/view_model/active_record/alias_test.rb +15 -13
  67. data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
  68. data/test/unit/view_model/active_record/cache_test.rb +68 -31
  69. data/test/unit/view_model/active_record/cloner_test.rb +67 -63
  70. data/test/unit/view_model/active_record/controller_test.rb +113 -65
  71. data/test/unit/view_model/active_record/counter_test.rb +10 -9
  72. data/test/unit/view_model/active_record/customization_test.rb +59 -58
  73. data/test/unit/view_model/active_record/has_many_test.rb +112 -111
  74. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
  75. data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
  76. data/test/unit/view_model/active_record/has_one_test.rb +37 -36
  77. data/test/unit/view_model/active_record/migration_test.rb +161 -0
  78. data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
  79. data/test/unit/view_model/active_record/poly_test.rb +44 -45
  80. data/test/unit/view_model/active_record/shared_test.rb +30 -28
  81. data/test/unit/view_model/active_record/version_test.rb +9 -7
  82. data/test/unit/view_model/active_record_test.rb +72 -72
  83. data/test/unit/view_model/callbacks_test.rb +19 -15
  84. data/test/unit/view_model/controller_test.rb +4 -2
  85. data/test/unit/view_model/record_test.rb +92 -97
  86. data/test/unit/view_model/traversal_context_test.rb +4 -5
  87. data/test/unit/view_model_test.rb +18 -16
  88. metadata +36 -12
  89. data/.travis.yml +0 -31
  90. data/appveyor.yml +0 -22
  91. data/gemfiles/rails_6_0_beta.gemfile +0 -9
@@ -40,10 +40,10 @@ module ViewModel::ActiveRecord::Cache::CacheableView
40
40
  @viewmodel_cache
41
41
  end
42
42
 
43
- def serialize_from_cache(views, serialize_context:)
43
+ def serialize_from_cache(views, migration_versions: {}, locked: false, serialize_context:)
44
44
  plural = views.is_a?(Array)
45
45
  views = Array.wrap(views)
46
- json_views, json_refs = viewmodel_cache.fetch_by_viewmodel(views, serialize_context: serialize_context)
46
+ json_views, json_refs = viewmodel_cache.fetch_by_viewmodel(views, locked: locked, migration_versions: migration_versions, serialize_context: serialize_context)
47
47
  json_views = json_views.first unless plural
48
48
  return json_views, json_refs
49
49
  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)
@@ -17,6 +17,8 @@ module ViewModel::ActiveRecord::Controller
17
17
  include ViewModel::ActiveRecord::CollectionNestedController
18
18
  include ViewModel::ActiveRecord::SingularNestedController
19
19
 
20
+ MIGRATION_VERSION_HEADER = 'X-ViewModel-Versions'
21
+
20
22
  def show(scope: nil, viewmodel_class: self.viewmodel_class, serialize_context: new_serialize_context(viewmodel_class: viewmodel_class))
21
23
  view = nil
22
24
  pre_rendered = viewmodel_class.transaction do
@@ -62,7 +64,29 @@ module ViewModel::ActiveRecord::Controller
62
64
  end
63
65
 
64
66
  included do
65
- etag { self.viewmodel_class.deep_schema_version }
67
+ etag { migrated_deep_schema_version }
68
+ end
69
+
70
+ def parse_viewmodel_updates
71
+ super.tap do |update_hash, refs|
72
+ if migration_versions.present?
73
+ migrator = ViewModel::UpMigrator.new(migration_versions)
74
+ migrator.migrate!([update_hash, refs], references: refs)
75
+ end
76
+ end
77
+ end
78
+
79
+ def prerender_viewmodel(*)
80
+ super do |jbuilder|
81
+ yield(jbuilder) if block_given?
82
+
83
+ # migrate the resulting structure before it's serialized to a json string
84
+ if migration_versions.present?
85
+ tree = jbuilder.attributes!
86
+ migrator = ViewModel::DownMigrator.new(migration_versions)
87
+ migrator.migrate!(tree, references: tree['references'])
88
+ end
89
+ end
66
90
  end
67
91
 
68
92
  private
@@ -70,4 +94,47 @@ module ViewModel::ActiveRecord::Controller
70
94
  def viewmodel_id
71
95
  parse_param(:id)
72
96
  end
97
+
98
+ def migration_versions
99
+ @migration_versions ||=
100
+ begin
101
+ version_spec =
102
+ if params.include?(:versions)
103
+ params[:versions]
104
+ elsif request.headers.include?(MIGRATION_VERSION_HEADER)
105
+ begin
106
+ JSON.parse(request.headers[MIGRATION_VERSION_HEADER])
107
+ rescue JSON::ParserError
108
+ raise ViewModel::Error.new(status: 400, detail: "Invalid JSON in #{MIGRATION_VERSION_HEADER}")
109
+ end
110
+ else
111
+ {}
112
+ end
113
+
114
+ versions =
115
+ IknowParams::Parser.parse_value(
116
+ version_spec,
117
+ with: IknowParams::Serializer::HashOf.new(
118
+ IknowParams::Serializer::String, IknowParams::Serializer::Integer))
119
+
120
+ migration_versions = {}
121
+
122
+ versions.each do |view_name, required_version|
123
+ viewmodel_class = ViewModel::Registry.for_view_name(view_name)
124
+
125
+ if viewmodel_class.schema_version != required_version
126
+ migration_versions[viewmodel_class] = required_version
127
+ end
128
+ rescue ViewModel::DeserializationError::UnknownView
129
+ # Ignore requests to migrate types that no longer exist
130
+ next
131
+ end
132
+
133
+ migration_versions.freeze
134
+ end
135
+ end
136
+
137
+ def migrated_deep_schema_version
138
+ ViewModel::Migrator.migrated_deep_schema_version(viewmodel_class, migration_versions, include_referenced: true)
139
+ end
73
140
  end
@@ -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
@@ -1,4 +1,6 @@
1
- require "renum"
1
+ # frozen_string_literal: true
2
+
3
+ require 'renum'
2
4
 
3
5
  # Partially parsed tree of user-specified update hashes, created during deserialization.
4
6
  class ViewModel::ActiveRecord
@@ -14,7 +16,7 @@ class ViewModel::ActiveRecord
14
16
  :update_data,
15
17
  :points_to, # AssociationData => UpdateOperation (returns single new viewmodel to update fkey)
16
18
  :pointed_to, # AssociationData => UpdateOperation(s) (returns viewmodel(s) with which to update assoc cache)
17
- :reparent_to, # If node needs to update its pointer to a new parent, ParentData for the parent
19
+ :reparent_to, # If node needs to update its pointer to a new parent, ParentData for the parent
18
20
  :reposition_to, # if this node participates in a list under its parent, what should its position be?
19
21
  :released_children # Set of children that have been released
20
22
 
@@ -46,11 +48,11 @@ class ViewModel::ActiveRecord
46
48
 
47
49
  # Evaluate a built update tree, applying and saving changes to the models.
48
50
  def run!(deserialize_context:)
49
- raise ViewModel::DeserializationError::Internal.new("Internal error: UpdateOperation run before build") unless built?
51
+ raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation run before build') unless built?
50
52
 
51
53
  case @run_state
52
54
  when RunState::Running
53
- raise ViewModel::DeserializationError::Internal.new("Internal error: Cycle found in running UpdateOperation")
55
+ raise ViewModel::DeserializationError::Internal.new('Internal error: Cycle found in running UpdateOperation')
54
56
  when RunState::Run
55
57
  return viewmodel
56
58
  end
@@ -217,7 +219,7 @@ class ViewModel::ActiveRecord
217
219
 
218
220
  # Recursively builds UpdateOperations for the associations in our UpdateData
219
221
  def build!(update_context)
220
- raise ViewModel::DeserializationError::Internal.new("Internal error: UpdateOperation cannot build a deferred update") if viewmodel.nil?
222
+ raise ViewModel::DeserializationError::Internal.new('Internal error: UpdateOperation cannot build a deferred update') if viewmodel.nil?
221
223
  return self if built?
222
224
 
223
225
  update_data.associations.each do |association_name, association_update_data|
@@ -254,8 +256,8 @@ class ViewModel::ActiveRecord
254
256
  def add_update(association_data, update)
255
257
  target =
256
258
  case association_data.pointer_location
257
- when :remote; pointed_to
258
- when :local; points_to
259
+ when :remote then pointed_to
260
+ when :local then points_to
259
261
  end
260
262
 
261
263
  target[association_data] = update
@@ -658,7 +660,7 @@ class ViewModel::ActiveRecord
658
660
  other.indirect_viewmodel_reference == self.indirect_viewmodel_reference
659
661
  end
660
662
 
661
- alias :eql? :==
663
+ alias eql? ==
662
664
  end
663
665
 
664
666
  # Helper class to wrap the previous members of a referenced collection and
@@ -767,6 +769,7 @@ class ViewModel::ActiveRecord
767
769
  member.ref_string = ref_string if ref_string
768
770
  member
769
771
  end
772
+
770
773
  def remove_from_members(removed_members)
771
774
  s = removed_members.to_set
772
775
  members.reject! { |m| s.include?(m) }
@@ -900,11 +903,12 @@ class ViewModel::ActiveRecord
900
903
 
901
904
  def clear_association_cache(model, reflection)
902
905
  association = model.association(reflection.name)
903
- if reflection.collection?
904
- association.target = []
905
- else
906
- association.target = nil
907
- end
906
+ association.target =
907
+ if reflection.collection?
908
+ []
909
+ else
910
+ nil
911
+ end
908
912
  end
909
913
 
910
914
  def blame_reference