module ViewModel::MigratableView
def inherited(base)
def inherited(base) super base.initialize_as_migratable_view end
def initialize_as_migratable_view
def initialize_as_migratable_view @migrations_lock = Monitor.new @migration_classes = {} @migration_paths = {} @realized_migration_paths = true end
def migrates(from:, to:, inherit: nil, at: nil, &block)
def migrates(from:, to:, inherit: nil, at: nil, &block) @migrations_lock.synchronize do migration_superclass = if inherit raise ArgumentError.new('Must provide inherit version') unless at inherit.migration_class(at - 1, at) else ViewModel::Migration end builder = ViewModel::Migration::Builder.new(migration_superclass) builder.instance_exec(&block) migration_class = builder.build! const_set(:"Migration_#{from}_To_#{to}", migration_class) @migration_classes[[from, to]] = migration_class @realized_migration_paths = false end end
def migration_class(from, to)
def migration_class(from, to) @migration_classes.fetch([from, to]) do raise ViewModel::Migration::NoPathError.new(self, from, to) end end
def migration_path(from:, to:)
def migration_path(from:, to:) @migrations_lock.synchronize do realize_paths! unless @realized_migration_paths migrations = @migration_paths.fetch([from, to]) do raise ViewModel::Migration::NoPathError.new(self, from, to) end migrations end end
def realize_paths!
def realize_paths! @migration_paths.clear graph = RGL::DirectedAdjacencyGraph.new # Add a vertex for the current version, in case no edges reach it graph.add_vertex(self.schema_version) # Add edges backwards, as we care about paths from the latest version @migration_classes.each_key do |from, to| graph.add_edge(to, from) end paths = graph.dijkstra_shortest_paths(Hash.new { 1 }, self.schema_version) paths.each do |target_version, path| next if path.nil? || path.length == 1 # Store the path forwards rather than backwards path_migration_classes = path.reverse.each_cons(2).map do |from, to| @migration_classes.fetch([from, to]) end key = [target_version, schema_version] @migration_paths[key] = path_migration_classes.map(&:new) end @realized_paths = true end