iknow_view_models 3.7.0 → 3.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b0920a06f9988d3cc846a4bf1fb592525891adb09ba44e3e7ccded40ed18c2b
4
- data.tar.gz: 70267048408dbef5eaeb162465ffb2583e93fcd562f5d75d38c0ea7c7dca096d
3
+ metadata.gz: 1fb4421931099dc325d159573afcd9ea3529f6e29b4ac80f60d3628c9812df4c
4
+ data.tar.gz: 97d730c4d0a28ec9ce257906f69a1dd79e1b99c5fe025067ad57ba4ca9774931
5
5
  SHA512:
6
- metadata.gz: a47fe00e8aaac20252a8508438adcf916946f4f5e4e34beabf342d530a30e5b9dbd1837c3422cd38fec8c60ecb0c0e95d1aae76a45b47fde8a66340b5346c3b4
7
- data.tar.gz: d327bfab6f0c9a985aa4b44995deb51879b9e854d3bec028ebe48dc3eedd4d14158a20e960f16ab7dfcffb80d3ce4d11979f2fb46833e91a07be3f23d056fbc3
6
+ metadata.gz: 53aff699c92efd1c334333b2f417444498c504b2f91ea57a00741e3e6e1e259c988723b23e68a7f2c16fba1a6906786471748f5adf5dc2797ca2a4d0924f10df
7
+ data.tar.gz: b739f19369e3282f6e897a000f04bb56b432796acbe78ca70777e349eef8f39a4058488032f4b42eeda0c4c7c4f60e9b4dba1fba1f2cf253a3c2bd960ff753e3
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IknowViewModels
4
- VERSION = '3.7.0'
4
+ VERSION = '3.7.1'
5
5
  end
@@ -39,7 +39,26 @@ class ViewModel
39
39
  def migrate!(serialization)
40
40
  references = (serialization['references'] ||= {})
41
41
 
42
- migrate_tree!(serialization, references: references)
42
+ # First visit everything except references; there's no issue with adding
43
+ # new references during this.
44
+ migrate_tree!(serialization.except('references'), references: references)
45
+
46
+ # While visiting references itself, we need to take care that we can
47
+ # concurrently modify them (e.g. by adding new referenced views).
48
+ # Moreover, such added references must themselves be visited, as they'll
49
+ # be synthesized at the current version and so may need to be migrated
50
+ # down to the client's requested version.
51
+ visited_refs = []
52
+ loop do
53
+ unvisited_refs = references.keys - visited_refs
54
+ break if unvisited_refs.empty?
55
+
56
+ unvisited_refs.each do |ref|
57
+ migrate_tree!(references[ref], references: references)
58
+ end
59
+
60
+ visited_refs.concat(unvisited_refs)
61
+ end
43
62
 
44
63
  GarbageCollection.garbage_collect_references!(serialization)
45
64
 
@@ -112,10 +131,11 @@ class ViewModel
112
131
  path = @paths[view_name]
113
132
  return false unless path
114
133
 
115
- # We assume that an unspecified source version is the same as the required
116
- # version.
117
134
  required_version, current_version = @versions[view_name]
135
+ return false if source_version == current_version
118
136
 
137
+ # We assume that an unspecified source version is the same as the required
138
+ # version (i.e. the version demanded by the client request).
119
139
  unless source_version.nil? || source_version == required_version
120
140
  raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
121
141
  end
@@ -29,5 +29,13 @@ class ViewModel
29
29
  def hash
30
30
  [viewmodel_class, model_id].hash
31
31
  end
32
+
33
+ # Generate a stable reference key for this viewmodel using type name and id
34
+ def stable_reference
35
+ raise RuntimeError.new('Model id required to generate a stable reference') unless model_id
36
+
37
+ hash = Digest::SHA256.base64digest("#{viewmodel_class.name}.#{model_id}")
38
+ "ref:h:#{hash}"
39
+ end
32
40
  end
33
41
  end
@@ -37,12 +37,13 @@ class ViewModel
37
37
 
38
38
  private
39
39
 
40
- # Ensure stable reference ids for the same (persisted) viewmodels.
40
+ # Ensure stable reference keys for the same (persisted) viewmodels. For
41
+ # unpersisted viewmodels, use a counter to generate a reference key unique
42
+ # to this serialization.
41
43
  def new_ref!(viewmodel)
42
44
  vm_ref = viewmodel.to_reference
43
45
  if vm_ref.model_id
44
- hash = Digest::SHA256.base64digest("#{vm_ref.viewmodel_class.name}.#{vm_ref.model_id}")
45
- "ref:h:#{hash}"
46
+ vm_ref.stable_reference
46
47
  else
47
48
  format('ref:i:%06<count>d', count: (@last_ref += 1))
48
49
  end
@@ -295,6 +295,14 @@ module ViewModelSpecHelpers
295
295
  end
296
296
  end
297
297
 
298
+ module ReferencedList
299
+ extend ActiveSupport::Concern
300
+ include ViewModelSpecHelpers::List
301
+ def model_attributes
302
+ super.merge(viewmodel: ->(_v) { root! })
303
+ end
304
+ end
305
+
298
306
  module ParentAndHasOneChild
299
307
  extend ActiveSupport::Concern
300
308
  include ViewModelSpecHelpers::Base
@@ -436,16 +436,17 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
436
436
  let(:migrator) { down_migrator }
437
437
  let(:subject) do
438
438
  ser = current_serialization.deep_dup
439
- raise ArgumentError.new("Expected no references") if ser.has_key?('references')
439
+ raise ArgumentError.new('Expected no references') if ser.has_key?('references')
440
+
440
441
  ser
441
442
  end
442
443
 
443
444
  let(:expected_result) do
444
445
  {
445
446
  'data' => v1_serialization_data.deep_dup.deep_merge(
446
- { ViewModel::MIGRATED_ATTRIBUTE => true }
447
+ { ViewModel::MIGRATED_ATTRIBUTE => true },
447
448
  ),
448
- 'references' => v1_serialization_references
449
+ 'references' => v1_serialization_references,
449
450
  }
450
451
  end
451
452
 
@@ -460,7 +461,8 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
460
461
  let(:migrator) { up_migrator }
461
462
  let(:subject) do
462
463
  ser = v1_serialization.deep_dup
463
- raise ArgumentError.new("Expected references") unless ser.has_key?('references')
464
+ raise ArgumentError.new('Expected references') unless ser.has_key?('references')
465
+
464
466
  ser
465
467
  end
466
468
 
@@ -480,4 +482,96 @@ class ViewModel::ActiveRecord::Migration < ActiveSupport::TestCase
480
482
  end
481
483
  end
482
484
  end
485
+
486
+ describe 'concurrently inserting a reference' do
487
+ include ViewModelSpecHelpers::ReferencedList
488
+ let(:migration_versions) { { viewmodel_class => 1 } }
489
+
490
+ # Use a list with two members
491
+ def new_model
492
+ model_class.new(name: 'root',
493
+ next: model_class.new(name: 'old-tail'))
494
+ end
495
+
496
+ # Define a down migration that matches the old tail to insert a new tail
497
+ # after it, and the new tail to change its name.
498
+ def model_attributes
499
+ super.merge(
500
+ viewmodel: ->(v) {
501
+ self.schema_version = 2
502
+
503
+ migrates from: 1, to: 2 do
504
+ down do |view, refs|
505
+ case view['name']
506
+ when 'old-tail'
507
+ view['next'] = { ViewModel::REFERENCE_ATTRIBUTE => 'ref:s:new_tail' }
508
+ refs['ref:s:new_tail'] = {
509
+ ViewModel::TYPE_ATTRIBUTE => v.view_name,
510
+ ViewModel::VERSION_ATTRIBUTE => v.schema_version,
511
+ 'id' => 100, # entirely fake
512
+ 'name' => 'new-tail',
513
+ 'next' => nil,
514
+ }
515
+
516
+ when 'new-tail'
517
+ view['name'] = 'newer-tail'
518
+ end
519
+ end
520
+ end
521
+ },
522
+ )
523
+ end
524
+
525
+ let(:v1_serialization_data) do
526
+ {
527
+ ViewModel::TYPE_ATTRIBUTE => viewmodel_class.view_name,
528
+ ViewModel::VERSION_ATTRIBUTE => 1,
529
+ ViewModel::ID_ATTRIBUTE => viewmodel.id,
530
+ 'name' => viewmodel.name,
531
+ 'next' => { ViewModel::REFERENCE_ATTRIBUTE => viewmodel.next.to_reference.stable_reference },
532
+ ViewModel::MIGRATED_ATTRIBUTE => true,
533
+ }
534
+ end
535
+
536
+ let(:v1_serialization_references) do
537
+ old_tail = viewmodel.next
538
+ old_tail_ref = old_tail.to_reference.stable_reference
539
+ {
540
+ old_tail_ref => {
541
+ ViewModel::TYPE_ATTRIBUTE => viewmodel_class.view_name,
542
+ ViewModel::VERSION_ATTRIBUTE => 1,
543
+ ViewModel::ID_ATTRIBUTE => old_tail.id,
544
+ 'name' => 'old-tail',
545
+ 'next' => { ViewModel::REFERENCE_ATTRIBUTE => 'ref:s:new_tail' },
546
+ ViewModel::MIGRATED_ATTRIBUTE => true,
547
+ },
548
+ 'ref:s:new_tail' => {
549
+ ViewModel::TYPE_ATTRIBUTE => viewmodel_class.view_name,
550
+ ViewModel::VERSION_ATTRIBUTE => 1,
551
+ ViewModel::ID_ATTRIBUTE => 100,
552
+ 'name' => 'newer-tail',
553
+ 'next' => nil,
554
+ ViewModel::MIGRATED_ATTRIBUTE => true,
555
+ },
556
+ }
557
+ end
558
+
559
+ let(:v1_serialization) do
560
+ {
561
+ 'data' => v1_serialization_data,
562
+ 'references' => v1_serialization_references,
563
+ }
564
+ end
565
+
566
+ describe 'downwards' do
567
+ let(:migrator) { down_migrator }
568
+ let(:subject) { current_serialization.deep_dup }
569
+
570
+ it 'migrates' do
571
+ migrate!
572
+
573
+ assert_equal(v1_serialization, subject)
574
+ end
575
+ end
576
+ end
483
577
  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.7.0
4
+ version: 3.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - iKnow Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-10 00:00:00.000000000 Z
11
+ date: 2022-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack