Class: Familia::Migration::Base Abstract
- Inherits:
-
Object
- Object
- Familia::Migration::Base
- Defined in:
- lib/familia/migration/base.rb
Overview
Subclass and implement #migration_needed? and #migrate
Base class for Familia data migrations providing common infrastructure for idempotent data transformations and configuration updates.
Unlike traditional database migrations, these migrations:
- Don't track execution state in a migrations table
- Use #migration_needed? to detect if changes are required
- Support both dry-run and actual execution modes
- Provide built-in statistics tracking and logging
Subclassing Requirements
Subclasses must implement these methods:
- #migration_needed? - Detect if migration should run
- #migrate - Perform the actual migration work
Subclasses may override:
- #prepare - Initialize and validate migration parameters
- #down - Rollback logic for reversible migrations
Usage Patterns
For simple data migrations, extend Base directly:
class ConfigurationMigration < Familia::Migration::Base self.migration_id = '20260131_120000_config_update'
def migration_needed?
!redis.exists('config:new_feature_flag')
end
def migrate
for_realsies_this_time? do
redis.set('config:new_feature_flag', 'true')
end
track_stat(:settings_updated)
end
end
For record-by-record processing, use Model. For bulk updates with Redis pipelining, use Pipeline.
CLI Usage
ConfigurationMigration.cli_run # Dry run (preview) ConfigurationMigration.cli_run(['--run']) # Actual execution
Direct Known Subclasses
Class Attribute Summary collapse
-
.dependencies ⇒ Array<String>
List of migration IDs that must run before this one.
-
.description ⇒ String
Human-readable description of what this migration does.
-
.migration_id ⇒ String
Unique identifier for this migration.
Instance Attribute Summary collapse
-
#options ⇒ Hash
CLI options passed to migration, typically { run: true/false }.
-
#stats ⇒ Hash
readonly
Migration statistics for tracking operations performed.
Class Method Summary collapse
-
.check_only ⇒ Integer
Check-only mode for programmatic use.
-
.cli_run(argv = ARGV) ⇒ Integer
CLI entry point for migration execution.
-
.inherited(subclass) ⇒ Object
Auto-registration hook called when a subclass is defined.
-
.run(options = {}) ⇒ Boolean?
Main entry point for migration execution.
Instance Method Summary collapse
-
#actual_run? ⇒ Boolean
Check if migration is running in actual execution mode.
-
#dbclient ⇒ Redis
(also: #redis)
protected
Access to database client.
-
#debug(message = nil) ⇒ void
Log debug message.
-
#down ⇒ Object
Optional rollback logic.
-
#dry_run? ⇒ Boolean
Check if migration is running in dry-run mode.
-
#dry_run_only? { ... } ⇒ Boolean
Execute block only in dry run mode.
-
#error(message = nil) ⇒ void
Log error message.
-
#for_realsies_this_time? { ... } ⇒ Boolean
Execute block only in actual run mode.
-
#handle_migration_not_needed ⇒ nil
Handle case where migration is not needed.
-
#header(message) ⇒ void
Print formatted header with separator lines.
-
#info(message = nil) ⇒ void
Log informational message.
-
#initialize(options = {}) ⇒ Base
constructor
Initialize new migration instance with default state.
-
#migrate ⇒ Boolean
abstract
Perform actual migration work.
-
#migration_needed? ⇒ Boolean
abstract
Detect if migration needs to run.
-
#prepare ⇒ void
Hook for subclass initialization and validation.
-
#print_summary(title = nil) {|Symbol| ... } ⇒ void
Display migration summary with custom content block.
-
#progress(current, total, message = 'Processing', step = 100) ⇒ void
Progress indicator for long operations.
-
#reversible? ⇒ Boolean
Check if this migration has rollback support.
-
#run_mode_banner ⇒ void
Display run mode banner with appropriate warnings.
-
#schema_validation_enabled? ⇒ Boolean
Check if schema validation is enabled for this migration.
-
#separator ⇒ String
Generate separator line for visual formatting.
-
#skip_schema_validation! ⇒ void
Disable schema validation for this migration.
-
#track_stat(key, increment = 1) ⇒ nil
Increment named counter for migration statistics.
-
#validate_schema(obj, context: nil) ⇒ Hash
Validate an object against its schema.
-
#validate_schema!(obj, context: nil) ⇒ true
Validate an object or raise SchemaValidationError.
-
#warn(message = nil) ⇒ void
Log warning message.
Constructor Details
#initialize(options = {}) ⇒ Base
Initialize new migration instance with default state
150 151 152 153 |
# File 'lib/familia/migration/base.rb', line 150 def initialize( = {}) @options = @stats = Hash.new(0) # Auto-incrementing counter for tracking migration stats end |
Class Attribute Details
.dependencies ⇒ Array<String>
List of migration IDs that must run before this one
68 69 70 |
# File 'lib/familia/migration/base.rb', line 68 def dependencies @dependencies end |
.description ⇒ String
Human-readable description of what this migration does
64 65 66 |
# File 'lib/familia/migration/base.rb', line 64 def description @description end |
.migration_id ⇒ String
Unique identifier for this migration
60 61 62 |
# File 'lib/familia/migration/base.rb', line 60 def migration_id @migration_id end |
Instance Attribute Details
#options ⇒ Hash
CLI options passed to migration, typically { run: true/false }
143 144 145 |
# File 'lib/familia/migration/base.rb', line 143 def @options end |
#stats ⇒ Hash (readonly)
Migration statistics for tracking operations performed
147 148 149 |
# File 'lib/familia/migration/base.rb', line 147 def stats @stats end |
Class Method Details
.check_only ⇒ Integer
Check-only mode for programmatic use
Returns exit code indicating whether migration is needed. Does not perform any migration work.
116 117 118 119 120 |
# File 'lib/familia/migration/base.rb', line 116 def check_only migration = new migration.prepare migration.migration_needed? ? 1 : 0 end |
.cli_run(argv = ARGV) ⇒ Integer
CLI entry point for migration execution
Handles command-line argument parsing and returns appropriate exit codes. This is the recommended entry point for migration scripts.
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/familia/migration/base.rb', line 99 def cli_run(argv = ARGV) if argv.include?('--check') check_only else result = run(run: argv.include?('--run')) # nil (not needed) and true (success) both return 0 # only false (failure) returns 1 result == false ? 1 : 0 end end |
.inherited(subclass) ⇒ Object
Auto-registration hook called when a subclass is defined. Registers the migration with Familia::Migration.migrations.
74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/familia/migration/base.rb', line 74 def inherited(subclass) super subclass.dependencies ||= [] # Only register named classes (skip anonymous classes) # Use respond_to? to handle load-order edge cases where migrations # array may not yet be defined (e.g., Model class loading during require) return if subclass.name.nil? return unless Familia::Migration.respond_to?(:migrations) Familia::Migration.migrations << subclass end |
.run(options = {}) ⇒ Boolean?
Main entry point for migration execution
Orchestrates the full migration process including preparation, conditional execution based on #migration_needed?, and cleanup.
130 131 132 133 134 135 136 137 138 |
# File 'lib/familia/migration/base.rb', line 130 def run( = {}) migration = new migration. = migration.prepare return migration.handle_migration_not_needed unless migration.migration_needed? migration.migrate end |
Instance Method Details
#actual_run? ⇒ Boolean
Check if migration is running in actual execution mode
216 217 218 |
# File 'lib/familia/migration/base.rb', line 216 def actual_run? [:run] end |
#dbclient ⇒ Redis (protected) Also known as: redis
Access to database client
Provides a database connection for migrations that need to access data outside of Familia models.
439 440 441 |
# File 'lib/familia/migration/base.rb', line 439 def dbclient @dbclient ||= Familia.dbclient end |
#debug(message = nil) ⇒ void
This method returns an undefined value.
Log debug message
291 292 293 |
# File 'lib/familia/migration/base.rb', line 291 def debug( = nil) Familia.logger.debug { } if end |
#down ⇒ Object
Optional rollback logic. Override in subclass to support reversible migrations.
195 196 197 |
# File 'lib/familia/migration/base.rb', line 195 def down # Override in subclass for rollback support end |
#dry_run? ⇒ Boolean
Check if migration is running in dry-run mode
210 211 212 |
# File 'lib/familia/migration/base.rb', line 210 def dry_run? ![:run] end |
#dry_run_only? { ... } ⇒ Boolean
Execute block only in dry run mode
Use this for dry-run specific logging or validation.
248 249 250 251 252 253 |
# File 'lib/familia/migration/base.rb', line 248 def dry_run_only? return false unless dry_run? yield if block_given? true end |
#error(message = nil) ⇒ void
This method returns an undefined value.
Log error message
305 306 307 |
# File 'lib/familia/migration/base.rb', line 305 def error( = nil) Familia.logger.error { } if end |
#for_realsies_this_time? { ... } ⇒ Boolean
Execute block only in actual run mode
Use this to wrap code that makes actual changes to the system. In dry-run mode, the block will not be executed.
235 236 237 238 239 240 |
# File 'lib/familia/migration/base.rb', line 235 def for_realsies_this_time? return false unless actual_run? yield if block_given? true end |
#handle_migration_not_needed ⇒ nil
Handle case where migration is not needed
Called automatically when #migration_needed? returns false. Provides standard messaging about migration state.
355 356 357 358 359 360 361 |
# File 'lib/familia/migration/base.rb', line 355 def handle_migration_not_needed info('') info('Migration needed? false.') info('') info('This usually means that the migration has already been applied.') nil end |
#header(message) ⇒ void
This method returns an undefined value.
Print formatted header with separator lines
275 276 277 278 279 |
# File 'lib/familia/migration/base.rb', line 275 def header() info '' info separator info(.upcase) end |
#info(message = nil) ⇒ void
This method returns an undefined value.
Log informational message
284 285 286 |
# File 'lib/familia/migration/base.rb', line 284 def info( = nil) Familia.logger.info { } if end |
#migrate ⇒ Boolean
Subclasses must implement this method
Perform actual migration work
This is the core migration logic that subclasses must implement. Use #for_realsies_this_time? to wrap actual changes and #track_stat to record operations performed.
176 177 178 |
# File 'lib/familia/migration/base.rb', line 176 def migrate raise NotImplementedError, "#{self.class} must implement #migrate" end |
#migration_needed? ⇒ Boolean
Subclasses must implement this method
Detect if migration needs to run
This method should implement idempotency logic by checking current system state and returning false if migration has already been applied or is not needed.
189 190 191 |
# File 'lib/familia/migration/base.rb', line 189 def migration_needed? raise NotImplementedError, "#{self.class} must implement #migration_needed?" end |
#prepare ⇒ void
This method returns an undefined value.
Hook for subclass initialization and validation
Override this method to:
- Set instance variables needed by the migration
- Validate prerequisites and configuration
- Initialize connections or external dependencies
163 164 165 |
# File 'lib/familia/migration/base.rb', line 163 def prepare debug('Preparing migration - default implementation') end |
#print_summary(title = nil) {|Symbol| ... } ⇒ void
This method returns an undefined value.
Display migration summary with custom content block
Automatically adjusts header based on run mode and yields the current mode to the block for conditional content.
339 340 341 342 343 344 345 346 347 |
# File 'lib/familia/migration/base.rb', line 339 def print_summary(title = nil) if dry_run? header(title || 'DRY RUN SUMMARY') yield(:dry_run) if block_given? else header(title || 'ACTUAL RUN SUMMARY') yield(:actual_run) if block_given? end end |
#progress(current, total, message = 'Processing', step = 100) ⇒ void
This method returns an undefined value.
Progress indicator for long operations
Displays progress updates at specified intervals to avoid overwhelming the log output during bulk operations.
325 326 327 328 329 |
# File 'lib/familia/migration/base.rb', line 325 def progress(current, total, = 'Processing', step = 100) return unless current % step == 0 || current == total info "#{} #{current}/#{total}..." end |
#reversible? ⇒ Boolean
Check if this migration has rollback support.
202 203 204 |
# File 'lib/familia/migration/base.rb', line 202 def reversible? method(:down).owner != Familia::Migration::Base end |
#run_mode_banner ⇒ void
This method returns an undefined value.
Display run mode banner with appropriate warnings
222 223 224 225 226 |
# File 'lib/familia/migration/base.rb', line 222 def header("Running in #{dry_run? ? 'DRY RUN' : 'ACTUAL RUN'} mode") info(dry_run? ? 'No changes will be made' : 'Changes WILL be applied to the database') info(separator) end |
#schema_validation_enabled? ⇒ Boolean
Check if schema validation is enabled for this migration
Schema validation is enabled by default when SchemaRegistry is loaded. Use #skip_schema_validation! to disable for this migration instance.
417 418 419 |
# File 'lib/familia/migration/base.rb', line 417 def schema_validation_enabled? @schema_validation != false && Familia::SchemaRegistry.loaded? end |
#separator ⇒ String
Generate separator line for visual formatting
311 312 313 |
# File 'lib/familia/migration/base.rb', line 311 def separator '-' * 60 end |
#skip_schema_validation! ⇒ void
This method returns an undefined value.
Disable schema validation for this migration
Call this in #prepare or at any point before validation to skip all schema validation for this migration run.
427 428 429 |
# File 'lib/familia/migration/base.rb', line 427 def skip_schema_validation! @schema_validation = false end |
#track_stat(key, increment = 1) ⇒ nil
Increment named counter for migration statistics
Use this to track operations, errors, skipped records, etc. Statistics are automatically displayed in migration summaries.
265 266 267 268 |
# File 'lib/familia/migration/base.rb', line 265 def track_stat(key, increment = 1) @stats[key] += increment nil end |
#validate_schema(obj, context: nil) ⇒ Hash
Validate an object against its schema
Uses the SchemaRegistry to validate an object's data against its registered JSON schema. Returns validation results without raising exceptions.
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/familia/migration/base.rb', line 374 def validate_schema(obj, context: nil) return { valid: true, errors: [] } unless schema_validation_enabled? klass_name = obj.class.name data = obj.respond_to?(:to_h) ? obj.to_h : obj result = Familia::SchemaRegistry.validate(klass_name, data) unless result[:valid] context_msg = context ? " (#{context})" : '' warn "Schema validation failed for #{klass_name}#{context_msg}: #{result[:errors].size} error(s)" result[:errors].first(3).each do |e| debug " - #{e['type'] || 'error'}: #{e['data_pointer'] || '/'}" end end result end |
#validate_schema!(obj, context: nil) ⇒ true
Validate an object or raise SchemaValidationError
Uses the SchemaRegistry to validate an object's data against its registered JSON schema. Raises an exception if validation fails.
402 403 404 405 406 407 408 409 |
# File 'lib/familia/migration/base.rb', line 402 def validate_schema!(obj, context: nil) result = validate_schema(obj, context: context) unless result[:valid] raise Familia::SchemaValidationError.new(result[:errors]) end true end |