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
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6a61a46fa1aa74a456ed6a3d639b5bfa5a2e246b8b3772e2a34fab2bf9013722
         | 
| 4 | 
            +
              data.tar.gz: 9beba4e8d176ab4ae5da14e66da70f2747a8ee68faa5ba1b79821f7dd956ab56
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1ffc79be3e1479fe94f53400558133e0d98a333112502f183e8d78da9da95cb08187ab3eff20dd408ca12ed9172b74c25d15a6343407349b46b21576d2698d99
         | 
| 7 | 
            +
              data.tar.gz: be2a8e88134247eed00a3adc051c741f86ac3437d6f47c921ecfd1c16d5d4d39104901aaf8cc3b29804fcd62e1c8ccfd89943d1457b89fc6b4976513e2d57069
         | 
    
        data/.circleci/config.yml
    CHANGED
    
    | @@ -102,15 +102,15 @@ workflows: | |
| 102 102 | 
             
              build:
         | 
| 103 103 | 
             
                jobs:
         | 
| 104 104 | 
             
                  - test:
         | 
| 105 | 
            -
                      name: 'ruby 2.6 rails 5.2 pg  | 
| 105 | 
            +
                      name: 'ruby 2.6 rails 5.2 pg 12'
         | 
| 106 106 | 
             
                      ruby-version: "2.6"
         | 
| 107 | 
            -
                      pg-version: " | 
| 107 | 
            +
                      pg-version: "12"
         | 
| 108 108 | 
             
                      gemfile: gemfiles/rails_5_2.gemfile
         | 
| 109 109 | 
             
                  - test:
         | 
| 110 | 
            -
                      name: 'ruby 2. | 
| 111 | 
            -
                      ruby-version: "2. | 
| 112 | 
            -
                      pg-version: " | 
| 113 | 
            -
                      gemfile: gemfiles/ | 
| 110 | 
            +
                      name: 'ruby 2.7 rails 6.0 pg 12'
         | 
| 111 | 
            +
                      ruby-version: "2.7"
         | 
| 112 | 
            +
                      pg-version: "12"
         | 
| 113 | 
            +
                      gemfile: gemfiles/rails_6_0.gemfile
         | 
| 114 114 | 
             
                  - publish:
         | 
| 115 115 | 
             
                      filters:
         | 
| 116 116 | 
             
                        branches:
         | 
    
        data/.rubocop.yml
    ADDED
    
    | @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            AllCops:
         | 
| 2 | 
            +
              TargetRubyVersion: 2.7
         | 
| 3 | 
            +
              Exclude:
         | 
| 4 | 
            +
                - Appraisals
         | 
| 5 | 
            +
                - Gemfile
         | 
| 6 | 
            +
                - Rakefile
         | 
| 7 | 
            +
                - 'gemfiles/*'
         | 
| 8 | 
            +
                - 'nix/gem/**/*'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            inherit_gem:
         | 
| 11 | 
            +
              rubocop-iknow: rubocop.yml
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Metrics/BlockNesting:
         | 
| 14 | 
            +
              Enabled: false
         | 
| 15 | 
            +
            Lint/IneffectiveAccessModifier:
         | 
| 16 | 
            +
              Enabled: false
         | 
| 17 | 
            +
            Lint/UselessAccessModifier:
         | 
| 18 | 
            +
              Enabled: false
         | 
    
        data/Appraisals
    CHANGED
    
    | @@ -1,9 +1,9 @@ | |
| 1 | 
            -
            appraise  | 
| 2 | 
            -
              gem  | 
| 3 | 
            -
              gem  | 
| 1 | 
            +
            appraise 'rails-5-2' do
         | 
| 2 | 
            +
              gem 'activerecord', '~> 5.2.0'
         | 
| 3 | 
            +
              gem 'activesupport', '~> 5.2.0'
         | 
| 4 4 | 
             
            end
         | 
| 5 5 |  | 
| 6 | 
            -
            appraise  | 
| 7 | 
            -
              gem  | 
| 8 | 
            -
              gem  | 
| 6 | 
            +
            appraise 'rails-6-0' do
         | 
| 7 | 
            +
              gem 'activerecord', '~> 6.0.0'
         | 
| 8 | 
            +
              gem 'activesupport', '~> 6.0.0'
         | 
| 9 9 | 
             
            end
         | 
    
        data/Gemfile
    CHANGED
    
    | @@ -3,9 +3,13 @@ source 'https://rubygems.org' | |
| 3 3 | 
             
            # Specify your gem's dependencies in cerego_view_models.gemspec
         | 
| 4 4 | 
             
            gemspec
         | 
| 5 5 |  | 
| 6 | 
            +
            # Add our linter rules as a development dependency
         | 
| 7 | 
            +
            gem 'rubocop'
         | 
| 8 | 
            +
            gem 'rubocop-iknow'
         | 
| 9 | 
            +
             | 
| 6 10 | 
             
            # Test metadata collection for circleci
         | 
| 7 11 | 
             
            gem 'minitest-ci'
         | 
| 8 12 |  | 
| 9 13 | 
             
            # Override gemspec for development version preferences
         | 
| 10 | 
            -
            gem 'activerecord',  '~>  | 
| 11 | 
            -
            gem 'activesupport', '~>  | 
| 14 | 
            +
            gem 'activerecord',  '~> 6.0.0'
         | 
| 15 | 
            +
            gem 'activesupport', '~> 6.0.0'
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,5 +1,5 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 1 | 
            +
            require 'bundler/gem_tasks'
         | 
| 2 | 
            +
            require 'rake/testtask'
         | 
| 3 3 |  | 
| 4 4 | 
             
            Rake::TestTask.new do |t|
         | 
| 5 5 | 
             
              t.libs << 'test'
         | 
| @@ -8,14 +8,14 @@ Rake::TestTask.new do |t| | |
| 8 8 | 
             
              t.verbose = true
         | 
| 9 9 | 
             
            end
         | 
| 10 10 |  | 
| 11 | 
            -
            desc  | 
| 11 | 
            +
            desc 'Open an IRB console with the test helpers'
         | 
| 12 12 | 
             
            task :test_console do
         | 
| 13 13 | 
             
              ruby %{-r bundler/setup -Ilib -e 'load "test/helpers/arvm_test_models.rb"' -r irb -e 'IRB.start(__FILE__)'}
         | 
| 14 14 | 
             
            end
         | 
| 15 15 |  | 
| 16 | 
            -
            desc  | 
| 16 | 
            +
            desc 'Open a Pry console with the test helpers'
         | 
| 17 17 | 
             
            task 'test_console:pry' do
         | 
| 18 | 
            -
              ruby % | 
| 18 | 
            +
              ruby %(-r bundler/setup -Ilib  -e 'load "test/helpers/arvm_test_models.rb"' -r pry -e 'Pry.start')
         | 
| 19 19 | 
             
            end
         | 
| 20 20 |  | 
| 21 21 | 
             
            task :default => :test
         | 
    
        data/gemfiles/rails_5_2.gemfile
    CHANGED
    
    | @@ -1,9 +1,9 @@ | |
| 1 1 | 
             
            # This file was generated by Appraisal
         | 
| 2 2 |  | 
| 3 | 
            -
            source  | 
| 3 | 
            +
            source 'https://rubygems.org'
         | 
| 4 4 |  | 
| 5 | 
            -
            gem  | 
| 6 | 
            -
            gem  | 
| 7 | 
            -
            gem  | 
| 5 | 
            +
            gem 'minitest-ci'
         | 
| 6 | 
            +
            gem 'activerecord', '~> 5.2.0'
         | 
| 7 | 
            +
            gem 'activesupport', '~> 5.2.0'
         | 
| 8 8 |  | 
| 9 | 
            -
            gemspec path:  | 
| 9 | 
            +
            gemspec path: '../'
         | 
    
        data/iknow_view_models.gemspec
    CHANGED
    
    | @@ -1,50 +1,52 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            -
            # coding: utf-8
         | 
| 3 2 |  | 
| 4 | 
            -
            lib = File.expand_path(' | 
| 3 | 
            +
            lib = File.expand_path('lib', __dir__)
         | 
| 5 4 | 
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 6 5 | 
             
            require 'iknow_view_models/version'
         | 
| 7 6 |  | 
| 8 7 | 
             
            Gem::Specification.new do |spec|
         | 
| 9 | 
            -
              spec.name          =  | 
| 8 | 
            +
              spec.name          = 'iknow_view_models'
         | 
| 10 9 | 
             
              spec.version       = IknowViewModels::VERSION
         | 
| 11 | 
            -
              spec.authors       = [ | 
| 12 | 
            -
              spec.email         = [ | 
| 13 | 
            -
              spec.summary       =  | 
| 14 | 
            -
              spec.description   =  | 
| 15 | 
            -
              spec.homepage      =  | 
| 16 | 
            -
              spec.license       =  | 
| 10 | 
            +
              spec.authors       = ['iKnow Team']
         | 
| 11 | 
            +
              spec.email         = ['edge@iknow.jp']
         | 
| 12 | 
            +
              spec.summary       = 'ViewModels provide a means of encapsulating a collection of related data and specifying its JSON serialization.'
         | 
| 13 | 
            +
              spec.description   = ''
         | 
| 14 | 
            +
              spec.homepage      = 'https://github.com/iknow/cerego_view_models'
         | 
| 15 | 
            +
              spec.license       = 'MIT'
         | 
| 17 16 |  | 
| 18 17 | 
             
              spec.files         = `git ls-files -z`.split("\x0")
         | 
| 19 18 | 
             
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 20 19 | 
             
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 21 | 
            -
              spec.require_paths = [ | 
| 22 | 
            -
             | 
| 23 | 
            -
              spec. | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
              spec.add_dependency  | 
| 27 | 
            -
             | 
| 28 | 
            -
              spec.add_dependency  | 
| 29 | 
            -
              spec.add_dependency  | 
| 30 | 
            -
              spec.add_dependency  | 
| 31 | 
            -
              spec.add_dependency  | 
| 32 | 
            -
             | 
| 33 | 
            -
              spec.add_dependency  | 
| 34 | 
            -
             | 
| 35 | 
            -
              spec.add_dependency  | 
| 36 | 
            -
              spec.add_dependency  | 
| 37 | 
            -
              spec.add_dependency  | 
| 38 | 
            -
              spec.add_dependency  | 
| 39 | 
            -
             | 
| 40 | 
            -
              spec. | 
| 41 | 
            -
              spec. | 
| 42 | 
            -
             | 
| 43 | 
            -
              spec.add_development_dependency  | 
| 44 | 
            -
              spec.add_development_dependency  | 
| 45 | 
            -
              spec.add_development_dependency  | 
| 46 | 
            -
              spec.add_development_dependency  | 
| 47 | 
            -
              spec.add_development_dependency  | 
| 48 | 
            -
              spec.add_development_dependency  | 
| 49 | 
            -
              spec.add_development_dependency  | 
| 20 | 
            +
              spec.require_paths = ['lib']
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              spec.required_ruby_version = 2.6
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              spec.add_dependency 'activerecord', '>= 5.0'
         | 
| 25 | 
            +
              spec.add_dependency 'activesupport', '>= 5.0'
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              spec.add_dependency 'acts_as_manual_list'
         | 
| 28 | 
            +
              spec.add_dependency 'deep_preloader', '>= 1.0.1'
         | 
| 29 | 
            +
              spec.add_dependency 'iknow_cache'
         | 
| 30 | 
            +
              spec.add_dependency 'iknow_params', '~> 2.2.0'
         | 
| 31 | 
            +
              spec.add_dependency 'keyword_builder'
         | 
| 32 | 
            +
              spec.add_dependency 'safe_values'
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              spec.add_dependency 'concurrent-ruby'
         | 
| 35 | 
            +
              spec.add_dependency 'jbuilder'
         | 
| 36 | 
            +
              spec.add_dependency 'json_schema'
         | 
| 37 | 
            +
              spec.add_dependency 'lazily'
         | 
| 38 | 
            +
              spec.add_dependency 'oj'
         | 
| 39 | 
            +
              spec.add_dependency 'renum'
         | 
| 40 | 
            +
              spec.add_dependency 'rgl'
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              spec.add_development_dependency 'appraisal'
         | 
| 43 | 
            +
              spec.add_development_dependency 'bundler'
         | 
| 44 | 
            +
              spec.add_development_dependency 'byebug'
         | 
| 45 | 
            +
              spec.add_development_dependency 'method_source'
         | 
| 46 | 
            +
              spec.add_development_dependency 'minitest-hooks'
         | 
| 47 | 
            +
              spec.add_development_dependency 'pg'
         | 
| 48 | 
            +
              spec.add_development_dependency 'pry'
         | 
| 49 | 
            +
              spec.add_development_dependency 'rake'
         | 
| 50 | 
            +
              spec.add_development_dependency 'rspec-expectations'
         | 
| 51 | 
            +
              spec.add_development_dependency 'sqlite3'
         | 
| 50 52 | 
             
            end
         | 
    
        data/lib/iknow_view_models.rb
    CHANGED
    
    | @@ -1,10 +1,12 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 6 | 
            -
            require  | 
| 7 | 
            -
            require  | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'iknow_view_models/version'
         | 
| 4 | 
            +
            require 'view_model'
         | 
| 5 | 
            +
            require 'view_model/controller'
         | 
| 6 | 
            +
            require 'view_model/active_record'
         | 
| 7 | 
            +
            require 'view_model/active_record/controller'
         | 
| 8 | 
            +
            require 'view_model/active_record/singular_nested_controller'
         | 
| 9 | 
            +
            require 'view_model/active_record/collection_nested_controller'
         | 
| 8 10 |  | 
| 9 11 | 
             
            module IknowViewModels
         | 
| 10 12 | 
             
            end
         | 
    
        data/lib/view_model.rb
    CHANGED
    
    | @@ -6,14 +6,19 @@ require 'jbuilder' | |
| 6 6 | 
             
            require 'deep_preloader'
         | 
| 7 7 |  | 
| 8 8 | 
             
            class ViewModel
         | 
| 9 | 
            -
              REFERENCE_ATTRIBUTE =  | 
| 10 | 
            -
              ID_ATTRIBUTE        =  | 
| 11 | 
            -
              TYPE_ATTRIBUTE      =  | 
| 12 | 
            -
              VERSION_ATTRIBUTE   =  | 
| 13 | 
            -
              NEW_ATTRIBUTE       =  | 
| 14 | 
            -
             | 
| 15 | 
            -
               | 
| 16 | 
            -
             | 
| 9 | 
            +
              REFERENCE_ATTRIBUTE = '_ref'
         | 
| 10 | 
            +
              ID_ATTRIBUTE        = 'id'
         | 
| 11 | 
            +
              TYPE_ATTRIBUTE      = '_type'
         | 
| 12 | 
            +
              VERSION_ATTRIBUTE   = '_version'
         | 
| 13 | 
            +
              NEW_ATTRIBUTE       = '_new'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # Migrations leave a metadata attribute _migrated on any views that they
         | 
| 16 | 
            +
              # alter. This attribute is accessible as metadata when deserializing migrated
         | 
| 17 | 
            +
              # input, and is included in the output serialization sent to clients.
         | 
| 18 | 
            +
              MIGRATED_ATTRIBUTE  = '_migrated'
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              Metadata = Struct.new(:id, :view_name, :schema_version, :new, :migrated) do
         | 
| 21 | 
            +
                alias_method :new?, :new
         | 
| 17 22 | 
             
              end
         | 
| 18 23 |  | 
| 19 24 | 
             
              class << self
         | 
| @@ -23,6 +28,7 @@ class ViewModel | |
| 23 28 | 
             
                attr_writer   :view_name
         | 
| 24 29 |  | 
| 25 30 | 
             
                def inherited(subclass)
         | 
| 31 | 
            +
                  super
         | 
| 26 32 | 
             
                  subclass.initialize_as_viewmodel
         | 
| 27 33 | 
             
                end
         | 
| 28 34 |  | 
| @@ -38,6 +44,7 @@ class ViewModel | |
| 38 44 | 
             
                      # try to auto-detect based on class name
         | 
| 39 45 | 
             
                      match = /(.*)View$/.match(self.name)
         | 
| 40 46 | 
             
                      raise ArgumentError.new("Could not auto-determine ViewModel name from class name '#{self.name}'") if match.nil?
         | 
| 47 | 
            +
             | 
| 41 48 | 
             
                      ViewModel::Registry.default_view_name(match[1])
         | 
| 42 49 | 
             
                    end
         | 
| 43 50 | 
             
                end
         | 
| @@ -72,6 +79,7 @@ class ViewModel | |
| 72 79 | 
             
                  end
         | 
| 73 80 |  | 
| 74 81 | 
             
                  attr_accessor attr
         | 
| 82 | 
            +
             | 
| 75 83 | 
             
                  define_method("deserialize_#{attr}") do |value, references: {}, deserialize_context: self.class.new_deserialize_context|
         | 
| 76 84 | 
             
                    self.public_send("#{attr}=", value)
         | 
| 77 85 | 
             
                  end
         | 
| @@ -99,8 +107,9 @@ class ViewModel | |
| 99 107 | 
             
                  type_name      = hash.delete(ViewModel::TYPE_ATTRIBUTE)
         | 
| 100 108 | 
             
                  schema_version = hash.delete(ViewModel::VERSION_ATTRIBUTE)
         | 
| 101 109 | 
             
                  new            = hash.delete(ViewModel::NEW_ATTRIBUTE) { false }
         | 
| 110 | 
            +
                  migrated       = hash.delete(ViewModel::MIGRATED_ATTRIBUTE) { false }
         | 
| 102 111 |  | 
| 103 | 
            -
                  Metadata.new(id, type_name, schema_version, new)
         | 
| 112 | 
            +
                  Metadata.new(id, type_name, schema_version, new, migrated)
         | 
| 104 113 | 
             
                end
         | 
| 105 114 |  | 
| 106 115 | 
             
                def extract_reference_only_metadata(hash)
         | 
| @@ -108,7 +117,7 @@ class ViewModel | |
| 108 117 | 
             
                  id             = hash.delete(ViewModel::ID_ATTRIBUTE)
         | 
| 109 118 | 
             
                  type_name      = hash.delete(ViewModel::TYPE_ATTRIBUTE)
         | 
| 110 119 |  | 
| 111 | 
            -
                  Metadata.new(id, type_name, nil, false)
         | 
| 120 | 
            +
                  Metadata.new(id, type_name, nil, false, false)
         | 
| 112 121 | 
             
                end
         | 
| 113 122 |  | 
| 114 123 | 
             
                def extract_reference_metadata(hash)
         | 
| @@ -116,7 +125,7 @@ class ViewModel | |
| 116 125 | 
             
                  hash.delete(ViewModel::REFERENCE_ATTRIBUTE)
         | 
| 117 126 | 
             
                end
         | 
| 118 127 |  | 
| 119 | 
            -
                def is_update_hash?(hash)
         | 
| 128 | 
            +
                def is_update_hash?(hash) # rubocop:disable Naming/PredicateName
         | 
| 120 129 | 
             
                  ViewModel::Schemas.verify_schema!(ViewModel::Schemas::VIEWMODEL_UPDATE, hash)
         | 
| 121 130 | 
             
                  hash.has_key?(ViewModel::ID_ATTRIBUTE) &&
         | 
| 122 131 | 
             
                    !hash.fetch(ViewModel::ActiveRecord::NEW_ATTRIBUTE, false)
         | 
| @@ -192,12 +201,12 @@ class ViewModel | |
| 192 201 | 
             
                    end
         | 
| 193 202 |  | 
| 194 203 | 
             
                    member_names.each do |attr|
         | 
| 195 | 
            -
                       | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 204 | 
            +
                      next unless view_hash.has_key?(attr)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                      viewmodel.public_send("deserialize_#{attr}",
         | 
| 207 | 
            +
                                            view_hash[attr],
         | 
| 208 | 
            +
                                            references: references,
         | 
| 209 | 
            +
                                            deserialize_context: deserialize_context)
         | 
| 201 210 | 
             
                    end
         | 
| 202 211 |  | 
| 203 212 | 
             
                    deserialize_context.run_callback(ViewModel::Callbacks::Hook::BeforeValidate, viewmodel)
         | 
| @@ -234,6 +243,11 @@ class ViewModel | |
| 234 243 | 
             
                  schema_version == self.schema_version
         | 
| 235 244 | 
             
                end
         | 
| 236 245 |  | 
| 246 | 
            +
                def schema_hash(schema_versions)
         | 
| 247 | 
            +
                  version_string = schema_versions.to_a.sort.join(',')
         | 
| 248 | 
            +
                  Base64.urlsafe_encode64(Digest::MD5.digest(version_string))
         | 
| 249 | 
            +
                end
         | 
| 250 | 
            +
             | 
| 237 251 | 
             
                def preload_for_serialization(viewmodels, serialize_context: new_serialize_context, include_referenced: true, lock: nil)
         | 
| 238 252 | 
             
                  Array.wrap(viewmodels).group_by(&:class).each do |type, views|
         | 
| 239 253 | 
             
                    DeepPreloader.preload(views.map(&:model),
         | 
| @@ -13,11 +13,12 @@ require 'view_model/access_control_error' | |
| 13 13 | 
             
            class ViewModel::AccessControl
         | 
| 14 14 | 
             
              Result = Struct.new(:permit, :error) do
         | 
| 15 15 | 
             
                def initialize(permit, error: nil)
         | 
| 16 | 
            -
                  raise ArgumentError.new( | 
| 16 | 
            +
                  raise ArgumentError.new('Successful AccessControl::Result may not have an error') if permit && error
         | 
| 17 | 
            +
             | 
| 17 18 | 
             
                  super(permit, error)
         | 
| 18 19 | 
             
                end
         | 
| 19 20 |  | 
| 20 | 
            -
                 | 
| 21 | 
            +
                alias_method :permit?, :permit
         | 
| 21 22 |  | 
| 22 23 | 
             
                # Merge this result with another access control result. Takes a block
         | 
| 23 24 | 
             
                # returning a result, and returns a combined result for both tests. Access
         | 
| @@ -129,6 +130,7 @@ class ViewModel::AccessControl | |
| 129 130 | 
             
                if @initial_editability_store.has_key?(view.object_id)
         | 
| 130 131 | 
             
                  raise RuntimeError.new("Access control data already recorded for view #{view.to_reference}")
         | 
| 131 132 | 
             
                end
         | 
| 133 | 
            +
             | 
| 132 134 | 
             
                @initial_editability_store[view.object_id] = initial_editability
         | 
| 133 135 | 
             
              end
         | 
| 134 136 |  | 
| @@ -136,6 +138,7 @@ class ViewModel::AccessControl | |
| 136 138 | 
             
                unless @initial_editability_store.has_key?(view.object_id)
         | 
| 137 139 | 
             
                  raise RuntimeError.new("No access control data recorded for view #{view.to_reference}")
         | 
| 138 140 | 
             
                end
         | 
| 141 | 
            +
             | 
| 139 142 | 
             
                @initial_editability_store.delete(view.object_id)
         | 
| 140 143 | 
             
              end
         | 
| 141 144 |  | 
| @@ -8,8 +8,9 @@ | |
| 8 8 | 
             
            class ViewModel::AccessControl::Composed < ViewModel::AccessControl
         | 
| 9 9 | 
             
              ComposedResult = Struct.new(:allow, :veto, :allow_error, :veto_error) do
         | 
| 10 10 | 
             
                def initialize(allow, veto, allow_error, veto_error)
         | 
| 11 | 
            -
                  raise ArgumentError.new( | 
| 12 | 
            -
                  raise ArgumentError.new( | 
| 11 | 
            +
                  raise ArgumentError.new('Non-vetoing result may not have a veto error') if veto_error  && !veto
         | 
| 12 | 
            +
                  raise ArgumentError.new('Allowing result may not have a allow error')   if allow_error && allow
         | 
| 13 | 
            +
             | 
| 13 14 | 
             
                  super
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
| @@ -64,7 +65,7 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl | |
| 64 65 | 
             
                attr_reader :reasons
         | 
| 65 66 |  | 
| 66 67 | 
             
                def initialize(nodes, reasons)
         | 
| 67 | 
            -
                  super( | 
| 68 | 
            +
                  super('Action not permitted because none of the possible conditions were met.', nodes)
         | 
| 68 69 | 
             
                  @reasons = reasons
         | 
| 69 70 | 
             
                end
         | 
| 70 71 |  | 
| @@ -92,7 +93,7 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl | |
| 92 93 | 
             
                end
         | 
| 93 94 |  | 
| 94 95 | 
             
                def initialize_as_composed_access_control
         | 
| 95 | 
            -
                  @included_checkers | 
| 96 | 
            +
                  @included_checkers = []
         | 
| 96 97 |  | 
| 97 98 | 
             
                  @edit_valid_ifs      = []
         | 
| 98 99 | 
             
                  @edit_valid_unlesses = []
         | 
| @@ -152,15 +153,16 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl | |
| 152 153 | 
             
                  @included_checkers.each do |ancestor|
         | 
| 153 154 | 
             
                    next unless visited.add?(ancestor)
         | 
| 154 155 | 
             
                    next if include_ancestor && !include_ancestor.call(ancestor)
         | 
| 156 | 
            +
             | 
| 155 157 | 
             
                    ancestor.each_check(check_name) { |x| yield x }
         | 
| 156 158 | 
             
                  end
         | 
| 157 159 | 
             
                end
         | 
| 158 160 |  | 
| 159 161 | 
             
                def inspect
         | 
| 160 | 
            -
                  s = super +  | 
| 161 | 
            -
                  s += inspect_checks.join( | 
| 162 | 
            +
                  s = super + '('
         | 
| 163 | 
            +
                  s += inspect_checks.join(', ')
         | 
| 162 164 | 
             
                  s += " includes checkers: #{@included_checkers.inspect}" if @included_checkers.present?
         | 
| 163 | 
            -
                  s +=  | 
| 165 | 
            +
                  s += ')'
         | 
| 164 166 | 
             
                  s
         | 
| 165 167 | 
             
                end
         | 
| 166 168 |  | 
| @@ -174,7 +176,6 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl | |
| 174 176 | 
             
                  checks << "edit_valid_unless: #{@edit_valid_unlesses.map(&:reason)}" if @edit_valid_unlesses.present?
         | 
| 175 177 | 
             
                  checks
         | 
| 176 178 | 
             
                end
         | 
| 177 | 
            -
             | 
| 178 179 | 
             
              end
         | 
| 179 180 |  | 
| 180 181 | 
             
              # final
         | 
| @@ -199,7 +200,7 @@ class ViewModel::AccessControl::Composed < ViewModel::AccessControl | |
| 199 200 |  | 
| 200 201 | 
             
                veto = vetoed_checker.present?
         | 
| 201 202 | 
             
                if veto
         | 
| 202 | 
            -
                  veto_error = vetoed_checker.error_type.new( | 
| 203 | 
            +
                  veto_error = vetoed_checker.error_type.new('Action not permitted because: ' +
         | 
| 203 204 | 
             
                                                             vetoed_checker.reason,
         | 
| 204 205 | 
             
                                                             env.view.blame_reference)
         | 
| 205 206 | 
             
                end
         |