iknow_view_models 2.10.1 → 3.0.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.
- checksums.yaml +4 -4
 - data/.circleci/config.yml +119 -0
 - data/.travis.yml +31 -0
 - data/Appraisals +6 -16
 - data/gemfiles/{rails_7_0.gemfile → rails_6_0_beta.gemfile} +2 -2
 - data/iknow_view_models.gemspec +3 -5
 - data/lib/iknow_view_models/version.rb +1 -1
 - data/lib/view_model/active_record/association_data.rb +206 -92
 - data/lib/view_model/active_record/association_manipulation.rb +22 -12
 - data/lib/view_model/active_record/cache/cacheable_view.rb +3 -13
 - data/lib/view_model/active_record/cache.rb +2 -2
 - data/lib/view_model/active_record/cloner.rb +11 -11
 - data/lib/view_model/active_record/controller.rb +0 -2
 - data/lib/view_model/active_record/update_context.rb +21 -3
 - data/lib/view_model/active_record/update_data.rb +43 -45
 - data/lib/view_model/active_record/update_operation.rb +265 -153
 - data/lib/view_model/active_record/visitor.rb +9 -6
 - data/lib/view_model/active_record.rb +94 -74
 - data/lib/view_model/after_transaction_runner.rb +3 -18
 - data/lib/view_model/callbacks.rb +2 -2
 - data/lib/view_model/changes.rb +24 -16
 - data/lib/view_model/config.rb +6 -2
 - data/lib/view_model/deserialization_error.rb +31 -0
 - data/lib/view_model/deserialize_context.rb +2 -6
 - data/lib/view_model/error_view.rb +6 -5
 - data/lib/view_model/record/attribute_data.rb +11 -6
 - data/lib/view_model/record.rb +44 -24
 - data/lib/view_model/serialize_context.rb +2 -63
 - data/lib/view_model/test_helpers/arvm_builder.rb +2 -4
 - data/lib/view_model/traversal_context.rb +2 -2
 - data/lib/view_model.rb +21 -13
 - data/shell.nix +1 -1
 - data/test/helpers/arvm_test_models.rb +4 -12
 - data/test/helpers/arvm_test_utilities.rb +6 -0
 - data/test/helpers/controller_test_helpers.rb +6 -6
 - data/test/helpers/viewmodel_spec_helpers.rb +63 -52
 - data/test/unit/view_model/access_control_test.rb +88 -37
 - data/test/unit/view_model/active_record/belongs_to_test.rb +110 -178
 - data/test/unit/view_model/active_record/cache_test.rb +11 -5
 - data/test/unit/view_model/active_record/cloner_test.rb +1 -1
 - data/test/unit/view_model/active_record/controller_test.rb +12 -20
 - data/test/unit/view_model/active_record/has_many_test.rb +540 -316
 - data/test/unit/view_model/active_record/has_many_through_poly_test.rb +12 -15
 - data/test/unit/view_model/active_record/has_many_through_test.rb +15 -58
 - data/test/unit/view_model/active_record/has_one_test.rb +288 -135
 - data/test/unit/view_model/active_record/poly_test.rb +0 -1
 - data/test/unit/view_model/active_record/shared_test.rb +21 -39
 - data/test/unit/view_model/active_record/version_test.rb +3 -2
 - data/test/unit/view_model/active_record_test.rb +5 -63
 - data/test/unit/view_model/callbacks_test.rb +1 -0
 - data/test/unit/view_model/record_test.rb +0 -32
 - data/test/unit/view_model/traversal_context_test.rb +13 -12
 - metadata +15 -25
 - data/.github/workflows/gem-push.yml +0 -31
 - data/.github/workflows/test.yml +0 -65
 - data/gemfiles/rails_6_0.gemfile +0 -9
 - data/gemfiles/rails_6_1.gemfile +0 -9
 - data/test/unit/view_model/active_record/optional_attribute_view_test.rb +0 -58
 
| 
         @@ -12,17 +12,7 @@ module ViewModel::ActiveRecord::Cache::CacheableView 
     | 
|
| 
       12 
12 
     | 
    
         
             
              CacheClearer = Struct.new(:cache, :id) do
         
     | 
| 
       13 
13 
     | 
    
         
             
                include ViewModel::AfterTransactionRunner
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
       16 
     | 
    
         
            -
                # on database locking to prevent cache race conditions. We require
         
     | 
| 
       17 
     | 
    
         
            -
                # reading/refreshing the cache to obtain a FOR SHARE lock, which means that
         
     | 
| 
       18 
     | 
    
         
            -
                # a reader must wait for a concurrent writer to commit before continuing to
         
     | 
| 
       19 
     | 
    
         
            -
                # the cache. If the writer cleared the cache after commit, the reader could
         
     | 
| 
       20 
     | 
    
         
            -
                # obtain old data before the clear, and then save the old data after it.
         
     | 
| 
       21 
     | 
    
         
            -
                def before_commit
         
     | 
| 
       22 
     | 
    
         
            -
                  cache.delete(id)
         
     | 
| 
       23 
     | 
    
         
            -
                end
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                def after_rollback
         
     | 
| 
      
 15 
     | 
    
         
            +
                def after_transaction
         
     | 
| 
       26 
16 
     | 
    
         
             
                  cache.delete(id)
         
     | 
| 
       27 
17 
     | 
    
         
             
                end
         
     | 
| 
       28 
18 
     | 
    
         | 
| 
         @@ -49,12 +39,12 @@ module ViewModel::ActiveRecord::Cache::CacheableView 
     | 
|
| 
       49 
39 
     | 
    
         
             
                end
         
     | 
| 
       50 
40 
     | 
    
         
             
              end
         
     | 
| 
       51 
41 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
              # Clear the cache if the view or its  
     | 
| 
      
 42 
     | 
    
         
            +
              # Clear the cache if the view or its nested children were changed during
         
     | 
| 
       53 
43 
     | 
    
         
             
              # deserialization
         
     | 
| 
       54 
44 
     | 
    
         
             
              def after_deserialize(deserialize_context:, changes:)
         
     | 
| 
       55 
45 
     | 
    
         
             
                super if defined?(super)
         
     | 
| 
       56 
46 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                if !changes.new? && changes. 
     | 
| 
      
 47 
     | 
    
         
            +
                if !changes.new? && changes.changed_nested_tree?
         
     | 
| 
       58 
48 
     | 
    
         
             
                  CacheClearer.new(self.class.viewmodel_cache, id).add_to_transaction
         
     | 
| 
       59 
49 
     | 
    
         
             
                end
         
     | 
| 
       60 
50 
     | 
    
         
             
              end
         
     | 
| 
         @@ -198,7 +198,7 @@ class ViewModel::ActiveRecord::Cache 
     | 
|
| 
       198 
198 
     | 
    
         
             
                  end
         
     | 
| 
       199 
199 
     | 
    
         | 
| 
       200 
200 
     | 
    
         
             
                  ViewModel.preload_for_serialization(viewmodels,
         
     | 
| 
       201 
     | 
    
         
            -
                                                       
     | 
| 
      
 201 
     | 
    
         
            +
                                                      include_referenced: false,
         
     | 
| 
       202 
202 
     | 
    
         
             
                                                      lock: "FOR SHARE",
         
     | 
| 
       203 
203 
     | 
    
         
             
                                                      serialize_context: serialize_context)
         
     | 
| 
       204 
204 
     | 
    
         | 
| 
         @@ -259,7 +259,7 @@ class ViewModel::ActiveRecord::Cache 
     | 
|
| 
       259 
259 
     | 
    
         
             
              end
         
     | 
| 
       260 
260 
     | 
    
         | 
| 
       261 
261 
     | 
    
         
             
              def cache_version
         
     | 
| 
       262 
     | 
    
         
            -
                version_string = @viewmodel_class.deep_schema_version( 
     | 
| 
      
 262 
     | 
    
         
            +
                version_string = @viewmodel_class.deep_schema_version(include_referenced: false).to_a.sort.join(',')
         
     | 
| 
       263 
263 
     | 
    
         
             
                Base64.urlsafe_encode64(Digest::MD5.digest(version_string))
         
     | 
| 
       264 
264 
     | 
    
         
             
              end
         
     | 
| 
       265 
265 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,8 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            # Simple visitor for cloning models through the tree structure defined by
         
     | 
| 
       2 
4 
     | 
    
         
             
            # ViewModel::ActiveRecord. Owned associations will be followed and cloned, while
         
     | 
| 
       3 
     | 
    
         
            -
            #  
     | 
| 
       4 
     | 
    
         
            -
            # foreign keys not covered by ViewModel 
     | 
| 
       5 
     | 
    
         
            -
            # original.
         
     | 
| 
      
 5 
     | 
    
         
            +
            # non-owned referenced associations will be copied directly as references.
         
     | 
| 
      
 6 
     | 
    
         
            +
            # Attributes (including association foreign keys not covered by ViewModel
         
     | 
| 
      
 7 
     | 
    
         
            +
            # `association`s) will be copied from the original.
         
     | 
| 
       6 
8 
     | 
    
         
             
            #
         
     | 
| 
       7 
9 
     | 
    
         
             
            # To customize, subclasses may define methods `visit_x_view(node, new_model)`
         
     | 
| 
       8 
10 
     | 
    
         
             
            # for each type they wish to affect. These callbacks may update attributes of
         
     | 
| 
         @@ -19,9 +21,9 @@ class ViewModel::ActiveRecord::Cloner 
     | 
|
| 
       19 
21 
     | 
    
         
             
                return nil if ignored?
         
     | 
| 
       20 
22 
     | 
    
         | 
| 
       21 
23 
     | 
    
         
             
                if node.class.name
         
     | 
| 
       22 
     | 
    
         
            -
                  class_name 
     | 
| 
       23 
     | 
    
         
            -
                  visit 
     | 
| 
       24 
     | 
    
         
            -
                  end_visit 
     | 
| 
      
 24 
     | 
    
         
            +
                  class_name = node.class.name.underscore.gsub('/', '__')
         
     | 
| 
      
 25 
     | 
    
         
            +
                  visit      = :"visit_#{class_name}"
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end_visit  = :"end_visit_#{class_name}"
         
     | 
| 
       25 
27 
     | 
    
         
             
                end
         
     | 
| 
       26 
28 
     | 
    
         | 
| 
       27 
29 
     | 
    
         
             
                if visit && respond_to?(visit, true)
         
     | 
| 
         @@ -44,7 +46,7 @@ class ViewModel::ActiveRecord::Cloner 
     | 
|
| 
       44 
46 
     | 
    
         | 
| 
       45 
47 
     | 
    
         
             
                    if associated.nil?
         
     | 
| 
       46 
48 
     | 
    
         
             
                      new_associated = nil
         
     | 
| 
       47 
     | 
    
         
            -
                    elsif association_data. 
     | 
| 
      
 49 
     | 
    
         
            +
                    elsif !association_data.owned? && !association_data.through?
         
     | 
| 
       48 
50 
     | 
    
         
             
                      # simply attach the associated target to the new model
         
     | 
| 
       49 
51 
     | 
    
         
             
                      new_associated = associated
         
     | 
| 
       50 
52 
     | 
    
         
             
                    else
         
     | 
| 
         @@ -82,11 +84,9 @@ class ViewModel::ActiveRecord::Cloner 
     | 
|
| 
       82 
84 
     | 
    
         
             
                new_model
         
     | 
| 
       83 
85 
     | 
    
         
             
              end
         
     | 
| 
       84 
86 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
              def pre_visit(node, new_model)
         
     | 
| 
       86 
     | 
    
         
            -
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
              def pre_visit(node, new_model); end
         
     | 
| 
       87 
88 
     | 
    
         | 
| 
       88 
     | 
    
         
            -
              def post_visit(node, new_model)
         
     | 
| 
       89 
     | 
    
         
            -
              end
         
     | 
| 
      
 89 
     | 
    
         
            +
              def post_visit(node, new_model); end
         
     | 
| 
       90 
90 
     | 
    
         | 
| 
       91 
91 
     | 
    
         
             
              private
         
     | 
| 
       92 
92 
     | 
    
         | 
| 
         @@ -46,8 +46,6 @@ module ViewModel::ActiveRecord::Controller 
     | 
|
| 
       46 
46 
     | 
    
         
             
                pre_rendered = viewmodel_class.transaction do
         
     | 
| 
       47 
47 
     | 
    
         
             
                  view = viewmodel_class.deserialize_from_view(update_hash, references: refs, deserialize_context: deserialize_context)
         
     | 
| 
       48 
48 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                  serialize_context.add_includes(deserialize_context.updated_associations)
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
49 
     | 
    
         
             
                  view = yield(view) if block_given?
         
     | 
| 
       52 
50 
     | 
    
         | 
| 
       53 
51 
     | 
    
         
             
                  ViewModel.preload_for_serialization(view, serialize_context: serialize_context)
         
     | 
| 
         @@ -74,7 +74,7 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       74 
74 
     | 
    
         | 
| 
       75 
75 
     | 
    
         
             
                def initialize
         
     | 
| 
       76 
76 
     | 
    
         
             
                  @root_update_operations       = [] # The subject(s) of this update
         
     | 
| 
       77 
     | 
    
         
            -
                  @referenced_update_operations = {} #  
     | 
| 
      
 77 
     | 
    
         
            +
                  @referenced_update_operations = {} # data updates to other root models, referred to by a ref hash
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
79 
     | 
    
         
             
                  # Set of ViewModel::Reference used to assert only a single update is
         
     | 
| 
       80 
80 
     | 
    
         
             
                  # present for each viewmodel
         
     | 
| 
         @@ -178,8 +178,20 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       178 
178 
     | 
    
         
             
                      raise ViewModel::DeserializationError::ParentNotFound.new(@worklist.keys)
         
     | 
| 
       179 
179 
     | 
    
         
             
                    end
         
     | 
| 
       180 
180 
     | 
    
         | 
| 
       181 
     | 
    
         
            -
                    deferred_update 
     | 
| 
       182 
     | 
    
         
            -
                     
     | 
| 
      
 181 
     | 
    
         
            +
                    deferred_update    = @worklist.delete(key)
         
     | 
| 
      
 182 
     | 
    
         
            +
                    released_viewmodel = @release_pool.claim_from_pool(key)
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                    if deferred_update.viewmodel
         
     | 
| 
      
 185 
     | 
    
         
            +
                      # Deferred reference updates already have a viewmodel: ensure it
         
     | 
| 
      
 186 
     | 
    
         
            +
                      # matches the tree
         
     | 
| 
      
 187 
     | 
    
         
            +
                      unless deferred_update.viewmodel == released_viewmodel
         
     | 
| 
      
 188 
     | 
    
         
            +
                        raise ViewModel::DeserializationError::Internal.new(
         
     | 
| 
      
 189 
     | 
    
         
            +
                                "Released viewmodel doesn't match reference update", blame_reference)
         
     | 
| 
      
 190 
     | 
    
         
            +
                      end
         
     | 
| 
      
 191 
     | 
    
         
            +
                    else
         
     | 
| 
      
 192 
     | 
    
         
            +
                      deferred_update.viewmodel = released_viewmodel
         
     | 
| 
      
 193 
     | 
    
         
            +
                    end
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
       183 
195 
     | 
    
         
             
                    deferred_update.build!(self)
         
     | 
| 
       184 
196 
     | 
    
         
             
                  end
         
     | 
| 
       185 
197 
     | 
    
         | 
| 
         @@ -201,6 +213,12 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       201 
213 
     | 
    
         
             
                  update_operation = ViewModel::ActiveRecord::UpdateOperation.new(
         
     | 
| 
       202 
214 
     | 
    
         
             
                    nil, update_data, reparent_to: reparent_to, reposition_to: reposition_to)
         
     | 
| 
       203 
215 
     | 
    
         
             
                  check_unique_update!(viewmodel_reference)
         
     | 
| 
      
 216 
     | 
    
         
            +
                  defer_update(viewmodel_reference, update_operation)
         
     | 
| 
      
 217 
     | 
    
         
            +
                end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                # Defer an existing update: used if we need to ensure that an owned
         
     | 
| 
      
 220 
     | 
    
         
            +
                # reference has been freed before we use it.
         
     | 
| 
      
 221 
     | 
    
         
            +
                def defer_update(viewmodel_reference, update_operation)
         
     | 
| 
       204 
222 
     | 
    
         
             
                  @worklist[viewmodel_reference] = update_operation
         
     | 
| 
       205 
223 
     | 
    
         
             
                end
         
     | 
| 
       206 
224 
     | 
    
         | 
| 
         @@ -1,3 +1,5 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'renum'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'view_model/schemas'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
         @@ -45,14 +47,13 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       45 
47 
     | 
    
         
             
                end
         
     | 
| 
       46 
48 
     | 
    
         
             
              end
         
     | 
| 
       47 
49 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
              # Parser for collection updates. Collection updates have a regular
         
     | 
| 
       49 
     | 
    
         
            -
              #  
     | 
| 
       50 
     | 
    
         
            -
              #  
     | 
| 
       51 
     | 
    
         
            -
              #  
     | 
| 
       52 
     | 
    
         
            -
              # reference strings.
         
     | 
| 
      
 50 
     | 
    
         
            +
              # Parser for collection updates. Collection updates have a regular structure,
         
     | 
| 
      
 51 
     | 
    
         
            +
              # but vary based on the contents. Parsing a nested collection recurses deeply
         
     | 
| 
      
 52 
     | 
    
         
            +
              # and creates a tree of UpdateDatas, while parsing a referenced collection
         
     | 
| 
      
 53 
     | 
    
         
            +
              # collects reference strings.
         
     | 
| 
       53 
54 
     | 
    
         
             
              class AbstractCollectionUpdate
         
     | 
| 
       54 
     | 
    
         
            -
                # Wraps a complete collection of new data: either UpdateDatas for  
     | 
| 
       55 
     | 
    
         
            -
                #  
     | 
| 
      
 55 
     | 
    
         
            +
                # Wraps a complete collection of new data: either UpdateDatas for non-root
         
     | 
| 
      
 56 
     | 
    
         
            +
                # associations or reference strings for root.
         
     | 
| 
       56 
57 
     | 
    
         
             
                class Replace
         
     | 
| 
       57 
58 
     | 
    
         
             
                  attr_reader :contents
         
     | 
| 
       58 
59 
     | 
    
         
             
                  def initialize(contents)
         
     | 
| 
         @@ -61,7 +62,8 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       61 
62 
     | 
    
         
             
                end
         
     | 
| 
       62 
63 
     | 
    
         | 
| 
       63 
64 
     | 
    
         
             
                # Wraps an ordered list of FunctionalUpdates, each of whose `contents` are
         
     | 
| 
       64 
     | 
    
         
            -
                # either UpdateData for  
     | 
| 
      
 65 
     | 
    
         
            +
                # either UpdateData for nested associations or references for referenced
         
     | 
| 
      
 66 
     | 
    
         
            +
                # associations.
         
     | 
| 
       65 
67 
     | 
    
         
             
                class Functional
         
     | 
| 
       66 
68 
     | 
    
         
             
                  attr_reader :actions
         
     | 
| 
       67 
69 
     | 
    
         
             
                  def initialize(actions)
         
     | 
| 
         @@ -81,8 +83,8 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       81 
83 
     | 
    
         | 
| 
       82 
84 
     | 
    
         
             
                  # Resolve ViewModel::References used in the update's contents, whether by
         
     | 
| 
       83 
85 
     | 
    
         
             
                  # reference or value.
         
     | 
| 
       84 
     | 
    
         
            -
                  def used_vm_refs( 
     | 
| 
       85 
     | 
    
         
            -
                    raise  
     | 
| 
      
 86 
     | 
    
         
            +
                  def used_vm_refs(_update_context)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    raise RuntimeError.new('abstract method')
         
     | 
| 
       86 
88 
     | 
    
         
             
                  end
         
     | 
| 
       87 
89 
     | 
    
         | 
| 
       88 
90 
     | 
    
         
             
                  def removed_vm_refs
         
     | 
| 
         @@ -153,7 +155,8 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       153 
155 
     | 
    
         
             
                  # The shape of the actions are always the same
         
     | 
| 
       154 
156 
     | 
    
         | 
| 
       155 
157 
     | 
    
         
             
                  # Parse an anchor for a functional_update, before/after
         
     | 
| 
       156 
     | 
    
         
            -
                  # May only contain type and id fields, is never a reference even for 
     | 
| 
      
 158 
     | 
    
         
            +
                  # May only contain type and id fields, is never a reference even for
         
     | 
| 
      
 159 
     | 
    
         
            +
                  # referenced associations.
         
     | 
| 
       157 
160 
     | 
    
         
             
                  def parse_anchor(child_hash) # final
         
     | 
| 
       158 
161 
     | 
    
         
             
                    child_metadata = ViewModel.extract_reference_only_metadata(child_hash)
         
     | 
| 
       159 
162 
     | 
    
         | 
| 
         @@ -271,9 +274,13 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       271 
274 
     | 
    
         | 
| 
       272 
275 
     | 
    
         
             
                  def used_vm_refs(update_context)
         
     | 
| 
       273 
276 
     | 
    
         
             
                    update_datas
         
     | 
| 
       274 
     | 
    
         
            -
                      .map { |upd| upd 
     | 
| 
      
 277 
     | 
    
         
            +
                      .map { |upd| resolve_vm_reference(upd, update_context) }
         
     | 
| 
       275 
278 
     | 
    
         
             
                      .compact
         
     | 
| 
       276 
279 
     | 
    
         
             
                  end
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                  def resolve_vm_reference(update_data, _update_context)
         
     | 
| 
      
 282 
     | 
    
         
            +
                    update_data.viewmodel_reference if update_data.id
         
     | 
| 
      
 283 
     | 
    
         
            +
                  end
         
     | 
| 
       277 
284 
     | 
    
         
             
                end
         
     | 
| 
       278 
285 
     | 
    
         | 
| 
       279 
286 
     | 
    
         
             
                class Parser < AbstractCollectionUpdate::Parser
         
     | 
| 
         @@ -319,9 +326,13 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       319 
326 
     | 
    
         | 
| 
       320 
327 
     | 
    
         
             
                  def used_vm_refs(update_context)
         
     | 
| 
       321 
328 
     | 
    
         
             
                    references.map do |ref|
         
     | 
| 
       322 
     | 
    
         
            -
                       
     | 
| 
      
 329 
     | 
    
         
            +
                      resolve_vm_reference(ref, update_context)
         
     | 
| 
       323 
330 
     | 
    
         
             
                    end
         
     | 
| 
       324 
331 
     | 
    
         
             
                  end
         
     | 
| 
      
 332 
     | 
    
         
            +
             
     | 
| 
      
 333 
     | 
    
         
            +
                  def resolve_vm_reference(ref, update_context)
         
     | 
| 
      
 334 
     | 
    
         
            +
                    update_context.resolve_reference(ref, nil).viewmodel_reference
         
     | 
| 
      
 335 
     | 
    
         
            +
                  end
         
     | 
| 
       325 
336 
     | 
    
         
             
                end
         
     | 
| 
       326 
337 
     | 
    
         | 
| 
       327 
338 
     | 
    
         
             
                class Parser < AbstractCollectionUpdate::Parser
         
     | 
| 
         @@ -356,6 +367,7 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       356 
367 
     | 
    
         
             
                      unless valid_reference_keys.include?(ref)
         
     | 
| 
       357 
368 
     | 
    
         
             
                        raise ViewModel::DeserializationError::InvalidSharedReference.new(ref, blame_reference)
         
     | 
| 
       358 
369 
     | 
    
         
             
                      end
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
       359 
371 
     | 
    
         
             
                      ref
         
     | 
| 
       360 
372 
     | 
    
         
             
                    end
         
     | 
| 
       361 
373 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -421,7 +433,7 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       421 
433 
     | 
    
         
             
                  fupdate_owned =
         
     | 
| 
       422 
434 
     | 
    
         
             
                    fupdate_base.(ViewModel::Schemas::VIEWMODEL_UPDATE_SCHEMA)
         
     | 
| 
       423 
435 
     | 
    
         | 
| 
       424 
     | 
    
         
            -
                  fupdate_shared 
     | 
| 
      
 436 
     | 
    
         
            +
                  fupdate_shared =
         
     | 
| 
       425 
437 
     | 
    
         
             
                    fupdate_base.({ 'oneOf' => [ViewModel::Schemas::VIEWMODEL_REFERENCE_SCHEMA,
         
     | 
| 
       426 
438 
     | 
    
         
             
                                                viewmodel_reference_only] })
         
     | 
| 
       427 
439 
     | 
    
         | 
| 
         @@ -571,30 +583,15 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       571 
583 
     | 
    
         
             
                  case
         
     | 
| 
       572 
584 
     | 
    
         
             
                  when value.nil?
         
     | 
| 
       573 
585 
     | 
    
         
             
                    []
         
     | 
| 
       574 
     | 
    
         
            -
                  when association_data. 
     | 
| 
      
 586 
     | 
    
         
            +
                  when association_data.referenced?
         
     | 
| 
       575 
587 
     | 
    
         
             
                    []
         
     | 
| 
       576 
     | 
    
         
            -
                  when association_data.collection? #  
     | 
| 
      
 588 
     | 
    
         
            +
                  when association_data.collection? # nested, because of referenced? check above
         
     | 
| 
       577 
589 
     | 
    
         
             
                    value.update_datas
         
     | 
| 
       578 
590 
     | 
    
         
             
                  else
         
     | 
| 
       579 
591 
     | 
    
         
             
                    [value]
         
     | 
| 
       580 
592 
     | 
    
         
             
                  end
         
     | 
| 
       581 
593 
     | 
    
         
             
                end
         
     | 
| 
       582 
594 
     | 
    
         | 
| 
       583 
     | 
    
         
            -
                # Updates in terms of viewmodel associations
         
     | 
| 
       584 
     | 
    
         
            -
                def updated_associations
         
     | 
| 
       585 
     | 
    
         
            -
                  deps = {}
         
     | 
| 
       586 
     | 
    
         
            -
             
     | 
| 
       587 
     | 
    
         
            -
                  (associations.merge(referenced_associations)).each do |assoc_name, assoc_update|
         
     | 
| 
       588 
     | 
    
         
            -
                    deps[assoc_name] =
         
     | 
| 
       589 
     | 
    
         
            -
                      to_sequence(assoc_name, assoc_update)
         
     | 
| 
       590 
     | 
    
         
            -
                        .each_with_object({}) do |update_data, updated_associations|
         
     | 
| 
       591 
     | 
    
         
            -
                          updated_associations.deep_merge!(update_data.updated_associations)
         
     | 
| 
       592 
     | 
    
         
            -
                        end
         
     | 
| 
       593 
     | 
    
         
            -
                  end
         
     | 
| 
       594 
     | 
    
         
            -
             
     | 
| 
       595 
     | 
    
         
            -
                  deps
         
     | 
| 
       596 
     | 
    
         
            -
                end
         
     | 
| 
       597 
     | 
    
         
            -
             
     | 
| 
       598 
595 
     | 
    
         
             
                def build_preload_specs(association_data, updates)
         
     | 
| 
       599 
596 
     | 
    
         
             
                  if association_data.polymorphic?
         
     | 
| 
       600 
597 
     | 
    
         
             
                    updates.map do |update_data|
         
     | 
| 
         @@ -682,6 +679,7 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       682 
679 
     | 
    
         | 
| 
       683 
680 
     | 
    
         
             
                    when AssociationData
         
     | 
| 
       684 
681 
     | 
    
         
             
                      association_data = member_data
         
     | 
| 
      
 682 
     | 
    
         
            +
             
     | 
| 
       685 
683 
     | 
    
         
             
                      case
         
     | 
| 
       686 
684 
     | 
    
         
             
                      when value.nil?
         
     | 
| 
       687 
685 
     | 
    
         
             
                        if association_data.collection?
         
     | 
| 
         @@ -689,28 +687,28 @@ class ViewModel::ActiveRecord 
     | 
|
| 
       689 
687 
     | 
    
         
             
                                  "Invalid collection update value 'nil' for association '#{name}'",
         
     | 
| 
       690 
688 
     | 
    
         
             
                                  blame_reference)
         
     | 
| 
       691 
689 
     | 
    
         
             
                        end
         
     | 
| 
       692 
     | 
    
         
            -
                        if association_data. 
     | 
| 
      
 690 
     | 
    
         
            +
                        if association_data.referenced?
         
     | 
| 
       693 
691 
     | 
    
         
             
                          referenced_associations[name] = nil
         
     | 
| 
       694 
692 
     | 
    
         
             
                        else
         
     | 
| 
       695 
693 
     | 
    
         
             
                          associations[name] = nil
         
     | 
| 
       696 
694 
     | 
    
         
             
                        end
         
     | 
| 
       697 
695 
     | 
    
         | 
| 
       698 
     | 
    
         
            -
                      when association_data. 
     | 
| 
       699 
     | 
    
         
            -
                         
     | 
| 
       700 
     | 
    
         
            -
                           
     | 
| 
       701 
     | 
    
         
            -
                             
     | 
| 
       702 
     | 
    
         
            -
             
     | 
| 
      
 696 
     | 
    
         
            +
                      when association_data.referenced?
         
     | 
| 
      
 697 
     | 
    
         
            +
                        if association_data.collection?
         
     | 
| 
      
 698 
     | 
    
         
            +
                          referenced_associations[name] =
         
     | 
| 
      
 699 
     | 
    
         
            +
                            ReferencedCollectionUpdate::Parser
         
     | 
| 
      
 700 
     | 
    
         
            +
                              .new(association_data, blame_reference, valid_reference_keys)
         
     | 
| 
      
 701 
     | 
    
         
            +
                              .parse(value)
         
     | 
| 
      
 702 
     | 
    
         
            +
                        else
         
     | 
| 
      
 703 
     | 
    
         
            +
                          # Extract and check reference
         
     | 
| 
      
 704 
     | 
    
         
            +
                          ref = ViewModel.extract_reference_metadata(value)
         
     | 
| 
       703 
705 
     | 
    
         | 
| 
       704 
     | 
    
         
            -
             
     | 
| 
       705 
     | 
    
         
            -
             
     | 
| 
       706 
     | 
    
         
            -
             
     | 
| 
      
 706 
     | 
    
         
            +
                          unless valid_reference_keys.include?(ref)
         
     | 
| 
      
 707 
     | 
    
         
            +
                            raise ViewModel::DeserializationError::InvalidSharedReference.new(ref, blame_reference)
         
     | 
| 
      
 708 
     | 
    
         
            +
                          end
         
     | 
| 
       707 
709 
     | 
    
         | 
| 
       708 
     | 
    
         
            -
             
     | 
| 
       709 
     | 
    
         
            -
                          raise ViewModel::DeserializationError::InvalidSharedReference.new(ref, blame_reference)
         
     | 
| 
      
 710 
     | 
    
         
            +
                          referenced_associations[name] = ref
         
     | 
| 
       710 
711 
     | 
    
         
             
                        end
         
     | 
| 
       711 
     | 
    
         
            -
             
     | 
| 
       712 
     | 
    
         
            -
                        referenced_associations[name] = ref
         
     | 
| 
       713 
     | 
    
         
            -
             
     | 
| 
       714 
712 
     | 
    
         
             
                      else
         
     | 
| 
       715 
713 
     | 
    
         
             
                        if association_data.collection?
         
     | 
| 
       716 
714 
     | 
    
         
             
                          associations[name] =
         
     |