class ViewModel::Migrator
def initialize(required_versions)
def initialize(required_versions) @paths = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h| if required_version != viewmodel_class.schema_version path = viewmodel_class.migration_path(from: required_version, to: viewmodel_class.schema_version) h[viewmodel_class.view_name] = path end end @versions = required_versions.each_with_object({}) do |(viewmodel_class, required_version), h| h[viewmodel_class.view_name] = [required_version, viewmodel_class.schema_version] end end
def migrate!(serialization)
def migrate!(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 = [] loop do unvisited_refs = references.keys - visited_refs break if unvisited_refs.empty? unvisited_refs.each do |ref| migrate_tree!(references[ref], references: references) end visited_refs.concat(unvisited_refs) end GarbageCollection.garbage_collect_references!(serialization) if references.empty? serialization.delete('references') end end
def migrate_tree!(node, references:)
def migrate_tree!(node, references:) case node when Hash if (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. return if node[EXCLUDE_FROM_MIGRATION] if migrate_viewmodel!(type, version, node, references) node[ViewModel::MIGRATED_ATTRIBUTE] = true end end node.each_value do |child| migrate_tree!(child, references: references) end when Array node.each { |child| migrate_tree!(child, references: references) } end end
def migrate_viewmodel!(_view_name, _version, _view_hash, _references)
def migrate_viewmodel!(_view_name, _version, _view_hash, _references) raise RuntimeError.new('abstract method') end
def migrated_deep_schema_version(viewmodel_class, required_versions, include_referenced: true)
def migrated_deep_schema_version(viewmodel_class, required_versions, include_referenced: true) deep_schema_version = viewmodel_class.deep_schema_version(include_referenced: include_referenced) if required_versions.present? deep_schema_version = deep_schema_version.dup required_versions.each do |required_vm_class, required_version| name = required_vm_class.view_name if deep_schema_version.has_key?(name) deep_schema_version[name] = required_version end end end deep_schema_version end