Class: Familia::Migration::Runner
- Inherits:
-
Object
- Object
- Familia::Migration::Runner
- Defined in:
- lib/familia/migration/runner.rb
Overview
Runner orchestrates migration execution with dependency resolution.
Provides methods for querying migration status, validating dependencies, and executing migrations in the correct order using topological sorting.
Instance Attribute Summary collapse
-
#logger ⇒ Logger
readonly
Logger for migration output.
-
#migrations ⇒ Array<Class>
readonly
Migration classes to operate on.
-
#registry ⇒ Registry
readonly
Registry for tracking applied migrations.
Instance Method Summary collapse
-
#initialize(migrations: nil, registry: nil, logger: nil) ⇒ Runner
constructor
Initialize a new Runner instance.
-
#pending ⇒ Array<Class>
Get all pending (unapplied) migrations.
-
#rollback(migration_id) ⇒ Hash
Rollback a previously applied migration.
-
#run(dry_run: false, limit: nil) ⇒ Array<Hash>
Run all pending migrations in dependency order.
-
#run_one(migration_class_or_id, dry_run: false) ⇒ Hash
Run a single migration.
-
#status ⇒ Array<Hash>
Get the status of all migrations.
-
#validate ⇒ Array<Hash>
Validate migration dependencies and configuration.
Constructor Details
#initialize(migrations: nil, registry: nil, logger: nil) ⇒ Runner
Initialize a new Runner instance.
38 39 40 41 42 |
# File 'lib/familia/migration/runner.rb', line 38 def initialize(migrations: nil, registry: nil, logger: nil) @migrations = migrations || Familia::Migration.migrations @registry = registry || Registry.new @logger = logger || Familia.logger end |
Instance Attribute Details
#logger ⇒ Logger (readonly)
Returns Logger for migration output.
30 31 32 |
# File 'lib/familia/migration/runner.rb', line 30 def logger @logger end |
#migrations ⇒ Array<Class> (readonly)
Returns Migration classes to operate on.
24 25 26 |
# File 'lib/familia/migration/runner.rb', line 24 def migrations @migrations end |
#registry ⇒ Registry (readonly)
Returns Registry for tracking applied migrations.
27 28 29 |
# File 'lib/familia/migration/runner.rb', line 27 def registry @registry end |
Instance Method Details
#pending ⇒ Array<Class>
Get all pending (unapplied) migrations.
78 79 80 |
# File 'lib/familia/migration/runner.rb', line 78 def pending @registry.pending(@migrations) end |
#rollback(migration_id) ⇒ Hash
Rollback a previously applied migration.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/familia/migration/runner.rb', line 198 def rollback(migration_id) klass = resolve_migration(migration_id) unless @registry.applied?(migration_id) raise Familia::Migration::Errors::NotApplied, "Migration #{migration_id} is not applied" end # Batch fetch all applied migrations to check for dependents applied_ids = @registry.all_applied.map { |e| e[:migration_id] }.to_set # Check no dependents are applied @migrations.each do |m| if (m.dependencies || []).include?(migration_id) && applied_ids.include?(m.migration_id) raise Familia::Migration::Errors::HasDependents, "Cannot rollback: #{m.migration_id} depends on #{migration_id}" end end instance = klass.new unless instance.reversible? raise Familia::Migration::Errors::NotReversible, "Migration #{migration_id} does not have a down method" end result = { migration_id: migration_id } begin instance.down @registry.record_rollback(migration_id) result[:status] = :rolled_back rescue StandardError => e result[:status] = :failed result[:error] = e. end result end |
#run(dry_run: false, limit: nil) ⇒ Array<Hash>
Run all pending migrations in dependency order.
125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/familia/migration/runner.rb', line 125 def run(dry_run: false, limit: nil) pending_migrations = topological_sort(pending) pending_migrations = pending_migrations.first(limit) if limit results = [] pending_migrations.each do |klass| result = run_one(klass, dry_run: dry_run) results << result break if result[:status] == :failed end results end |
#run_one(migration_class_or_id, dry_run: false) ⇒ Hash
Run a single migration.
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/familia/migration/runner.rb', line 149 def run_one(migration_class_or_id, dry_run: false) klass = resolve_migration(migration_class_or_id) # Validate dependencies are applied (klass.dependencies || []).each do |dep_id| unless @registry.applied?(dep_id) raise Familia::Migration::Errors::DependencyNotMet, "Dependency #{dep_id} not applied for #{klass.migration_id}" end end instance = klass.new(run: !dry_run) instance.prepare result = { migration_id: klass.migration_id, dry_run: dry_run, stats: {}, } begin if instance.migration_needed? instance.migrate result[:status] = :success result[:stats] = instance.stats @registry.record_applied(instance, instance.stats) unless dry_run else result[:status] = :skipped end rescue StandardError => e result[:status] = :failed result[:error] = e. @logger.error { "Migration failed: #{e.}" } end result end |
#status ⇒ Array<Hash>
Get the status of all migrations.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/familia/migration/runner.rb', line 55 def status # Batch fetch all applied migrations with timestamps in a single Redis call applied_info = @registry.all_applied.each_with_object({}) do |entry, hash| hash[entry[:migration_id]] = entry[:applied_at] end @migrations.map do |klass| id = klass.migration_id applied_at = applied_info[id] { migration_id: id, description: klass.description, status: applied_at ? :applied : :pending, applied_at: applied_at, reversible: klass.new.reversible?, } end end |
#validate ⇒ Array<Hash>
Validate migration dependencies and configuration.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/familia/migration/runner.rb', line 90 def validate issues = [] # Check for missing dependencies all_ids = @migrations.map(&:migration_id) @migrations.each do |klass| (klass.dependencies || []).each do |dep_id| unless all_ids.include?(dep_id) issues << { type: :missing_dependency, migration_id: klass.migration_id, dependency: dep_id, } end end end # Check for circular dependencies begin topological_sort(@migrations) rescue Familia::Migration::Errors::CircularDependency => e issues << { type: :circular_dependency, message: e. } end issues end |