iknow_view_models 3.1.8 → 3.2.0

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.
@@ -34,7 +34,7 @@ class ViewModel::ActiveRecord
34
34
  # Defines a cacheable parent Model with a owned Child and a cachable shared Shared.
35
35
  module CacheableParentAndChildren
36
36
  extend ActiveSupport::Concern
37
- include ViewModelSpecHelpers::ParentAndBelongsToChild
37
+ include ViewModelSpecHelpers::ParentAndBelongsToChildWithMigration
38
38
 
39
39
  def model_attributes
40
40
  super.merge(
@@ -43,7 +43,7 @@ class ViewModel::ActiveRecord
43
43
  viewmodel: ->(_) {
44
44
  association :shared
45
45
  cacheable!
46
- }
46
+ },
47
47
  )
48
48
  end
49
49
 
@@ -64,8 +64,18 @@ class ViewModel::ActiveRecord
64
64
 
65
65
  define_viewmodel do
66
66
  root!
67
+ self.schema_version = 2
67
68
  attributes :name
68
69
  cacheable!(cache_group: shared_cache_group)
70
+ migrates from: 1, to: 2 do
71
+ up do |view, _references|
72
+ view['name'] = view.delete('old_name')
73
+ end
74
+
75
+ down do |view, _references|
76
+ view['old_name'] = view.delete('name')
77
+ end
78
+ end
69
79
  end
70
80
  end
71
81
  end
@@ -91,10 +101,17 @@ class ViewModel::ActiveRecord
91
101
  DUMMY_RAILS_CACHE.clear
92
102
  end
93
103
 
104
+ # Request serializations to be migrated to the specified versions
105
+ let(:migration_versions) { {} }
106
+
94
107
  # Extract the iKnowCaches to verify their contents
95
108
  def read_cache(viewmodel_class, id)
96
109
  vm_cache = viewmodel_class.viewmodel_cache
97
- vm_cache.send(:cache).read(vm_cache.key_for(id))
110
+ cache_migration_version = vm_cache.migrated_cache_version(migration_versions)
111
+
112
+ key = vm_cache.key_for(id, cache_migration_version)
113
+ iknow_cache = vm_cache.cache_for(cache_migration_version)
114
+ iknow_cache.read(key)
98
115
  end
99
116
 
100
117
  def serialize_from_database
@@ -102,6 +119,12 @@ class ViewModel::ActiveRecord
102
119
  context = viewmodel_class.new_serialize_context
103
120
  data = ViewModel.serialize_to_hash([view], serialize_context: context)
104
121
  refs = context.serialize_references_to_hash
122
+
123
+ if migration_versions.present?
124
+ migrator = ViewModel::DownMigrator.new(migration_versions)
125
+ migrator.migrate!([data, refs], references: refs)
126
+ end
127
+
105
128
  [data, refs]
106
129
  end
107
130
 
@@ -113,7 +136,7 @@ class ViewModel::ActiveRecord
113
136
  end
114
137
 
115
138
  def fetch_with_cache
116
- viewmodel_class.viewmodel_cache.fetch([root.id])
139
+ viewmodel_class.viewmodel_cache.fetch([root.id], migration_versions: migration_versions)
117
140
  end
118
141
 
119
142
  def serialize_with_cache
@@ -179,7 +202,20 @@ class ViewModel::ActiveRecord
179
202
 
180
203
  describe 'with owned and shared children' do
181
204
  include CacheableParentAndChildren
182
- include BehavesLikeACache
205
+
206
+ describe 'without migrations' do
207
+ include BehavesLikeACache
208
+ end
209
+
210
+ describe 'with migrations' do
211
+ let(:migration_versions) { { viewmodel_class => 1, child_viewmodel_class => 2 } }
212
+ include BehavesLikeACache
213
+ end
214
+
215
+ describe 'with shared migrations' do
216
+ let(:migration_versions) { { shared_viewmodel_class => 1 } }
217
+ include BehavesLikeACache
218
+ end
183
219
 
184
220
  describe 'with a record in the cache' do
185
221
  # Fetch the root record to ensure it's in the cache
@@ -109,6 +109,24 @@ class ViewModel::ActiveRecord::ControllerTest < ActiveSupport::TestCase
109
109
  assert_all_hooks_nested_inside_parent_hook(parentcontroller.hook_trace)
110
110
  end
111
111
 
112
+ def test_migrated_show
113
+ parentcontroller = ParentController.new(id: @parent.id, versions: { ParentView.view_name => 1 })
114
+ parentcontroller.invoke(:show)
115
+
116
+ expected_view = @parent_view.to_hash
117
+ .except('name')
118
+ .merge('old_name' => @parent.name,
119
+ ViewModel::VERSION_ATTRIBUTE => 1,
120
+ ViewModel::MIGRATED_ATTRIBUTE => true)
121
+
122
+ assert_equal({ 'data' => expected_view },
123
+ parentcontroller.hash_response)
124
+
125
+ assert_equal(200, parentcontroller.status)
126
+
127
+ assert_all_hooks_nested_inside_parent_hook(parentcontroller.hook_trace)
128
+ end
129
+
112
130
  def test_index
113
131
  p2 = Parent.create(name: "p2")
114
132
  p2_view = ParentView.new(p2)
@@ -148,6 +166,22 @@ class ViewModel::ActiveRecord::ControllerTest < ActiveSupport::TestCase
148
166
  assert_all_hooks_nested_inside_parent_hook(parentcontroller.hook_trace)
149
167
  end
150
168
 
169
+ def test_migrated_create
170
+ data = {
171
+ '_type' => 'Parent',
172
+ '_version' => 1,
173
+ 'old_name' => 'p2',
174
+ }
175
+
176
+ parentcontroller = ParentController.new(data: data, versions: { ParentView.view_name => 1 })
177
+ parentcontroller.invoke(:create)
178
+
179
+ assert_equal(200, parentcontroller.status)
180
+
181
+ p2 = Parent.where(name: 'p2').first
182
+ assert(p2.present?, 'p2 created')
183
+ end
184
+
151
185
  def test_create_empty
152
186
  parentcontroller = ParentController.new(data: [])
153
187
  parentcontroller.invoke(:create)
@@ -0,0 +1,161 @@
1
+ require_relative "../../../helpers/arvm_test_utilities.rb"
2
+ require_relative "../../../helpers/arvm_test_models.rb"
3
+ require_relative "../../../helpers/viewmodel_spec_helpers.rb"
4
+
5
+ require "minitest/autorun"
6
+
7
+ require "view_model/active_record"
8
+
9
+ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
10
+ include ARVMTestUtilities
11
+ extend Minitest::Spec::DSL
12
+
13
+ include ViewModelSpecHelpers::ParentAndBelongsToChildWithMigration
14
+
15
+ def new_model
16
+ model_class.new(name: 'm1', child: child_model_class.new(name: 'c1'))
17
+ end
18
+
19
+ let(:viewmodel) { create_viewmodel! }
20
+
21
+ let(:current_serialization) { ViewModel.serialize_to_hash(viewmodel) }
22
+
23
+ let(:v2_serialization) do
24
+ {
25
+ ViewModel::TYPE_ATTRIBUTE => viewmodel_class.view_name,
26
+ ViewModel::VERSION_ATTRIBUTE => 2,
27
+ ViewModel::ID_ATTRIBUTE => viewmodel.id,
28
+ 'name' => viewmodel.name,
29
+ 'old_field' => 1,
30
+ 'child' => {
31
+ ViewModel::TYPE_ATTRIBUTE => child_viewmodel_class.view_name,
32
+ ViewModel::VERSION_ATTRIBUTE => 2,
33
+ ViewModel::ID_ATTRIBUTE => viewmodel.child.id,
34
+ 'name' => viewmodel.child.name,
35
+ 'former_field' => 'former_value',
36
+ }
37
+ }
38
+ end
39
+
40
+ let(:migration_versions) { { viewmodel_class => 2, child_viewmodel_class => 2 } }
41
+
42
+ let(:down_migrator) { ViewModel::DownMigrator.new(migration_versions) }
43
+ let(:up_migrator) { ViewModel::UpMigrator.new(migration_versions) }
44
+
45
+ def migrate!
46
+ migrator.migrate!(subject, references: {})
47
+ end
48
+
49
+
50
+ describe 'downwards' do
51
+ let(:migrator) { down_migrator }
52
+ let(:subject) { current_serialization.deep_dup }
53
+
54
+ let(:expected_result) do
55
+ v2_serialization.deep_merge(
56
+ {
57
+ ViewModel::MIGRATED_ATTRIBUTE => true,
58
+ 'old_field' => -1,
59
+ 'child' => {
60
+ ViewModel::MIGRATED_ATTRIBUTE => true,
61
+ 'former_field' => 'reconstructed'
62
+ }
63
+ }
64
+ )
65
+ end
66
+
67
+ it 'migrates' do
68
+ migrate!
69
+
70
+ assert_equal(expected_result, subject)
71
+ end
72
+
73
+
74
+ describe 'to an unreachable version' do
75
+ let(:migration_versions) { { viewmodel_class => 2, child_viewmodel_class => 1 } }
76
+
77
+ it 'raises' do
78
+ assert_raises(ViewModel::Migration::NoPathError) do
79
+ migrate!
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ describe 'upwards' do
86
+ let(:migrator) { up_migrator }
87
+ let(:subject) { v2_serialization.deep_dup }
88
+
89
+ let(:expected_result) do
90
+ current_serialization.deep_merge(
91
+ ViewModel::MIGRATED_ATTRIBUTE => true,
92
+ 'new_field' => 3,
93
+ 'child' => {
94
+ ViewModel::MIGRATED_ATTRIBUTE => true,
95
+ }
96
+ )
97
+ end
98
+
99
+ it 'migrates' do
100
+ migrate!
101
+
102
+ assert_equal(expected_result, subject)
103
+ end
104
+
105
+ describe 'with version unspecified' do
106
+ let(:subject) do
107
+ v2_serialization
108
+ .except(ViewModel::VERSION_ATTRIBUTE)
109
+ end
110
+
111
+ it 'treats it as the requested version' do
112
+ migrate!
113
+ assert_equal(expected_result, subject)
114
+ end
115
+ end
116
+
117
+ describe 'with a version not in the specification' do
118
+ let(:subject) do
119
+ v2_serialization
120
+ .except('old_field')
121
+ .deep_merge(ViewModel::VERSION_ATTRIBUTE => 3, 'mid_field' => 1)
122
+ end
123
+
124
+ it 'rejects it' do
125
+ assert_raises(ViewModel::Migration::UnspecifiedVersionError) do
126
+ migrate!
127
+ end
128
+ end
129
+ end
130
+
131
+ describe 'from an unreachable version' do
132
+ let(:migration_versions) { { viewmodel_class => 2, child_viewmodel_class => 1 } }
133
+
134
+ let(:subject) do
135
+ v2_serialization.deep_merge(
136
+ 'child' => { ViewModel::VERSION_ATTRIBUTE => 1 }
137
+ )
138
+ end
139
+
140
+ it 'raises' do
141
+ assert_raises(ViewModel::Migration::NoPathError) do
142
+ migrate!
143
+ end
144
+ end
145
+ end
146
+
147
+ describe 'in an undefined direction' do
148
+ let(:migration_versions) { { viewmodel_class => 1, child_viewmodel_class => 2 } }
149
+
150
+ let(:subject) do
151
+ v2_serialization.except('old_field').merge(ViewModel::VERSION_ATTRIBUTE => 1)
152
+ end
153
+
154
+ it 'raises' do
155
+ assert_raises(ViewModel::Migration::OneWayError) do
156
+ migrate!
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
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.1.8
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - iKnow Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-26 00:00:00.000000000 Z
11
+ date: 2020-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - ">="
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: rgl
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: appraisal
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -280,16 +294,16 @@ dependencies:
280
294
  name: pg
281
295
  requirement: !ruby/object:Gem::Requirement
282
296
  requirements:
283
- - - "~>"
297
+ - - ">="
284
298
  - !ruby/object:Gem::Version
285
- version: '0.18'
299
+ version: '0'
286
300
  type: :development
287
301
  prerelease: false
288
302
  version_requirements: !ruby/object:Gem::Requirement
289
303
  requirements:
290
- - - "~>"
304
+ - - ">="
291
305
  - !ruby/object:Gem::Version
292
- version: '0.18'
306
+ version: '0'
293
307
  - !ruby/object:Gem::Dependency
294
308
  name: pry
295
309
  requirement: !ruby/object:Gem::Requirement
@@ -357,15 +371,14 @@ files:
357
371
  - ".envrc"
358
372
  - ".gitignore"
359
373
  - ".idea/codeStyleSettings.xml"
360
- - ".travis.yml"
374
+ - ".rubocop.yml"
361
375
  - Appraisals
362
376
  - Gemfile
363
377
  - LICENSE.txt
364
378
  - README.md
365
379
  - Rakefile
366
- - appveyor.yml
367
380
  - gemfiles/rails_5_2.gemfile
368
- - gemfiles/rails_6_0_beta.gemfile
381
+ - gemfiles/rails_6_0.gemfile
369
382
  - iknow_view_models.gemspec
370
383
  - lib/iknow_view_models.rb
371
384
  - lib/iknow_view_models/railtie.rb
@@ -401,6 +414,12 @@ files:
401
414
  - lib/view_model/deserialize_context.rb
402
415
  - lib/view_model/error.rb
403
416
  - lib/view_model/error_view.rb
417
+ - lib/view_model/migratable_view.rb
418
+ - lib/view_model/migration.rb
419
+ - lib/view_model/migration/no_path_error.rb
420
+ - lib/view_model/migration/one_way_error.rb
421
+ - lib/view_model/migration/unspecified_version_error.rb
422
+ - lib/view_model/migrator.rb
404
423
  - lib/view_model/record.rb
405
424
  - lib/view_model/record/attribute_data.rb
406
425
  - lib/view_model/reference.rb
@@ -414,6 +433,7 @@ files:
414
433
  - lib/view_model/traversal_context.rb
415
434
  - lib/view_model/utils.rb
416
435
  - lib/view_model/utils/collections.rb
436
+ - nix/dependencies.nix
417
437
  - nix/gem/generate.rb
418
438
  - shell.nix
419
439
  - test/config/database.yml
@@ -437,6 +457,7 @@ files:
437
457
  - test/unit/view_model/active_record/has_many_through_poly_test.rb
438
458
  - test/unit/view_model/active_record/has_many_through_test.rb
439
459
  - test/unit/view_model/active_record/has_one_test.rb
460
+ - test/unit/view_model/active_record/migration_test.rb
440
461
  - test/unit/view_model/active_record/namespacing_test.rb
441
462
  - test/unit/view_model/active_record/poly_test.rb
442
463
  - test/unit/view_model/active_record/shared_test.rb
@@ -494,6 +515,7 @@ test_files:
494
515
  - test/unit/view_model/active_record/has_many_through_poly_test.rb
495
516
  - test/unit/view_model/active_record/has_many_through_test.rb
496
517
  - test/unit/view_model/active_record/has_one_test.rb
518
+ - test/unit/view_model/active_record/migration_test.rb
497
519
  - test/unit/view_model/active_record/namespacing_test.rb
498
520
  - test/unit/view_model/active_record/poly_test.rb
499
521
  - test/unit/view_model/active_record/shared_test.rb
@@ -1,31 +0,0 @@
1
- dist: trusty
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
-
6
- rvm:
7
- - 2.5
8
-
9
- gemfile:
10
- - gemfiles/rails_5_2.gemfile
11
-
12
- addons:
13
- postgresql: "10"
14
- apt:
15
- packages:
16
- - postgresql-10
17
- - postgresql-client-10
18
- - postgresql-server-dev-10
19
- env:
20
- global:
21
- - PGPORT=5433
22
-
23
- before_install:
24
- - gem update --system
25
- - gem install bundler
26
-
27
- before_script:
28
- - psql -c 'CREATE DATABASE iknow_view_models;'
29
-
30
- notifications:
31
- email: false