iknow_view_models 3.14.2 → 3.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cf3f71de6d7c8c39cf70b91be824338142ad42ace6520b544cc6a4b7ebd243e
4
- data.tar.gz: ba711696a0db14aefd68d09073be63804aeff9a64d591b59a877cd8c1b22d1da
3
+ metadata.gz: 32034ee1a16051289f76e317a76a7356f32093fd4439aa75503ffa00ab1f8b95
4
+ data.tar.gz: cb8a4669e79d68888c14d76b9ed718c4f2d8336da0989a22a7eafc779aff3bca
5
5
  SHA512:
6
- metadata.gz: b603cb052812681a8f892d6297f6cb869f0f5ee7b8976e61829ebba2ee2f9ce96834a8d3cacb136c6a7acf87d89413bd23d16dd620877a57d97b72c398bab7fa
7
- data.tar.gz: 1e5f60a31a7edbd084eaf69389b31f85efcee41df34b891e7f3d11d08b12b888ba19082da45f7b48a853a98e0581b2a98c36b6d4ed1841de62c7871919c687c2
6
+ metadata.gz: 8effb32744fe38236249ff4a483ff9a778efdd92ca00693eb8c3f00546fedfc4de7792456fcbd04d652659a713a4ecd8d3efefde43633bd9d19a666c93e930fb
7
+ data.tar.gz: 3915fbdfa63db615439e186da904c7c2899727e66a0326abf31b6d7398423761dd79db1c544ae69ba5983200d91d5ed1929449307cf961da2073cc02e3909307
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IknowViewModels
4
- VERSION = '3.14.2'
4
+ VERSION = '3.14.3'
5
5
  end
@@ -19,14 +19,15 @@ module ViewModel::MigratableView
19
19
  @migrations_lock = Monitor.new
20
20
  @migration_classes = {}
21
21
  @migration_paths = {}
22
- @realized_migration_paths = true
23
22
  end
24
23
 
25
24
  def migration_path(from:, to:)
26
25
  @migrations_lock.synchronize do
27
- realize_paths! unless @realized_migration_paths
26
+ unless @migration_paths.has_key?(to)
27
+ @migration_paths[to] = compute_migration_paths(to)
28
+ end
28
29
 
29
- migrations = @migration_paths.fetch([from, to]) do
30
+ migrations = @migration_paths[to].fetch(from) do
30
31
  raise ViewModel::Migration::NoPathError.new(self, from, to)
31
32
  end
32
33
 
@@ -64,25 +65,35 @@ module ViewModel::MigratableView
64
65
  const_set(:"Migration_#{from}_To_#{to}", migration_class)
65
66
  @migration_classes[[from, to]] = migration_class
66
67
 
67
- @realized_migration_paths = false
68
+ clear_migration_paths!
69
+ end
70
+ end
71
+
72
+ def clear_migration_paths!
73
+ @migrations_lock.synchronize do
74
+ @migration_paths.clear
68
75
  end
69
76
  end
70
77
 
71
78
  # Internal: find and record possible paths to the current schema version.
72
- def realize_paths!
73
- @migration_paths.clear
79
+ def compute_migration_paths(to_version)
80
+ unless to_version <= self.schema_version
81
+ raise RuntimeError.new("Cannot compute path to future version '#{to_version}'")
82
+ end
74
83
 
75
84
  graph = RGL::DirectedAdjacencyGraph.new
76
85
 
77
- # Add a vertex for the current version, in case no edges reach it
78
- graph.add_vertex(self.schema_version)
86
+ # Add a vertex for the destination version, in case no edges reach it
87
+ graph.add_vertex(to_version)
79
88
 
80
89
  # Add edges backwards, as we care about paths from the latest version
81
90
  @migration_classes.each_key do |from, to|
82
91
  graph.add_edge(to, from)
83
92
  end
84
93
 
85
- paths = graph.dijkstra_shortest_paths(Hash.new { 1 }, self.schema_version)
94
+ paths = graph.dijkstra_shortest_paths(Hash.new { 1 }, to_version)
95
+
96
+ result = {}
86
97
 
87
98
  paths.each do |target_version, path|
88
99
  next if path.nil? || path.length == 1
@@ -92,12 +103,10 @@ module ViewModel::MigratableView
92
103
  @migration_classes.fetch([from, to])
93
104
  end
94
105
 
95
- key = [target_version, schema_version]
96
-
97
- @migration_paths[key] = path_migration_classes.map(&:new)
106
+ result[target_version] = path_migration_classes.map(&:new)
98
107
  end
99
108
 
100
- @realized_paths = true
109
+ result
101
110
  end
102
111
  end
103
112
  end
@@ -23,16 +23,15 @@ class ViewModel
23
23
  end
24
24
  end
25
25
 
26
+ MigrationPlan = Struct.new(:path, :required_version, :current_version, :viewmodel_class)
27
+
26
28
  def initialize(required_versions)
27
- @paths = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
28
- if required_version != viewmodel_class.schema_version
29
- path = viewmodel_class.migration_path(from: required_version, to: viewmodel_class.schema_version)
30
- h[viewmodel_class.view_name] = path
31
- end
32
- end
29
+ @plans = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
30
+ current_version = viewmodel_class.schema_version
31
+ next if required_version == current_version
33
32
 
34
- @versions = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
35
- h[viewmodel_class.view_name] = [required_version, viewmodel_class.schema_version]
33
+ path = viewmodel_class.migration_path(from: required_version, to: current_version)
34
+ h[viewmodel_class.view_name] = MigrationPlan.new(path, required_version, current_version, viewmodel_class)
36
35
  end
37
36
  end
38
37
 
@@ -128,23 +127,22 @@ class ViewModel
128
127
  end
129
128
 
130
129
  def migrate_viewmodel!(view_name, source_version, view_hash, references)
131
- path = @paths[view_name]
132
- return false unless path
130
+ plan = @plans[view_name]
131
+ return false unless plan
133
132
 
134
- required_version, current_version = @versions[view_name]
135
- return false if source_version == current_version
133
+ return false if source_version == plan.current_version
136
134
 
137
135
  # We assume that an unspecified source version is the same as the required
138
136
  # version (i.e. the version demanded by the client request).
139
- unless source_version.nil? || source_version == required_version
137
+ unless source_version.nil? || source_version == plan.required_version
140
138
  raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
141
139
  end
142
140
 
143
- path.each do |migration|
141
+ plan.path.each do |migration|
144
142
  migration.up(view_hash, references)
145
143
  end
146
144
 
147
- view_hash[ViewModel::VERSION_ATTRIBUTE] = current_version
145
+ view_hash[ViewModel::VERSION_ATTRIBUTE] = plan.current_version
148
146
 
149
147
  true
150
148
  end
@@ -156,15 +154,23 @@ class ViewModel
156
154
  private
157
155
 
158
156
  def migrate_viewmodel!(view_name, source_version, view_hash, references)
159
- path = @paths[view_name]
160
- return false unless path
157
+ plan = @plans[view_name]
158
+ return false unless plan
161
159
 
162
160
  # In a serialized output, the source version should always be the present
163
161
  # and the current version, unless already modified by a parent migration
164
- required_version, current_version = @versions[view_name]
165
- return false if source_version == required_version
166
-
167
- unless source_version == current_version
162
+ return false if source_version == plan.required_version
163
+
164
+ # If a parent migration has already partially migrated us to an
165
+ # intermediate version, we need to construct a new path to the required
166
+ # version.
167
+ if source_version == plan.current_version
168
+ path = plan.path
169
+ elsif view_hash[ViewModel::MIGRATED_ATTRIBUTE] &&
170
+ source_version > plan.required_version &&
171
+ source_version < plan.current_version
172
+ path = plan.viewmodel_class.migration_path(from: plan.required_version, to: source_version)
173
+ else
168
174
  raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
169
175
  end
170
176
 
@@ -172,7 +178,7 @@ class ViewModel
172
178
  migration.down(view_hash, references)
173
179
  end
174
180
 
175
- view_hash[ViewModel::VERSION_ATTRIBUTE] = required_version
181
+ view_hash[ViewModel::VERSION_ATTRIBUTE] = plan.required_version
176
182
 
177
183
  true
178
184
  end
@@ -483,6 +483,87 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
483
483
  end
484
484
  end
485
485
 
486
+ describe 'partially migrating children' do
487
+ include ViewModelSpecHelpers::ParentAndBelongsToChild
488
+ let(:migration_versions) { { viewmodel_class => 1, child_viewmodel_class => 1 } }
489
+
490
+ # Parent 2->1 requires premigrating child 3->2
491
+ def model_attributes
492
+ child_viewmodel_class = self.child_viewmodel_class
493
+ super.merge(
494
+ viewmodel: ->(_v) {
495
+ self.schema_version = 2
496
+
497
+ # The migration from 1 -> 2 relies on a field only present in child v2
498
+ migrates from: 1, to: 2 do
499
+ down do |view, references|
500
+ child = view['child']
501
+
502
+ if child['_version'] > 2
503
+ migrator = ViewModel::DownMigrator.new({ child_viewmodel_class => 2 })
504
+ migrator.migrate!({ 'data' => child, 'references' => references.deep_dup })
505
+ end
506
+
507
+ view['child_v2_field'] = child['v2_field']
508
+ end
509
+ end
510
+ },
511
+ )
512
+ end
513
+
514
+ def child_attributes
515
+ super().merge(
516
+ viewmodel: ->(_v) {
517
+ self.schema_version = 3
518
+ migrates from: 1, to: 2 do
519
+ down do |view, _references|
520
+ view.delete('v2_field')
521
+ end
522
+ end
523
+
524
+ migrates from: 2, to: 3 do
525
+ down do |view, _references|
526
+ view['v2_field'] = 'v2_field_data'
527
+ end
528
+ end
529
+ })
530
+ end
531
+
532
+ let(:v1_serialization_data) do
533
+ {
534
+ ViewModel::TYPE_ATTRIBUTE => viewmodel_class.view_name,
535
+ ViewModel::VERSION_ATTRIBUTE => 1,
536
+ ViewModel::ID_ATTRIBUTE => viewmodel.id,
537
+ 'name' => viewmodel.name,
538
+ 'child' => {
539
+ ViewModel::TYPE_ATTRIBUTE => child_viewmodel_class.view_name,
540
+ ViewModel::VERSION_ATTRIBUTE => 1,
541
+ ViewModel::ID_ATTRIBUTE => viewmodel.child.id,
542
+ 'name' => viewmodel.child.name,
543
+ },
544
+ 'child_v2_field' => 'v2_field_data',
545
+ }
546
+ end
547
+
548
+ let(:migrator) { down_migrator }
549
+ let(:subject) do
550
+ current_serialization.deep_dup
551
+ end
552
+
553
+ let(:expected_result) do
554
+ data = v1_serialization_data.deep_dup
555
+ data[ViewModel::MIGRATED_ATTRIBUTE] = true
556
+ data['child'][ViewModel::MIGRATED_ATTRIBUTE] = true
557
+
558
+ { 'data' => data }
559
+ end
560
+
561
+ it 'migrates' do
562
+ migrate!
563
+ assert_equal(expected_result, subject)
564
+ end
565
+ end
566
+
486
567
  describe 'concurrently inserting a reference' do
487
568
  include ViewModelSpecHelpers::ReferencedList
488
569
  let(:migration_versions) { { viewmodel_class => 1 } }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iknow_view_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.14.2
4
+ version: 3.14.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - iKnow Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-21 00:00:00.000000000 Z
11
+ date: 2025-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack