# frozen_string_literal: trueclassViewModelclassMigratorEXCLUDE_FROM_MIGRATION='_exclude_from_migration'class<<selfdefmigrated_deep_schema_version(viewmodel_class,required_versions,include_referenced: true)deep_schema_version=viewmodel_class.deep_schema_version(include_referenced: include_referenced)ifrequired_versions.present?deep_schema_version=deep_schema_version.duprequired_versions.eachdo|required_vm_class,required_version|name=required_vm_class.view_nameifdeep_schema_version.has_key?(name)deep_schema_version[name]=required_versionendendenddeep_schema_versionendenddefinitialize(required_versions)@paths=required_versions.each_with_object({})do|(viewmodel_class,required_version),h|ifrequired_version!=viewmodel_class.schema_versionpath=viewmodel_class.migration_path(from: required_version,to: viewmodel_class.schema_version)h[viewmodel_class.view_name]=pathendend@versions=required_versions.each_with_object({})do|(viewmodel_class,required_version),h|h[viewmodel_class.view_name]=[required_version,viewmodel_class.schema_version]endenddefmigrate!(serialization)references=(serialization['references']||={})# First visit everything except references; there's no issue with adding# new references during this.migrate_tree!(serialization.except('references'),references: references)# While visiting references itself, we need to take care that we can# concurrently modify them (e.g. by adding new referenced views).# Moreover, such added references must themselves be visited, as they'll# be synthesized at the current version and so may need to be migrated# down to the client's requested version.visited_refs=[]loopdounvisited_refs=references.keys-visited_refsbreakifunvisited_refs.empty?unvisited_refs.eachdo|ref|migrate_tree!(references[ref],references: references)endvisited_refs.concat(unvisited_refs)endGarbageCollection.garbage_collect_references!(serialization)ifreferences.empty?serialization.delete('references')endendprivatedefmigrate_tree!(node,references:)casenodewhenHashif(type=node[ViewModel::TYPE_ATTRIBUTE])version=node[ViewModel::VERSION_ATTRIBUTE]# We allow subtrees to be excluded from migration. This is used# internally to permit stub references that are not a full# serialization of the referenced type: see ViewModel::Cache.returnifnode[EXCLUDE_FROM_MIGRATION]ifmigrate_viewmodel!(type,version,node,references)node[ViewModel::MIGRATED_ATTRIBUTE]=trueendendnode.each_valuedo|child|migrate_tree!(child,references: references)endwhenArraynode.each{|child|migrate_tree!(child,references: references)}endenddefmigrate_viewmodel!(_view_name,_version,_view_hash,_references)raiseRuntimeError.new('abstract method')endendclassUpMigrator<Migratorprivatedefmigrate_tree!(node,references:)ifnode.is_a?(Hash)&&node[ViewModel::TYPE_ATTRIBUTE]==ViewModel::ActiveRecord::FUNCTIONAL_UPDATE_TYPEmigrate_functional_update!(node,references: references)elsesuperendendNESTED_FUPDATE_TYPES=['append','update'].freeze# The functional update structure uses `_type` internally with a# context-dependent meaning. Retrospectively this was a poor choice, but we# need to account for it here.defmigrate_functional_update!(node,references:)actions=node[ViewModel::ActiveRecord::ACTIONS_ATTRIBUTE]actions&.eachdo|action|action_type=action[ViewModel::TYPE_ATTRIBUTE]nextunlessNESTED_FUPDATE_TYPES.include?(action_type)values=action[ViewModel::ActiveRecord::VALUES_ATTRIBUTE]values&.eachdo|value|migrate_tree!(value,references: references)endendenddefmigrate_viewmodel!(view_name,source_version,view_hash,references)path=@paths[view_name]returnfalseunlesspathrequired_version,current_version=@versions[view_name]returnfalseifsource_version==current_version# We assume that an unspecified source version is the same as the required# version (i.e. the version demanded by the client request).unlesssource_version.nil?||source_version==required_versionraiseViewModel::Migration::UnspecifiedVersionError.new(view_name,source_version)endpath.eachdo|migration|migration.up(view_hash,references)endview_hash[ViewModel::VERSION_ATTRIBUTE]=current_versiontrueendend# down migrations find a reverse path from the current schema version to the# specific version requested by the client.classDownMigrator<Migratorprivatedefmigrate_viewmodel!(view_name,source_version,view_hash,references)path=@paths[view_name]returnfalseunlesspath# In a serialized output, the source version should always be the present# and the current version, unless already modified by a parent migrationrequired_version,current_version=@versions[view_name]returnfalseifsource_version==required_versionunlesssource_version==current_versionraiseViewModel::Migration::UnspecifiedVersionError.new(view_name,source_version)endpath.reverse_eachdo|migration|migration.down(view_hash,references)endview_hash[ViewModel::VERSION_ATTRIBUTE]=required_versiontrueendendend