iknow_view_models 3.14.2 → 3.14.4

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -3
  3. data/Appraisals +7 -15
  4. data/iknow_view_models.gemspec +3 -0
  5. data/lib/iknow_view_models/version.rb +1 -1
  6. data/lib/view_model/config.rb +1 -0
  7. data/lib/view_model/controller/migration_versions.rb +13 -3
  8. data/lib/view_model/migratable_view.rb +22 -13
  9. data/lib/view_model/migration/strict_migration_error.rb +23 -0
  10. data/lib/view_model/migration.rb +1 -0
  11. data/lib/view_model/migrator.rb +39 -22
  12. data/test/helpers/arvm_test_utilities.rb +0 -1
  13. data/test/helpers/match_enumerator.rb +3 -3
  14. data/test/helpers/viewmodel_spec_helpers.rb +1 -2
  15. data/test/test_helper.rb +28 -0
  16. data/test/unit/view_model/access_control_test.rb +2 -5
  17. data/test/unit/view_model/active_record/alias_test.rb +2 -2
  18. data/test/unit/view_model/active_record/belongs_to_test.rb +2 -2
  19. data/test/unit/view_model/active_record/cache_test.rb +1 -3
  20. data/test/unit/view_model/active_record/cloner_test.rb +2 -4
  21. data/test/unit/view_model/active_record/controller_nested_test.rb +4 -6
  22. data/test/unit/view_model/active_record/controller_test.rb +4 -6
  23. data/test/unit/view_model/active_record/counter_test.rb +2 -2
  24. data/test/unit/view_model/active_record/customization_test.rb +2 -2
  25. data/test/unit/view_model/active_record/has_many_test.rb +2 -2
  26. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +2 -2
  27. data/test/unit/view_model/active_record/has_many_through_test.rb +2 -2
  28. data/test/unit/view_model/active_record/has_one_test.rb +2 -2
  29. data/test/unit/view_model/active_record/migration_test.rb +156 -4
  30. data/test/unit/view_model/active_record/namespacing_test.rb +1 -3
  31. data/test/unit/view_model/active_record/poly_test.rb +2 -2
  32. data/test/unit/view_model/active_record/shared_test.rb +2 -2
  33. data/test/unit/view_model/active_record/version_test.rb +2 -2
  34. data/test/unit/view_model/active_record_test.rb +2 -3
  35. data/test/unit/view_model/callbacks_test.rb +2 -2
  36. data/test/unit/view_model/controller_test.rb +1 -3
  37. data/test/unit/view_model/deserialization_error/unique_violation_test.rb +1 -3
  38. data/test/unit/view_model/error_wrapping_test.rb +1 -4
  39. data/test/unit/view_model/garbage_collection_test.rb +1 -2
  40. data/test/unit/view_model/record_test.rb +2 -3
  41. data/test/unit/view_model/registry_test.rb +1 -3
  42. data/test/unit/view_model/traversal_context_test.rb +1 -3
  43. data/test/unit/view_model_test.rb +1 -4
  44. metadata +47 -3
  45. data/gemfiles/rails_6_1.gemfile +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cf3f71de6d7c8c39cf70b91be824338142ad42ace6520b544cc6a4b7ebd243e
4
- data.tar.gz: ba711696a0db14aefd68d09073be63804aeff9a64d591b59a877cd8c1b22d1da
3
+ metadata.gz: d2167f858fe318caa9d87ee64a0d3a3898226f5705c596549094894be334a62f
4
+ data.tar.gz: d45adf1829482a676d47a820be735d3ae7385b27ad6cb7406e0d6a62b77cf7fa
5
5
  SHA512:
6
- metadata.gz: b603cb052812681a8f892d6297f6cb869f0f5ee7b8976e61829ebba2ee2f9ce96834a8d3cacb136c6a7acf87d89413bd23d16dd620877a57d97b72c398bab7fa
7
- data.tar.gz: 1e5f60a31a7edbd084eaf69389b31f85efcee41df34b891e7f3d11d08b12b888ba19082da45f7b48a853a98e0581b2a98c36b6d4ed1841de62c7871919c687c2
6
+ metadata.gz: 897fb03fe571579b5b8d2d259ac2705ac176ae779deda665b662ef90421ae09aeec829468f863f3c63e6bc1946480ffd971687a57c920477bfb65a66025d7dae
7
+ data.tar.gz: d931e8d90d6cf5f4440053df982d507f090a75ee6fe96cd8a511952d7b0e04a86a51624afcc209c98f75b05a852c35192ce36cd095d9ea3de38bfe1cf6f18f30
@@ -26,10 +26,8 @@ jobs:
26
26
  strategy:
27
27
  fail-fast: false
28
28
  matrix:
29
- ruby-version: ['3.1', '3.2', '3.3']
29
+ ruby-version: ['3.2', '3.3']
30
30
  include:
31
- - ruby-version: '3.1'
32
- bundle-gemfile: gemfiles/rails_6_1.gemfile
33
31
  - ruby-version: '3.2'
34
32
  bundle-gemfile: gemfiles/rails_7_0.gemfile
35
33
  - ruby-version: '3.3'
data/Appraisals CHANGED
@@ -1,19 +1,11 @@
1
- appraise 'rails-5-2' do
2
- gem 'activerecord', '~> 5.2.0'
3
- gem 'activesupport', '~> 5.2.0'
4
- end
5
-
6
- appraise 'rails-6-0' do
7
- gem 'activerecord', '~> 6.0.0'
8
- gem 'activesupport', '~> 6.0.0'
9
- end
10
-
11
- appraise 'rails-6-1' do
12
- gem 'activerecord', '~> 6.1.0'
13
- gem 'activesupport', '~> 6.1.0'
14
- end
15
-
16
1
  appraise 'rails-7-0' do
2
+ gem 'minitest-ci'
17
3
  gem 'activerecord', '~> 7.0.0'
18
4
  gem 'activesupport', '~> 7.0.0'
19
5
  end
6
+
7
+ appraise 'rails-7-1' do
8
+ gem 'minitest-ci'
9
+ gem 'activerecord', '~> 7.1.0'
10
+ gem 'activesupport', '~> 7.1.0'
11
+ end
@@ -44,7 +44,10 @@ Gem::Specification.new do |spec|
44
44
  spec.add_development_dependency 'bundler'
45
45
  spec.add_development_dependency 'byebug'
46
46
  spec.add_development_dependency 'method_source'
47
+ spec.add_development_dependency 'minitest', '~> 6.0.0'
47
48
  spec.add_development_dependency 'minitest-hooks'
49
+ spec.add_development_dependency 'minitest-mock'
50
+ spec.add_development_dependency 'minitest-reporters'
48
51
  spec.add_development_dependency 'pg'
49
52
  spec.add_development_dependency 'pry'
50
53
  spec.add_development_dependency 'rake'
@@ -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.4'
5
5
  end
@@ -6,6 +6,7 @@ require 'keyword_builder'
6
6
  ViewModel::Config = Value.new(
7
7
  show_cause_in_error_view: false,
8
8
  debug_deserialization: false,
9
+ strict_migration_versions: false,
9
10
  )
10
11
 
11
12
  class ViewModel::Config
@@ -8,9 +8,19 @@ module ViewModel::Controller::MigrationVersions
8
8
  def migration_versions
9
9
  @migration_versions ||=
10
10
  begin
11
- specified_migration_versions.reject do |viewmodel_class, required_version|
12
- viewmodel_class.schema_version == required_version
13
- end.freeze
11
+ versions = specified_migration_versions.dup
12
+
13
+ unless ViewModel::Config.strict_migration_versions
14
+ # Ignore the current version. This allows skipping migrations if only
15
+ # current versions are requested. Can't be ignored when strict
16
+ # migrations are enabled, since we need to walk the tree to assert
17
+ # that all visited types are mentioned.
18
+ versions.reject! do |viewmodel_class, required_version|
19
+ viewmodel_class.schema_version == required_version
20
+ end
21
+ end
22
+
23
+ versions.freeze
14
24
  end
15
25
  end
16
26
 
@@ -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
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ViewModel::Migration::StrictMigrationError < ViewModel::AbstractError
4
+ attr_reader :vm_name
5
+
6
+ status 400
7
+ code 'Migration.StrictMigrationError'
8
+
9
+ def initialize(vm_name)
10
+ @vm_name = vm_name
11
+ super()
12
+ end
13
+
14
+ def detail
15
+ "No version was provided for the view #{vm_name}"
16
+ end
17
+
18
+ def meta
19
+ {
20
+ viewmodel: vm_name,
21
+ }
22
+ end
23
+ end
@@ -4,6 +4,7 @@ class ViewModel::Migration
4
4
  require 'view_model/migration/no_path_error'
5
5
  require 'view_model/migration/one_way_error'
6
6
  require 'view_model/migration/unspecified_version_error'
7
+ require 'view_model/migration/strict_migration_error'
7
8
 
8
9
  REFERENCE_ONLY_KEYS = [
9
10
  ViewModel::TYPE_ATTRIBUTE,
@@ -23,16 +23,19 @@ 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
+ # If strict migrations are enabled, a version must be explicitly specified
30
+ # for every view used
31
+ @strict_permitted_views = required_versions.each_key.to_set(&:view_name)
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
+ @plans = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h|
34
+ current_version = viewmodel_class.schema_version
35
+ next if required_version == current_version
36
+
37
+ path = viewmodel_class.migration_path(from: required_version, to: current_version)
38
+ h[viewmodel_class.view_name] = MigrationPlan.new(path, required_version, current_version, viewmodel_class)
36
39
  end
37
40
  end
38
41
 
@@ -69,6 +72,13 @@ class ViewModel
69
72
 
70
73
  private
71
74
 
75
+ def fetch_plan(view_name)
76
+ if ViewModel::Config.strict_migration_versions && !@strict_permitted_views.include?(view_name)
77
+ raise ViewModel::Migration::StrictMigrationError.new(view_name)
78
+ end
79
+ @plans[view_name]
80
+ end
81
+
72
82
  def migrate_tree!(node, references:)
73
83
  case node
74
84
  when Hash
@@ -128,23 +138,22 @@ class ViewModel
128
138
  end
129
139
 
130
140
  def migrate_viewmodel!(view_name, source_version, view_hash, references)
131
- path = @paths[view_name]
132
- return false unless path
141
+ plan = fetch_plan(view_name)
142
+ return false unless plan
133
143
 
134
- required_version, current_version = @versions[view_name]
135
- return false if source_version == current_version
144
+ return false if source_version == plan.current_version
136
145
 
137
146
  # We assume that an unspecified source version is the same as the required
138
147
  # version (i.e. the version demanded by the client request).
139
- unless source_version.nil? || source_version == required_version
148
+ unless source_version.nil? || source_version == plan.required_version
140
149
  raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
141
150
  end
142
151
 
143
- path.each do |migration|
152
+ plan.path.each do |migration|
144
153
  migration.up(view_hash, references)
145
154
  end
146
155
 
147
- view_hash[ViewModel::VERSION_ATTRIBUTE] = current_version
156
+ view_hash[ViewModel::VERSION_ATTRIBUTE] = plan.current_version
148
157
 
149
158
  true
150
159
  end
@@ -156,15 +165,23 @@ class ViewModel
156
165
  private
157
166
 
158
167
  def migrate_viewmodel!(view_name, source_version, view_hash, references)
159
- path = @paths[view_name]
160
- return false unless path
168
+ plan = fetch_plan(view_name)
169
+ return false unless plan
161
170
 
162
171
  # In a serialized output, the source version should always be the present
163
172
  # 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
173
+ return false if source_version == plan.required_version
174
+
175
+ # If a parent migration has already partially migrated us to an
176
+ # intermediate version, we need to construct a new path to the required
177
+ # version.
178
+ if source_version == plan.current_version
179
+ path = plan.path
180
+ elsif view_hash[ViewModel::MIGRATED_ATTRIBUTE] &&
181
+ source_version > plan.required_version &&
182
+ source_version < plan.current_version
183
+ path = plan.viewmodel_class.migration_path(from: plan.required_version, to: source_version)
184
+ else
168
185
  raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
169
186
  end
170
187
 
@@ -172,7 +189,7 @@ class ViewModel
172
189
  migration.down(view_hash, references)
173
190
  end
174
191
 
175
- view_hash[ViewModel::VERSION_ATTRIBUTE] = required_version
192
+ view_hash[ViewModel::VERSION_ATTRIBUTE] = plan.required_version
176
193
 
177
194
  true
178
195
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'logger'
4
4
  require 'active_support'
5
- require 'minitest/hooks'
6
5
 
7
6
  require 'view_model'
8
7
  require 'view_model/test_helpers'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # From https://stackoverflow.com/a/41293357
4
- module MiniTest::Assertions
4
+ module Minitest::Assertions
5
5
  class MatchEnumerator
6
6
  def initialize(expected, actual)
7
7
  @expected = expected
@@ -54,6 +54,6 @@ module MiniTest::Assertions
54
54
  result, message = MatchEnumerator.new(expected, actual).match
55
55
  assert result, message
56
56
  end
57
- end # MiniTest::Assertions
57
+ end # Minitest::Assertions
58
58
 
59
- Enumerator.infect_an_assertion :assert_match_enumerator, :must_contain_exactly
59
+ Minitest::Expectation.infect_an_assertion :assert_match_enumerator, :must_contain_exactly
@@ -3,8 +3,7 @@
3
3
  require 'view_model'
4
4
  require 'view_model/test_helpers'
5
5
 
6
- require 'minitest/unit'
7
- require 'minitest/hooks'
6
+ require 'minitest'
8
7
 
9
8
  module ViewModelSpecHelpers
10
9
  module Base
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'minitest'
6
+ Minitest.load_plugins
7
+
8
+ require 'minitest/hooks/default'
9
+ require 'minitest/hooks/test'
10
+ require 'minitest/mock'
11
+ require 'minitest/reporters'
12
+ require 'minitest/reporters/junit_reporter'
13
+
14
+ require 'rspec/expectations'
15
+ require 'rspec/expectations/minitest_integration'
16
+
17
+ FileUtils.mkdir_p('test/reports')
18
+ Minitest::Reporters.use!(
19
+ [
20
+ Minitest::Reporters::DefaultReporter.new,
21
+ Minitest::Reporters::JUnitReporter.new(
22
+ 'test/reports',
23
+ single_file: false,
24
+ ),
25
+ ],
26
+ )
27
+
28
+ require 'minitest/autorun'
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../test_helper'
4
+
3
5
  require_relative '../../helpers/arvm_test_utilities'
4
6
  require_relative '../../helpers/arvm_test_models'
5
7
  require_relative '../../helpers/viewmodel_spec_helpers'
6
8
 
7
- require 'minitest/autorun'
8
- require 'minitest/unit'
9
-
10
- require 'rspec/expectations/minitest_integration'
11
-
12
9
  require 'view_model/active_record'
13
10
 
14
11
  class ViewModel::AccessControlTest < ActiveSupport::TestCase
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
  require_relative '../../../helpers/viewmodel_spec_helpers'
6
8
 
7
- require 'minitest/autorun'
8
-
9
9
  require 'view_model/active_record'
10
10
 
11
11
  class ViewModel::ActiveRecord::Alias < ActiveSupport::TestCase
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
  require_relative '../../../helpers/viewmodel_spec_helpers'
6
8
 
7
- require 'minitest/autorun'
8
-
9
9
  require 'view_model/active_record'
10
10
 
11
11
  class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
3
+ require_relative '../../../test_helper'
6
4
 
7
5
  require_relative '../../../helpers/arvm_test_models'
8
6
  require_relative '../../../helpers/arvm_test_utilities'
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
3
+ require_relative '../../../test_helper'
6
4
 
7
5
  require_relative '../../../helpers/arvm_test_models'
8
6
  require_relative '../../../helpers/viewmodel_spec_helpers'
9
7
 
10
- # MiniTest::Spec.register_spec_type(/./, Minitest::HooksSpec)
8
+ # Minitest::Spec.register_spec_type(/./, Minitest::HooksSpec)
11
9
 
12
10
  require 'view_model'
13
11
  require 'view_model/active_record'
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
6
-
7
- require 'view_model'
8
- require 'view_model/active_record'
3
+ require_relative '../../../test_helper'
9
4
 
10
5
  require_relative '../../../helpers/controller_test_helpers'
11
6
  require_relative '../../../helpers/callback_tracer'
12
7
 
8
+ require 'view_model'
9
+ require 'view_model/active_record'
10
+
13
11
  class ViewModel::ActiveRecord::ControllerNestedTest < ActiveSupport::TestCase
14
12
  include ARVMTestUtilities
15
13
  include ControllerTestModels
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
6
-
7
- require 'view_model'
8
- require 'view_model/active_record'
3
+ require_relative '../../../test_helper'
9
4
 
10
5
  require_relative '../../../helpers/controller_test_helpers'
11
6
  require_relative '../../../helpers/callback_tracer'
12
7
 
8
+ require 'view_model'
9
+ require 'view_model/active_record'
10
+
13
11
  class ViewModel::ActiveRecord::ControllerTest < ActiveSupport::TestCase
14
12
  include ARVMTestUtilities
15
13
  include ControllerTestModels
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  class ViewModel::ActiveRecord::CounterTest < ActiveSupport::TestCase
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  require 'renum'
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
  require_relative '../../../helpers/viewmodel_spec_helpers'
6
8
 
7
- require 'minitest/autorun'
8
-
9
9
  require 'view_model/active_record'
10
10
 
11
11
  class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  class ViewModel::ActiveRecord::HasManyThroughPolyTest < ActiveSupport::TestCase
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  class ViewModel::ActiveRecord::HasManyThroughTest < ActiveSupport::TestCase
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
  require_relative '../../../helpers/viewmodel_spec_helpers'
6
8
 
7
- require 'minitest/autorun'
8
-
9
9
  require 'view_model/active_record'
10
10
 
11
11
  class ViewModel::ActiveRecord::HasOneTest < ActiveSupport::TestCase
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
  require_relative '../../../helpers/viewmodel_spec_helpers'
6
8
 
7
- require 'minitest/autorun'
8
-
9
9
  require 'view_model/active_record'
10
10
 
11
11
  class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
@@ -88,10 +88,18 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
88
88
 
89
89
  it 'migrates' do
90
90
  migrate!
91
-
92
91
  assert_equal(expected_result, subject)
93
92
  end
94
93
 
94
+ describe 'with strict migrations' do
95
+ it 'migrates' do
96
+ ViewModel::Config.stub(:strict_migration_versions, true) do
97
+ migrate!
98
+ end
99
+ assert_equal(expected_result, subject)
100
+ end
101
+ end
102
+
95
103
  describe 'to an unreachable version' do
96
104
  let(:migration_versions) { { viewmodel_class => 2, child_viewmodel_class => 1 } }
97
105
 
@@ -101,6 +109,30 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
101
109
  end
102
110
  end
103
111
  end
112
+
113
+ describe 'leaving a version out of the request' do
114
+ let(:migration_versions) { { viewmodel_class => 2 } }
115
+ let(:parent_only_expected_result) do
116
+ v = expected_result.deep_dup
117
+ v['data']['child'] = current_serialization['data']['child'].deep_dup
118
+ v
119
+ end
120
+
121
+ it 'migrates only the included view' do
122
+ migrate!
123
+ assert_equal(parent_only_expected_result, subject)
124
+ end
125
+
126
+ describe 'with strict migrations' do
127
+ it 'refuses to migrate' do
128
+ ViewModel::Config.stub(:strict_migration_versions, true) do
129
+ assert_raises(ViewModel::Migration::StrictMigrationError) do
130
+ migrate!
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
104
136
  end
105
137
 
106
138
  describe 'upwards' do
@@ -124,10 +156,18 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
124
156
 
125
157
  it 'migrates' do
126
158
  migrate!
127
-
128
159
  assert_equal(expected_result, subject)
129
160
  end
130
161
 
162
+ describe 'with strict migrations' do
163
+ it 'migrates' do
164
+ ViewModel::Config.stub(:strict_migration_versions, true) do
165
+ migrate!
166
+ end
167
+ assert_equal(expected_result, subject)
168
+ end
169
+ end
170
+
131
171
  describe 'with version unspecified' do
132
172
  let(:subject_data) do
133
173
  v2_serialization_data.except(ViewModel::VERSION_ATTRIBUTE)
@@ -139,6 +179,37 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
139
179
  end
140
180
  end
141
181
 
182
+ describe 'leaving a version out of the request' do
183
+ let(:migration_versions) { { viewmodel_class => 2 } }
184
+
185
+ let(:subject_data) do
186
+ v = v2_serialization_data.deep_dup
187
+ v['child'] = current_serialization['data']['child'].deep_dup
188
+ v
189
+ end
190
+
191
+ let(:parent_only_expected_result) do
192
+ v = expected_result.deep_dup
193
+ v['data']['child'] = current_serialization['data']['child'].deep_dup
194
+ v
195
+ end
196
+
197
+ it 'migrates only the included view' do
198
+ migrate!
199
+ assert_equal(parent_only_expected_result, subject)
200
+ end
201
+
202
+ describe 'with strict migrations' do
203
+ it 'refuses to migrate' do
204
+ ViewModel::Config.stub(:strict_migration_versions, true) do
205
+ assert_raises(ViewModel::Migration::StrictMigrationError) do
206
+ migrate!
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
142
213
  describe 'with a version not in the specification' do
143
214
  let(:subject_data) do
144
215
  v2_serialization_data
@@ -483,6 +554,87 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
483
554
  end
484
555
  end
485
556
 
557
+ describe 'partially migrating children' do
558
+ include ViewModelSpecHelpers::ParentAndBelongsToChild
559
+ let(:migration_versions) { { viewmodel_class => 1, child_viewmodel_class => 1 } }
560
+
561
+ # Parent 2->1 requires premigrating child 3->2
562
+ def model_attributes
563
+ child_viewmodel_class = self.child_viewmodel_class
564
+ super.merge(
565
+ viewmodel: ->(_v) {
566
+ self.schema_version = 2
567
+
568
+ # The migration from 1 -> 2 relies on a field only present in child v2
569
+ migrates from: 1, to: 2 do
570
+ down do |view, references|
571
+ child = view['child']
572
+
573
+ if child['_version'] > 2
574
+ migrator = ViewModel::DownMigrator.new({ child_viewmodel_class => 2 })
575
+ migrator.migrate!({ 'data' => child, 'references' => references.deep_dup })
576
+ end
577
+
578
+ view['child_v2_field'] = child['v2_field']
579
+ end
580
+ end
581
+ },
582
+ )
583
+ end
584
+
585
+ def child_attributes
586
+ super().merge(
587
+ viewmodel: ->(_v) {
588
+ self.schema_version = 3
589
+ migrates from: 1, to: 2 do
590
+ down do |view, _references|
591
+ view.delete('v2_field')
592
+ end
593
+ end
594
+
595
+ migrates from: 2, to: 3 do
596
+ down do |view, _references|
597
+ view['v2_field'] = 'v2_field_data'
598
+ end
599
+ end
600
+ })
601
+ end
602
+
603
+ let(:v1_serialization_data) do
604
+ {
605
+ ViewModel::TYPE_ATTRIBUTE => viewmodel_class.view_name,
606
+ ViewModel::VERSION_ATTRIBUTE => 1,
607
+ ViewModel::ID_ATTRIBUTE => viewmodel.id,
608
+ 'name' => viewmodel.name,
609
+ 'child' => {
610
+ ViewModel::TYPE_ATTRIBUTE => child_viewmodel_class.view_name,
611
+ ViewModel::VERSION_ATTRIBUTE => 1,
612
+ ViewModel::ID_ATTRIBUTE => viewmodel.child.id,
613
+ 'name' => viewmodel.child.name,
614
+ },
615
+ 'child_v2_field' => 'v2_field_data',
616
+ }
617
+ end
618
+
619
+ let(:migrator) { down_migrator }
620
+ let(:subject) do
621
+ current_serialization.deep_dup
622
+ end
623
+
624
+ let(:expected_result) do
625
+ data = v1_serialization_data.deep_dup
626
+ data[ViewModel::MIGRATED_ATTRIBUTE] = true
627
+ data['child'][ViewModel::MIGRATED_ATTRIBUTE] = true
628
+
629
+ { 'data' => data }
630
+ end
631
+
632
+ it 'migrates' do
633
+ migrate!
634
+ assert_equal(expected_result, subject)
635
+ end
636
+ end
637
+
486
638
  describe 'concurrently inserting a reference' do
487
639
  include ViewModelSpecHelpers::ReferencedList
488
640
  let(:migration_versions) { { viewmodel_class => 1 } }
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
3
+ require_relative '../../../test_helper'
6
4
 
7
5
  require_relative '../../../helpers/arvm_test_utilities'
8
6
  require_relative '../../../helpers/arvm_test_models'
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  module ViewModel::ActiveRecord::PolyTest
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  class ViewModel::ActiveRecord::SharedTest < ActiveSupport::TestCase
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../../test_helper'
4
+
3
5
  require_relative '../../../helpers/arvm_test_utilities'
4
6
  require_relative '../../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
-
8
8
  require 'view_model/active_record'
9
9
 
10
10
  class ViewModel::ActiveRecord::VersionTest < ActiveSupport::TestCase
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../test_helper'
4
+
3
5
  require_relative '../../helpers/arvm_test_utilities'
4
6
  require_relative '../../helpers/arvm_test_models'
5
7
 
6
- require 'minitest/autorun'
7
- require 'minitest/unit'
8
-
9
8
  require 'view_model/active_record'
10
9
 
11
10
  class ViewModel::ActiveRecordTest < ActiveSupport::TestCase
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../test_helper'
4
+
3
5
  require_relative '../../helpers/arvm_test_utilities'
4
6
  require_relative '../../helpers/arvm_test_models'
5
7
  require_relative '../../helpers/callback_tracer'
6
8
  require_relative '../../helpers/viewmodel_spec_helpers'
7
9
 
8
- require 'minitest/autorun'
9
-
10
10
  require 'view_model/active_record'
11
11
 
12
12
  class ViewModel::CallbacksTest < ActiveSupport::TestCase
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
3
+ require_relative '../../test_helper'
6
4
 
7
5
  require_relative '../../helpers/arvm_test_utilities'
8
6
  require_relative '../../helpers/viewmodel_spec_helpers'
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'rspec/expectations/minitest_integration'
3
+ require_relative '../../../test_helper'
6
4
 
7
5
  require 'view_model'
8
6
  require 'view_model/deserialization_error'
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
6
- require 'rspec/expectations/minitest_integration'
3
+ require_relative '../../test_helper'
7
4
 
8
5
  require_relative '../../helpers/arvm_test_utilities'
9
6
  require_relative '../../helpers/arvm_test_models'
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
3
+ require_relative '../../test_helper'
5
4
 
6
5
  require 'view_model'
7
6
  require 'view_model/garbage_collection'
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../helpers/test_access_control'
3
+ require_relative '../../test_helper'
4
4
 
5
- require 'minitest/autorun'
6
- require 'minitest/unit'
5
+ require_relative '../../helpers/test_access_control'
7
6
 
8
7
  require 'view_model'
9
8
  require 'view_model/record'
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
3
+ require_relative '../../test_helper'
6
4
 
7
5
  require_relative '../../helpers/arvm_test_utilities'
8
6
  require_relative '../../helpers/arvm_test_models'
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'minitest/autorun'
4
- require 'minitest/unit'
5
- require 'minitest/hooks'
3
+ require_relative "../../test_helper"
6
4
 
7
5
  require_relative '../../helpers/match_enumerator'
8
6
  require_relative '../../helpers/arvm_test_utilities'
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup'
4
- Bundler.require
5
-
6
- require 'minitest/autorun'
3
+ require_relative '../test_helper'
7
4
 
8
5
  class DefaultViewModel < ViewModel
9
6
  self.view_name = 'DefaultViewModel'
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.4
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: 2026-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -290,6 +290,20 @@ dependencies:
290
290
  - - ">="
291
291
  - !ruby/object:Gem::Version
292
292
  version: '0'
293
+ - !ruby/object:Gem::Dependency
294
+ name: minitest
295
+ requirement: !ruby/object:Gem::Requirement
296
+ requirements:
297
+ - - "~>"
298
+ - !ruby/object:Gem::Version
299
+ version: 6.0.0
300
+ type: :development
301
+ prerelease: false
302
+ version_requirements: !ruby/object:Gem::Requirement
303
+ requirements:
304
+ - - "~>"
305
+ - !ruby/object:Gem::Version
306
+ version: 6.0.0
293
307
  - !ruby/object:Gem::Dependency
294
308
  name: minitest-hooks
295
309
  requirement: !ruby/object:Gem::Requirement
@@ -304,6 +318,34 @@ dependencies:
304
318
  - - ">="
305
319
  - !ruby/object:Gem::Version
306
320
  version: '0'
321
+ - !ruby/object:Gem::Dependency
322
+ name: minitest-mock
323
+ requirement: !ruby/object:Gem::Requirement
324
+ requirements:
325
+ - - ">="
326
+ - !ruby/object:Gem::Version
327
+ version: '0'
328
+ type: :development
329
+ prerelease: false
330
+ version_requirements: !ruby/object:Gem::Requirement
331
+ requirements:
332
+ - - ">="
333
+ - !ruby/object:Gem::Version
334
+ version: '0'
335
+ - !ruby/object:Gem::Dependency
336
+ name: minitest-reporters
337
+ requirement: !ruby/object:Gem::Requirement
338
+ requirements:
339
+ - - ">="
340
+ - !ruby/object:Gem::Version
341
+ version: '0'
342
+ type: :development
343
+ prerelease: false
344
+ version_requirements: !ruby/object:Gem::Requirement
345
+ requirements:
346
+ - - ">="
347
+ - !ruby/object:Gem::Version
348
+ version: '0'
307
349
  - !ruby/object:Gem::Dependency
308
350
  name: pg
309
351
  requirement: !ruby/object:Gem::Requirement
@@ -392,7 +434,6 @@ files:
392
434
  - LICENSE.txt
393
435
  - README.md
394
436
  - Rakefile
395
- - gemfiles/rails_6_1.gemfile
396
437
  - gemfiles/rails_7_0.gemfile
397
438
  - gemfiles/rails_7_1.gemfile
398
439
  - iknow_view_models.gemspec
@@ -437,6 +478,7 @@ files:
437
478
  - lib/view_model/migration.rb
438
479
  - lib/view_model/migration/no_path_error.rb
439
480
  - lib/view_model/migration/one_way_error.rb
481
+ - lib/view_model/migration/strict_migration_error.rb
440
482
  - lib/view_model/migration/unspecified_version_error.rb
441
483
  - lib/view_model/migrator.rb
442
484
  - lib/view_model/record.rb
@@ -465,6 +507,7 @@ files:
465
507
  - test/helpers/query_logging.rb
466
508
  - test/helpers/test_access_control.rb
467
509
  - test/helpers/viewmodel_spec_helpers.rb
510
+ - test/test_helper.rb
468
511
  - test/unit/view_model/access_control_test.rb
469
512
  - test/unit/view_model/active_record/alias_test.rb
470
513
  - test/unit/view_model/active_record/belongs_to_test.rb
@@ -528,6 +571,7 @@ test_files:
528
571
  - test/helpers/query_logging.rb
529
572
  - test/helpers/test_access_control.rb
530
573
  - test/helpers/viewmodel_spec_helpers.rb
574
+ - test/test_helper.rb
531
575
  - test/unit/view_model/access_control_test.rb
532
576
  - test/unit/view_model/active_record/alias_test.rb
533
577
  - test/unit/view_model/active_record/belongs_to_test.rb
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source 'https://rubygems.org'
4
-
5
- gem 'minitest-ci'
6
- gem 'activerecord', '~> 6.1.0'
7
- gem 'activesupport', '~> 6.1.0'
8
- gem 'actionpack', '~> 6.1.0'
9
-
10
- gemspec path: '../'