iknow_view_models 3.7.0 → 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
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