iknow_view_models 3.1.8 → 3.2.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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +6 -6
- data/.rubocop.yml +18 -0
- data/Appraisals +6 -6
- data/Gemfile +6 -2
- data/Rakefile +5 -5
- data/gemfiles/rails_5_2.gemfile +5 -5
- data/gemfiles/rails_6_0.gemfile +9 -0
- data/iknow_view_models.gemspec +40 -38
- data/lib/iknow_view_models.rb +9 -7
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model.rb +31 -17
- data/lib/view_model/access_control.rb +5 -2
- data/lib/view_model/access_control/composed.rb +10 -9
- data/lib/view_model/access_control/open.rb +2 -0
- data/lib/view_model/access_control/read_only.rb +2 -0
- data/lib/view_model/access_control/tree.rb +11 -6
- data/lib/view_model/access_control_error.rb +4 -1
- data/lib/view_model/active_record.rb +17 -15
- data/lib/view_model/active_record/association_data.rb +2 -1
- data/lib/view_model/active_record/association_manipulation.rb +6 -4
- data/lib/view_model/active_record/cache.rb +114 -34
- data/lib/view_model/active_record/cache/cacheable_view.rb +2 -2
- data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
- data/lib/view_model/active_record/controller.rb +68 -1
- data/lib/view_model/active_record/controller_base.rb +4 -1
- data/lib/view_model/active_record/nested_controller_base.rb +1 -0
- data/lib/view_model/active_record/update_context.rb +8 -6
- data/lib/view_model/active_record/update_data.rb +32 -30
- data/lib/view_model/active_record/update_operation.rb +17 -13
- data/lib/view_model/active_record/visitor.rb +0 -1
- data/lib/view_model/after_transaction_runner.rb +0 -1
- data/lib/view_model/callbacks.rb +3 -1
- data/lib/view_model/controller.rb +13 -3
- data/lib/view_model/deserialization_error.rb +15 -12
- data/lib/view_model/error.rb +12 -10
- data/lib/view_model/error_view.rb +3 -1
- data/lib/view_model/migratable_view.rb +78 -0
- data/lib/view_model/migration.rb +48 -0
- data/lib/view_model/migration/no_path_error.rb +26 -0
- data/lib/view_model/migration/one_way_error.rb +24 -0
- data/lib/view_model/migration/unspecified_version_error.rb +24 -0
- data/lib/view_model/migrator.rb +108 -0
- data/lib/view_model/record.rb +15 -14
- data/lib/view_model/reference.rb +3 -1
- data/lib/view_model/references.rb +8 -5
- data/lib/view_model/registry.rb +14 -2
- data/lib/view_model/schemas.rb +9 -4
- data/lib/view_model/serialization_error.rb +4 -1
- data/lib/view_model/serialize_context.rb +4 -4
- data/lib/view_model/test_helpers.rb +8 -3
- data/lib/view_model/test_helpers/arvm_builder.rb +21 -15
- data/lib/view_model/traversal_context.rb +2 -1
- data/nix/dependencies.nix +5 -0
- data/nix/gem/generate.rb +2 -1
- data/shell.nix +8 -3
- data/test/.rubocop.yml +14 -0
- data/test/helpers/arvm_test_models.rb +12 -9
- data/test/helpers/arvm_test_utilities.rb +5 -3
- data/test/helpers/controller_test_helpers.rb +55 -32
- data/test/helpers/match_enumerator.rb +1 -0
- data/test/helpers/query_logging.rb +2 -1
- data/test/helpers/test_access_control.rb +5 -3
- data/test/helpers/viewmodel_spec_helpers.rb +88 -22
- data/test/unit/view_model/access_control_test.rb +144 -144
- data/test/unit/view_model/active_record/alias_test.rb +15 -13
- data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
- data/test/unit/view_model/active_record/cache_test.rb +68 -31
- data/test/unit/view_model/active_record/cloner_test.rb +67 -63
- data/test/unit/view_model/active_record/controller_test.rb +113 -65
- data/test/unit/view_model/active_record/counter_test.rb +10 -9
- data/test/unit/view_model/active_record/customization_test.rb +59 -58
- data/test/unit/view_model/active_record/has_many_test.rb +112 -111
- data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
- data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
- data/test/unit/view_model/active_record/has_one_test.rb +37 -36
- data/test/unit/view_model/active_record/migration_test.rb +161 -0
- data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
- data/test/unit/view_model/active_record/poly_test.rb +44 -45
- data/test/unit/view_model/active_record/shared_test.rb +30 -28
- data/test/unit/view_model/active_record/version_test.rb +9 -7
- data/test/unit/view_model/active_record_test.rb +72 -72
- data/test/unit/view_model/callbacks_test.rb +19 -15
- data/test/unit/view_model/controller_test.rb +4 -2
- data/test/unit/view_model/record_test.rb +158 -145
- data/test/unit/view_model/registry_test.rb +38 -0
- data/test/unit/view_model/traversal_context_test.rb +4 -5
- data/test/unit/view_model_test.rb +18 -16
- metadata +38 -12
- data/.travis.yml +0 -31
- data/appveyor.yml +0 -22
- data/gemfiles/rails_6_0_beta.gemfile +0 -9
| @@ -1,10 +1,12 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require_relative "../../../helpers/arvm_test_models.rb"
         | 
| 3 | 
            -
            require_relative "../../../helpers/viewmodel_spec_helpers.rb"
         | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 4 2 |  | 
| 5 | 
            -
             | 
| 3 | 
            +
            require_relative '../../../helpers/arvm_test_utilities'
         | 
| 4 | 
            +
            require_relative '../../../helpers/arvm_test_models'
         | 
| 5 | 
            +
            require_relative '../../../helpers/viewmodel_spec_helpers'
         | 
| 6 6 |  | 
| 7 | 
            -
            require  | 
| 7 | 
            +
            require 'minitest/autorun'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require 'view_model/active_record'
         | 
| 8 10 |  | 
| 9 11 | 
             
            class ViewModel::ActiveRecord::Alias < ActiveSupport::TestCase
         | 
| 10 12 | 
             
              include ARVMTestUtilities
         | 
| @@ -14,18 +16,18 @@ class ViewModel::ActiveRecord::Alias < ActiveSupport::TestCase | |
| 14 16 |  | 
| 15 17 | 
             
              def child_attributes
         | 
| 16 18 | 
             
                super.merge(
         | 
| 17 | 
            -
                  viewmodel: ->( | 
| 18 | 
            -
                    add_view_alias  | 
| 19 | 
            -
                    add_view_alias  | 
| 20 | 
            -
                  end
         | 
| 19 | 
            +
                  viewmodel: ->(_v) do
         | 
| 20 | 
            +
                    add_view_alias 'ChildA'
         | 
| 21 | 
            +
                    add_view_alias 'ChildB'
         | 
| 22 | 
            +
                  end,
         | 
| 21 23 | 
             
                )
         | 
| 22 24 | 
             
              end
         | 
| 23 25 |  | 
| 24 | 
            -
              it  | 
| 25 | 
            -
                %w | 
| 26 | 
            +
              it 'permits association types to be aliased' do
         | 
| 27 | 
            +
                %w[Child ChildA ChildB].each do |view_alias|
         | 
| 26 28 | 
             
                  view = {
         | 
| 27 | 
            -
                     | 
| 28 | 
            -
                     | 
| 29 | 
            +
                    '_type' => viewmodel_class.view_name,
         | 
| 30 | 
            +
                    'child' => { '_type' => view_alias },
         | 
| 29 31 | 
             
                  }
         | 
| 30 32 |  | 
| 31 33 | 
             
                  parent = viewmodel_class.deserialize_from_view(view).model
         | 
| @@ -1,10 +1,12 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require_relative "../../../helpers/arvm_test_models.rb"
         | 
| 3 | 
            -
            require_relative '../../../helpers/viewmodel_spec_helpers.rb'
         | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 4 2 |  | 
| 5 | 
            -
             | 
| 3 | 
            +
            require_relative '../../../helpers/arvm_test_utilities'
         | 
| 4 | 
            +
            require_relative '../../../helpers/arvm_test_models'
         | 
| 5 | 
            +
            require_relative '../../../helpers/viewmodel_spec_helpers'
         | 
| 6 6 |  | 
| 7 | 
            -
            require  | 
| 7 | 
            +
            require 'minitest/autorun'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require 'view_model/active_record'
         | 
| 8 10 |  | 
| 9 11 | 
             
            class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase
         | 
| 10 12 | 
             
              include ARVMTestUtilities
         | 
| @@ -14,13 +16,13 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 14 16 | 
             
              def setup
         | 
| 15 17 | 
             
                super
         | 
| 16 18 |  | 
| 17 | 
            -
                # TODO make a `has_list?` that allows a parent to set all children as an array
         | 
| 18 | 
            -
                @model1 = model_class.new(name:  | 
| 19 | 
            -
                                          child: child_model_class.new(name:  | 
| 19 | 
            +
                # TODO: make a `has_list?` that allows a parent to set all children as an array
         | 
| 20 | 
            +
                @model1 = model_class.new(name: 'p1',
         | 
| 21 | 
            +
                                          child: child_model_class.new(name: 'p1l'))
         | 
| 20 22 | 
             
                @model1.save!
         | 
| 21 23 |  | 
| 22 | 
            -
                @model2 = model_class.new(name:  | 
| 23 | 
            -
                                          child: child_model_class.new(name:  | 
| 24 | 
            +
                @model2 = model_class.new(name: 'p2',
         | 
| 25 | 
            +
                                          child: child_model_class.new(name: 'p2l'))
         | 
| 24 26 |  | 
| 25 27 | 
             
                @model2.save!
         | 
| 26 28 |  | 
| @@ -30,14 +32,14 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 30 32 | 
             
              def test_serialize_view
         | 
| 31 33 | 
             
                view, _refs = serialize_with_references(ModelView.new(@model1))
         | 
| 32 34 |  | 
| 33 | 
            -
                assert_equal({  | 
| 34 | 
            -
                                | 
| 35 | 
            -
                                | 
| 36 | 
            -
                                | 
| 37 | 
            -
                                | 
| 38 | 
            -
                                                | 
| 39 | 
            -
                                                | 
| 40 | 
            -
                                                | 
| 35 | 
            +
                assert_equal({ '_type'    => 'Model',
         | 
| 36 | 
            +
                               '_version' => 1,
         | 
| 37 | 
            +
                               'id'       => @model1.id,
         | 
| 38 | 
            +
                               'name'     => @model1.name,
         | 
| 39 | 
            +
                               'child'    => { '_type'    => 'Child',
         | 
| 40 | 
            +
                                               '_version' => 1,
         | 
| 41 | 
            +
                                               'id'       => @model1.child.id,
         | 
| 42 | 
            +
                                               'name'     => @model1.child.name },
         | 
| 41 43 | 
             
                             },
         | 
| 42 44 | 
             
                             view)
         | 
| 43 45 | 
             
              end
         | 
| @@ -53,9 +55,9 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 53 55 |  | 
| 54 56 | 
             
              def test_create_from_view
         | 
| 55 57 | 
             
                view = {
         | 
| 56 | 
            -
                   | 
| 57 | 
            -
                   | 
| 58 | 
            -
                   | 
| 58 | 
            +
                  '_type'    => 'Model',
         | 
| 59 | 
            +
                  'name'     => 'p',
         | 
| 60 | 
            +
                  'child'    => { '_type' => 'Child', 'name' => 'l' },
         | 
| 59 61 | 
             
                }
         | 
| 60 62 |  | 
| 61 63 | 
             
                pv = ModelView.deserialize_from_view(view)
         | 
| @@ -64,10 +66,10 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 64 66 | 
             
                assert(!p.changed?)
         | 
| 65 67 | 
             
                assert(!p.new_record?)
         | 
| 66 68 |  | 
| 67 | 
            -
                assert_equal( | 
| 69 | 
            +
                assert_equal('p', p.name)
         | 
| 68 70 |  | 
| 69 71 | 
             
                assert(p.child.present?)
         | 
| 70 | 
            -
                assert_equal( | 
| 72 | 
            +
                assert_equal('l', p.child.name)
         | 
| 71 73 | 
             
              end
         | 
| 72 74 |  | 
| 73 75 | 
             
              def test_create_belongs_to_nil
         | 
| @@ -86,7 +88,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 86 88 | 
             
              def test_belongs_to_create
         | 
| 87 89 | 
             
                @model1.update(child: nil)
         | 
| 88 90 |  | 
| 89 | 
            -
                alter_by_view!(ModelView, @model1) do |view,  | 
| 91 | 
            +
                alter_by_view!(ModelView, @model1) do |view, _refs|
         | 
| 90 92 | 
             
                  view['child'] = { '_type' => 'Child', 'name' => 'cheese' }
         | 
| 91 93 | 
             
                end
         | 
| 92 94 |  | 
| @@ -96,7 +98,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 96 98 | 
             
              def test_belongs_to_replace
         | 
| 97 99 | 
             
                old_child = @model1.child
         | 
| 98 100 |  | 
| 99 | 
            -
                alter_by_view!(ModelView, @model1) do |view,  | 
| 101 | 
            +
                alter_by_view!(ModelView, @model1) do |view, _refs|
         | 
| 100 102 | 
             
                  view['child'] = { '_type' => 'Child', 'name' => 'cheese' }
         | 
| 101 103 | 
             
                end
         | 
| 102 104 |  | 
| @@ -108,7 +110,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 108 110 | 
             
                old_p1_child = @model1.child
         | 
| 109 111 | 
             
                old_p2_child = @model2.child
         | 
| 110 112 |  | 
| 111 | 
            -
                set_by_view!(ModelView, [@model1, @model2]) do |(p1, p2),  | 
| 113 | 
            +
                set_by_view!(ModelView, [@model1, @model2]) do |(p1, p2), _refs|
         | 
| 112 114 | 
             
                  p1['child'] = nil
         | 
| 113 115 | 
             
                  p2['child'] = update_hash_for(ChildView, old_p1_child)
         | 
| 114 116 | 
             
                end
         | 
| @@ -122,7 +124,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 122 124 | 
             
                old_p1_child = @model1.child
         | 
| 123 125 | 
             
                old_p2_child = @model2.child
         | 
| 124 126 |  | 
| 125 | 
            -
                alter_by_view!(ModelView, [@model1, @model2]) do |(p1, p2),  | 
| 127 | 
            +
                alter_by_view!(ModelView, [@model1, @model2]) do |(p1, p2), _refs|
         | 
| 126 128 | 
             
                  p1['child'] = update_hash_for(ChildView, old_p2_child)
         | 
| 127 129 | 
             
                  p2['child'] = update_hash_for(ChildView, old_p1_child)
         | 
| 128 130 | 
             
                end
         | 
| @@ -136,22 +138,22 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 136 138 | 
             
                d_context = ModelView.new_deserialize_context
         | 
| 137 139 |  | 
| 138 140 | 
             
                target_child = Child.create
         | 
| 139 | 
            -
                from_model | 
| 140 | 
            -
                to_model | 
| 141 | 
            +
                from_model = Model.create(name: 'from', child: target_child)
         | 
| 142 | 
            +
                to_model = Model.create(name: 'p3')
         | 
| 141 143 |  | 
| 142 144 | 
             
                alter_by_view!(
         | 
| 143 145 | 
             
                  ModelView, [from_model, to_model],
         | 
| 144 146 | 
             
                  deserialize_context: d_context
         | 
| 145 | 
            -
                ) do |(from, to),  | 
| 147 | 
            +
                ) do |(from, to), _refs|
         | 
| 146 148 | 
             
                  from['child'] = nil
         | 
| 147 149 | 
             
                  to['child']   = update_hash_for(ChildView, target_child)
         | 
| 148 150 | 
             
                end
         | 
| 149 151 |  | 
| 150 152 | 
             
                assert_equal(target_child, to_model.child, 'target child moved')
         | 
| 151 153 | 
             
                assert_equal([ViewModel::Reference.new(ModelView, from_model.id),
         | 
| 152 | 
            -
                              ViewModel::Reference.new(ModelView, to_model.id)],
         | 
| 154 | 
            +
                              ViewModel::Reference.new(ModelView, to_model.id),],
         | 
| 153 155 | 
             
                             d_context.valid_edit_refs,
         | 
| 154 | 
            -
                              | 
| 156 | 
            +
                             'only models are checked for change; child was not')
         | 
| 155 157 | 
             
              end
         | 
| 156 158 |  | 
| 157 159 | 
             
              def test_implicit_release_invalid_belongs_to
         | 
| @@ -174,11 +176,11 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 174 176 | 
             
                      t.integer :deleted_child_id
         | 
| 175 177 | 
             
                      t.integer :ignored_child_id
         | 
| 176 178 | 
             
                    end,
         | 
| 177 | 
            -
                    model: ->( | 
| 179 | 
            +
                    model: ->(_m) do
         | 
| 178 180 | 
             
                      belongs_to :deleted_child, class_name: Child.name, dependent: :delete
         | 
| 179 181 | 
             
                      belongs_to :ignored_child, class_name: Child.name
         | 
| 180 182 | 
             
                    end,
         | 
| 181 | 
            -
                    viewmodel: ->( | 
| 183 | 
            +
                    viewmodel: ->(_v) do
         | 
| 182 184 | 
             
                      associations :deleted_child, :ignored_child
         | 
| 183 185 | 
             
                    end)
         | 
| 184 186 | 
             
                end
         | 
| @@ -198,7 +200,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 198 200 | 
             
                end
         | 
| 199 201 |  | 
| 200 202 | 
             
                def test_no_gc_dependent_ignore
         | 
| 201 | 
            -
                  model = model_class.create(ignored_child: Child.new(name:  | 
| 203 | 
            +
                  model = model_class.create(ignored_child: Child.new(name: 'one'))
         | 
| 202 204 | 
             
                  old_child = model.ignored_child
         | 
| 203 205 |  | 
| 204 206 | 
             
                  alter_by_view!(ModelView, model) do |ov, _refs|
         | 
| @@ -232,7 +234,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 232 234 | 
             
                end
         | 
| 233 235 |  | 
| 234 236 | 
             
                def test_renamed_roundtrip
         | 
| 235 | 
            -
                  alter_by_view!(ModelView, @model) do |view,  | 
| 237 | 
            +
                  alter_by_view!(ModelView, @model) do |view, _refs|
         | 
| 236 238 | 
             
                    assert_equal({ 'id'       => @model.child.id,
         | 
| 237 239 | 
             
                                   '_type'    => 'Child',
         | 
| 238 240 | 
             
                                   '_version' => 1,
         | 
| @@ -284,7 +286,6 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 284 286 | 
             
                  end
         | 
| 285 287 | 
             
                end
         | 
| 286 288 |  | 
| 287 | 
            -
             | 
| 288 289 | 
             
                # Do we support replacing a node in the tree and remodeling its children
         | 
| 289 290 | 
             
                # back to it? In theory we want to, but currently we don't: the child node
         | 
| 290 291 | 
             
                # is unresolvable.
         | 
| @@ -299,8 +300,8 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase | |
| 299 300 | 
             
                def test_move
         | 
| 300 301 | 
             
                  model = Aye.create(bee: Bee.new(cee: Cee.new))
         | 
| 301 302 | 
             
                  assert_raises(ViewModel::DeserializationError::ParentNotFound) do
         | 
| 302 | 
            -
                    alter_by_view!(AyeView, model) do |view,  | 
| 303 | 
            -
                      view['bee'].delete( | 
| 303 | 
            +
                    alter_by_view!(AyeView, model) do |view, _refs|
         | 
| 304 | 
            +
                      view['bee'].delete('id')
         | 
| 304 305 | 
             
                    end
         | 
| 305 306 | 
             
                  end
         | 
| 306 307 | 
             
                end
         | 
| @@ -1,15 +1,15 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 3 | 
            +
            require 'minitest/autorun'
         | 
| 4 | 
            +
            require 'minitest/unit'
         | 
| 5 | 
            +
            require 'minitest/hooks'
         | 
| 6 6 |  | 
| 7 | 
            -
            require_relative  | 
| 8 | 
            -
            require_relative  | 
| 9 | 
            -
            require_relative  | 
| 7 | 
            +
            require_relative '../../../helpers/arvm_test_models'
         | 
| 8 | 
            +
            require_relative '../../../helpers/arvm_test_utilities'
         | 
| 9 | 
            +
            require_relative '../../../helpers/viewmodel_spec_helpers'
         | 
| 10 10 |  | 
| 11 | 
            -
            require  | 
| 12 | 
            -
            require  | 
| 11 | 
            +
            require 'view_model'
         | 
| 12 | 
            +
            require 'view_model/active_record'
         | 
| 13 13 |  | 
| 14 14 | 
             
            # IknowCache uses Rails.cache: create a dummy cache.
         | 
| 15 15 |  | 
| @@ -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:: | 
| 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
         | 
| @@ -81,8 +91,8 @@ class ViewModel::ActiveRecord | |
| 81 91 | 
             
                  end
         | 
| 82 92 |  | 
| 83 93 | 
             
                  included do
         | 
| 84 | 
            -
                    let(:shared)     { shared_model_class.create!(name:  | 
| 85 | 
            -
                    let(:root)       { model_class.create!(name:  | 
| 94 | 
            +
                    let(:shared)     { shared_model_class.create!(name: 'shared1') }
         | 
| 95 | 
            +
                    let(:root)       { model_class.create!(name: 'root1', child: Child.new(name: 'owned1'), shared: shared) }
         | 
| 86 96 | 
             
                    let(:root_view)  { viewmodel_class.new(root) }
         | 
| 87 97 | 
             
                  end
         | 
| 88 98 | 
             
                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. | 
| 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
         | 
| @@ -160,13 +183,14 @@ class ViewModel::ActiveRecord | |
| 160 183 |  | 
| 161 184 | 
             
                        # The cached reference must correspond to the returned data.
         | 
| 162 185 | 
             
                        parsed_data = JSON.parse(ref_data)
         | 
| 163 | 
            -
                        value(parsed_data[ | 
| 164 | 
            -
                        value(parsed_data[ | 
| 186 | 
            +
                        value(parsed_data['id']).must_equal(id)
         | 
| 187 | 
            +
                        value(parsed_data['_type']).must_equal(view_name)
         | 
| 165 188 |  | 
| 166 189 | 
             
                        # When the cached reference is to independently cached data
         | 
| 167 190 | 
             
                        # (SharedView in this test), make sure that data is correctly
         | 
| 168 191 | 
             
                        # cached.
         | 
| 169 | 
            -
                        next unless view_name ==  | 
| 192 | 
            +
                        next unless view_name == 'Shared'
         | 
| 193 | 
            +
             | 
| 170 194 | 
             
                        value(id).must_equal(shared.id)
         | 
| 171 195 | 
             
                        cached_shared = read_cache(shared_viewmodel_class, id)
         | 
| 172 196 | 
             
                        value(cached_shared).must_be(:present?)
         | 
| @@ -179,7 +203,20 @@ class ViewModel::ActiveRecord | |
| 179 203 |  | 
| 180 204 | 
             
                describe 'with owned and shared children' do
         | 
| 181 205 | 
             
                  include CacheableParentAndChildren
         | 
| 182 | 
            -
             | 
| 206 | 
            +
             | 
| 207 | 
            +
                  describe 'without migrations' do
         | 
| 208 | 
            +
                    include BehavesLikeACache
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                  describe 'with migrations' do
         | 
| 212 | 
            +
                    let(:migration_versions) { { viewmodel_class => 1, child_viewmodel_class => 2 } }
         | 
| 213 | 
            +
                    include BehavesLikeACache
         | 
| 214 | 
            +
                  end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                  describe 'with shared migrations' do
         | 
| 217 | 
            +
                    let(:migration_versions) { { shared_viewmodel_class => 1 } }
         | 
| 218 | 
            +
                    include BehavesLikeACache
         | 
| 219 | 
            +
                  end
         | 
| 183 220 |  | 
| 184 221 | 
             
                  describe 'with a record in the cache' do
         | 
| 185 222 | 
             
                    # Fetch the root record to ensure it's in the cache
         | 
| @@ -188,8 +225,8 @@ class ViewModel::ActiveRecord | |
| 188 225 | 
             
                    end
         | 
| 189 226 |  | 
| 190 227 | 
             
                    def change_in_database
         | 
| 191 | 
            -
                      root.update_attribute(:name,  | 
| 192 | 
            -
                      shared.update_attribute(:name,  | 
| 228 | 
            +
                      root.update_attribute(:name, 'CHANGEDROOT')
         | 
| 229 | 
            +
                      shared.update_attribute(:name, 'CHANGEDSHARED')
         | 
| 193 230 | 
             
                    end
         | 
| 194 231 |  | 
| 195 232 | 
             
                    it 'resolves from the cache' do
         | 
| @@ -207,7 +244,7 @@ class ViewModel::ActiveRecord | |
| 207 244 | 
             
                      viewmodel_class.viewmodel_cache.clear
         | 
| 208 245 |  | 
| 209 246 | 
             
                      cache_data, cache_refs = serialize_with_cache
         | 
| 210 | 
            -
                      value(cache_data[0][ | 
| 247 | 
            +
                      value(cache_data[0]['name']).must_equal('CHANGEDROOT') # Root view invalidated
         | 
| 211 248 | 
             
                      value(cache_refs).must_equal(before_refs) # Shared view not invalidated
         | 
| 212 249 | 
             
                    end
         | 
| 213 250 |  | 
| @@ -265,7 +302,7 @@ class ViewModel::ActiveRecord | |
| 265 302 | 
             
                      viewmodel_class.viewmodel_cache.delete(root.id)
         | 
| 266 303 |  | 
| 267 304 | 
             
                      cache_data, cache_refs = serialize_with_cache
         | 
| 268 | 
            -
                      value(cache_data[0][ | 
| 305 | 
            +
                      value(cache_data[0]['name']).must_equal('CHANGEDROOT')
         | 
| 269 306 | 
             
                      value(cache_refs).must_equal(before_refs)
         | 
| 270 307 | 
             
                    end
         | 
| 271 308 |  | 
| @@ -275,8 +312,8 @@ class ViewModel::ActiveRecord | |
| 275 312 |  | 
| 276 313 | 
             
                      # Shared view invalidated, but root view not
         | 
| 277 314 | 
             
                      cache_data, cache_hrefs = serialize_with_cache
         | 
| 278 | 
            -
                      value(cache_data[0][ | 
| 279 | 
            -
                      value(cache_hrefs.values[0][ | 
| 315 | 
            +
                      value(cache_data[0]['name']).must_equal('root1')
         | 
| 316 | 
            +
                      value(cache_hrefs.values[0]['name']).must_equal('CHANGEDSHARED')
         | 
| 280 317 | 
             
                    end
         | 
| 281 318 |  | 
| 282 319 | 
             
                    it 'can clear a cache via its external cache group' do
         | 
| @@ -285,12 +322,12 @@ class ViewModel::ActiveRecord | |
| 285 322 |  | 
| 286 323 | 
             
                      # Shared view invalidated, but root view not
         | 
| 287 324 | 
             
                      cache_data, cache_hrefs = serialize_with_cache
         | 
| 288 | 
            -
                      value(cache_data[0][ | 
| 289 | 
            -
                      value(cache_hrefs.values[0][ | 
| 325 | 
            +
                      value(cache_data[0]['name']).must_equal('root1')
         | 
| 326 | 
            +
                      value(cache_hrefs.values[0]['name']).must_equal('CHANGEDSHARED')
         | 
| 290 327 | 
             
                    end
         | 
| 291 328 |  | 
| 292 329 | 
             
                    describe 'and a record not in the cache' do
         | 
| 293 | 
            -
                      let(:root2) { model_class.create!(name:  | 
| 330 | 
            +
                      let(:root2) { model_class.create!(name: 'root2', child: Child.new(name: 'owned2'), shared: shared) }
         | 
| 294 331 |  | 
| 295 332 | 
             
                      def serialize_from_database
         | 
| 296 333 | 
             
                        views   = model_class.find(root.id, root2.id).map { |r| viewmodel_class.new(r) }
         | 
| @@ -318,21 +355,21 @@ class ViewModel::ActiveRecord | |
| 318 355 | 
             
                        change_in_database
         | 
| 319 356 |  | 
| 320 357 | 
             
                        cache_data, cache_refs = serialize_with_cache
         | 
| 321 | 
            -
                        value(cache_data[0][ | 
| 322 | 
            -
                        value(cache_data[1][ | 
| 358 | 
            +
                        value(cache_data[0]['name']).must_equal('root1')
         | 
| 359 | 
            +
                        value(cache_data[1]['name']).must_equal('root2')
         | 
| 323 360 | 
             
                        value(cache_refs).must_equal(refs)
         | 
| 324 361 | 
             
                      end
         | 
| 325 362 | 
             
                    end
         | 
| 326 363 | 
             
                  end
         | 
| 327 364 | 
             
                end
         | 
| 328 365 |  | 
| 329 | 
            -
                describe  | 
| 366 | 
            +
                describe 'with a non-cacheable shared child' do
         | 
| 330 367 | 
             
                  include ViewModelSpecHelpers::ParentAndSharedBelongsToChild
         | 
| 331 368 | 
             
                  def model_attributes
         | 
| 332 369 | 
             
                    super.merge(viewmodel: ->(_) { cacheable! })
         | 
| 333 370 | 
             
                  end
         | 
| 334 371 |  | 
| 335 | 
            -
                  let(:root)       { model_class.create!(name:  | 
| 372 | 
            +
                  let(:root)       { model_class.create!(name: 'root1', child: Child.new(name: 'owned1')) }
         | 
| 336 373 | 
             
                  let(:root_view)  { viewmodel_class.new(root) }
         | 
| 337 374 |  | 
| 338 375 | 
             
                  include BehavesLikeACache
         |