familia 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.rst +45 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +11 -1
- data/docs/guides/writing-migrations.md +345 -0
- data/examples/migrations/v1_to_v2_serialization_migration.rb +374 -0
- data/examples/schemas/customer.json +33 -0
- data/examples/schemas/session.json +27 -0
- data/familia.gemspec +2 -0
- data/lib/familia/data_type/types/hashkey.rb +0 -238
- data/lib/familia/data_type/types/listkey.rb +4 -110
- data/lib/familia/data_type/types/sorted_set.rb +0 -365
- data/lib/familia/data_type/types/stringkey.rb +0 -139
- data/lib/familia/data_type/types/unsorted_set.rb +2 -122
- data/lib/familia/features/schema_validation.rb +139 -0
- data/lib/familia/migration/base.rb +447 -0
- data/lib/familia/migration/errors.rb +31 -0
- data/lib/familia/migration/model.rb +418 -0
- data/lib/familia/migration/pipeline.rb +226 -0
- data/lib/familia/migration/rake_tasks.rake +3 -0
- data/lib/familia/migration/rake_tasks.rb +160 -0
- data/lib/familia/migration/registry.rb +364 -0
- data/lib/familia/migration/runner.rb +311 -0
- data/lib/familia/migration/script.rb +234 -0
- data/lib/familia/migration.rb +43 -0
- data/lib/familia/schema_registry.rb +173 -0
- data/lib/familia/settings.rb +63 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +1 -0
- data/try/features/schema_registry_try.rb +193 -0
- data/try/features/schema_validation_feature_try.rb +218 -0
- data/try/migration/base_try.rb +226 -0
- data/try/migration/errors_try.rb +67 -0
- data/try/migration/integration_try.rb +451 -0
- data/try/migration/model_try.rb +431 -0
- data/try/migration/pipeline_try.rb +460 -0
- data/try/migration/rake_tasks_try.rb +61 -0
- data/try/migration/registry_try.rb +199 -0
- data/try/migration/runner_try.rb +311 -0
- data/try/migration/schema_validation_try.rb +201 -0
- data/try/migration/script_try.rb +192 -0
- data/try/migration/v1_to_v2_serialization_try.rb +513 -0
- data/try/performance/benchmarks_try.rb +11 -12
- metadata +44 -1
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# lib/familia/migration/base.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
module Familia
|
|
6
|
+
module Migration
|
|
7
|
+
# Base class for Familia data migrations providing common infrastructure
|
|
8
|
+
# for idempotent data transformations and configuration updates.
|
|
9
|
+
#
|
|
10
|
+
# Unlike traditional database migrations, these migrations:
|
|
11
|
+
# - Don't track execution state in a migrations table
|
|
12
|
+
# - Use {#migration_needed?} to detect if changes are required
|
|
13
|
+
# - Support both dry-run and actual execution modes
|
|
14
|
+
# - Provide built-in statistics tracking and logging
|
|
15
|
+
#
|
|
16
|
+
# ## Subclassing Requirements
|
|
17
|
+
#
|
|
18
|
+
# Subclasses must implement these methods:
|
|
19
|
+
# - {#migration_needed?} - Detect if migration should run
|
|
20
|
+
# - {#migrate} - Perform the actual migration work
|
|
21
|
+
#
|
|
22
|
+
# Subclasses may override:
|
|
23
|
+
# - {#prepare} - Initialize and validate migration parameters
|
|
24
|
+
# - {#down} - Rollback logic for reversible migrations
|
|
25
|
+
#
|
|
26
|
+
# ## Usage Patterns
|
|
27
|
+
#
|
|
28
|
+
# For simple data migrations, extend Base directly:
|
|
29
|
+
#
|
|
30
|
+
# class ConfigurationMigration < Familia::Migration::Base
|
|
31
|
+
# self.migration_id = '20260131_120000_config_update'
|
|
32
|
+
#
|
|
33
|
+
# def migration_needed?
|
|
34
|
+
# !redis.exists('config:new_feature_flag')
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# def migrate
|
|
38
|
+
# for_realsies_this_time? do
|
|
39
|
+
# redis.set('config:new_feature_flag', 'true')
|
|
40
|
+
# end
|
|
41
|
+
# track_stat(:settings_updated)
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# For record-by-record processing, use {Model}.
|
|
46
|
+
# For bulk updates with Redis pipelining, use {Pipeline}.
|
|
47
|
+
#
|
|
48
|
+
# ## CLI Usage
|
|
49
|
+
#
|
|
50
|
+
# ConfigurationMigration.cli_run # Dry run (preview)
|
|
51
|
+
# ConfigurationMigration.cli_run(['--run']) # Actual execution
|
|
52
|
+
#
|
|
53
|
+
# @abstract Subclass and implement {#migration_needed?} and {#migrate}
|
|
54
|
+
# @see Model For individual record processing
|
|
55
|
+
# @see Pipeline For bulk record processing with pipelining
|
|
56
|
+
class Base
|
|
57
|
+
class << self
|
|
58
|
+
# Unique identifier for this migration
|
|
59
|
+
# @return [String] format: {timestamp}_{snake_case_name}
|
|
60
|
+
attr_accessor :migration_id
|
|
61
|
+
|
|
62
|
+
# Human-readable description of what this migration does
|
|
63
|
+
# @return [String]
|
|
64
|
+
attr_accessor :description
|
|
65
|
+
|
|
66
|
+
# List of migration IDs that must run before this one
|
|
67
|
+
# @return [Array<String>]
|
|
68
|
+
attr_accessor :dependencies
|
|
69
|
+
|
|
70
|
+
# Auto-registration hook called when a subclass is defined.
|
|
71
|
+
# Registers the migration with Familia::Migration.migrations.
|
|
72
|
+
#
|
|
73
|
+
# @param subclass [Class] The inheriting class
|
|
74
|
+
def inherited(subclass)
|
|
75
|
+
super
|
|
76
|
+
subclass.dependencies ||= []
|
|
77
|
+
|
|
78
|
+
# Only register named classes (skip anonymous classes)
|
|
79
|
+
# Use respond_to? to handle load-order edge cases where migrations
|
|
80
|
+
# array may not yet be defined (e.g., Model class loading during require)
|
|
81
|
+
return if subclass.name.nil?
|
|
82
|
+
return unless Familia::Migration.respond_to?(:migrations)
|
|
83
|
+
|
|
84
|
+
Familia::Migration.migrations << subclass
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# CLI entry point for migration execution
|
|
88
|
+
#
|
|
89
|
+
# Handles command-line argument parsing and returns appropriate exit codes.
|
|
90
|
+
# This is the recommended entry point for migration scripts.
|
|
91
|
+
#
|
|
92
|
+
# @param argv [Array<String>] command-line arguments (default: ARGV)
|
|
93
|
+
# @return [Integer] exit code (0 = success, 1 = error/action required)
|
|
94
|
+
#
|
|
95
|
+
# @example In migration script
|
|
96
|
+
# if __FILE__ == $0
|
|
97
|
+
# exit(MyMigration.cli_run)
|
|
98
|
+
# end
|
|
99
|
+
def cli_run(argv = ARGV)
|
|
100
|
+
if argv.include?('--check')
|
|
101
|
+
check_only
|
|
102
|
+
else
|
|
103
|
+
result = run(run: argv.include?('--run'))
|
|
104
|
+
# nil (not needed) and true (success) both return 0
|
|
105
|
+
# only false (failure) returns 1
|
|
106
|
+
result == false ? 1 : 0
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Check-only mode for programmatic use
|
|
111
|
+
#
|
|
112
|
+
# Returns exit code indicating whether migration is needed.
|
|
113
|
+
# Does not perform any migration work.
|
|
114
|
+
#
|
|
115
|
+
# @return [Integer] 0 if no migration needed, 1 if migration needed
|
|
116
|
+
def check_only
|
|
117
|
+
migration = new
|
|
118
|
+
migration.prepare
|
|
119
|
+
migration.migration_needed? ? 1 : 0
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Main entry point for migration execution
|
|
123
|
+
#
|
|
124
|
+
# Orchestrates the full migration process including preparation,
|
|
125
|
+
# conditional execution based on {#migration_needed?}, and cleanup.
|
|
126
|
+
#
|
|
127
|
+
# @param options [Hash] CLI options, typically { run: true/false }
|
|
128
|
+
# @return [Boolean, nil] true if migration completed successfully,
|
|
129
|
+
# nil if not needed, false if failed
|
|
130
|
+
def run(options = {})
|
|
131
|
+
migration = new
|
|
132
|
+
migration.options = options
|
|
133
|
+
migration.prepare
|
|
134
|
+
|
|
135
|
+
return migration.handle_migration_not_needed unless migration.migration_needed?
|
|
136
|
+
|
|
137
|
+
migration.migrate
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# CLI options passed to migration, typically { run: true/false }
|
|
142
|
+
# @return [Hash] the options hash
|
|
143
|
+
attr_accessor :options
|
|
144
|
+
|
|
145
|
+
# Migration statistics for tracking operations performed
|
|
146
|
+
# @return [Hash] auto-incrementing counters for named statistics
|
|
147
|
+
attr_reader :stats
|
|
148
|
+
|
|
149
|
+
# Initialize new migration instance with default state
|
|
150
|
+
def initialize(options = {})
|
|
151
|
+
@options = options
|
|
152
|
+
@stats = Hash.new(0) # Auto-incrementing counter for tracking migration stats
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Hook for subclass initialization and validation
|
|
156
|
+
#
|
|
157
|
+
# Override this method to:
|
|
158
|
+
# - Set instance variables needed by the migration
|
|
159
|
+
# - Validate prerequisites and configuration
|
|
160
|
+
# - Initialize connections or external dependencies
|
|
161
|
+
#
|
|
162
|
+
# @return [void]
|
|
163
|
+
def prepare
|
|
164
|
+
debug('Preparing migration - default implementation')
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Perform actual migration work
|
|
168
|
+
#
|
|
169
|
+
# This is the core migration logic that subclasses must implement.
|
|
170
|
+
# Use {#for_realsies_this_time?} to wrap actual changes and
|
|
171
|
+
# {#track_stat} to record operations performed.
|
|
172
|
+
#
|
|
173
|
+
# @abstract Subclasses must implement this method
|
|
174
|
+
# @return [Boolean] true if migration succeeded
|
|
175
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
176
|
+
def migrate
|
|
177
|
+
raise NotImplementedError, "#{self.class} must implement #migrate"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Detect if migration needs to run
|
|
181
|
+
#
|
|
182
|
+
# This method should implement idempotency logic by checking
|
|
183
|
+
# current system state and returning false if migration has
|
|
184
|
+
# already been applied or is not needed.
|
|
185
|
+
#
|
|
186
|
+
# @abstract Subclasses must implement this method
|
|
187
|
+
# @return [Boolean] true if migration should proceed
|
|
188
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
189
|
+
def migration_needed?
|
|
190
|
+
raise NotImplementedError, "#{self.class} must implement #migration_needed?"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Optional rollback logic.
|
|
194
|
+
# Override in subclass to support reversible migrations.
|
|
195
|
+
def down
|
|
196
|
+
# Override in subclass for rollback support
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Check if this migration has rollback support.
|
|
200
|
+
#
|
|
201
|
+
# @return [Boolean] true if down method is overridden
|
|
202
|
+
def reversible?
|
|
203
|
+
method(:down).owner != Familia::Migration::Base
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# === Run Mode Control ===
|
|
207
|
+
|
|
208
|
+
# Check if migration is running in dry-run mode
|
|
209
|
+
# @return [Boolean] true if no changes should be made
|
|
210
|
+
def dry_run?
|
|
211
|
+
!options[:run]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Check if migration is running in actual execution mode
|
|
215
|
+
# @return [Boolean] true if changes will be applied
|
|
216
|
+
def actual_run?
|
|
217
|
+
options[:run]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Display run mode banner with appropriate warnings
|
|
221
|
+
# @return [void]
|
|
222
|
+
def run_mode_banner
|
|
223
|
+
header("Running in #{dry_run? ? 'DRY RUN' : 'ACTUAL RUN'} mode")
|
|
224
|
+
info(dry_run? ? 'No changes will be made' : 'Changes WILL be applied to the database')
|
|
225
|
+
info(separator)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Execute block only in actual run mode
|
|
229
|
+
#
|
|
230
|
+
# Use this to wrap code that makes actual changes to the system.
|
|
231
|
+
# In dry-run mode, the block will not be executed.
|
|
232
|
+
#
|
|
233
|
+
# @yield Block to execute if in actual run mode
|
|
234
|
+
# @return [Boolean] true if block was executed, false if skipped
|
|
235
|
+
def for_realsies_this_time?
|
|
236
|
+
return false unless actual_run?
|
|
237
|
+
|
|
238
|
+
yield if block_given?
|
|
239
|
+
true
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Execute block only in dry run mode
|
|
243
|
+
#
|
|
244
|
+
# Use this for dry-run specific logging or validation.
|
|
245
|
+
#
|
|
246
|
+
# @yield Block to execute if in dry run mode
|
|
247
|
+
# @return [Boolean] true if block was executed, false if skipped
|
|
248
|
+
def dry_run_only?
|
|
249
|
+
return false unless dry_run?
|
|
250
|
+
|
|
251
|
+
yield if block_given?
|
|
252
|
+
true
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# === Statistics Tracking ===
|
|
256
|
+
|
|
257
|
+
# Increment named counter for migration statistics
|
|
258
|
+
#
|
|
259
|
+
# Use this to track operations, errors, skipped records, etc.
|
|
260
|
+
# Statistics are automatically displayed in migration summaries.
|
|
261
|
+
#
|
|
262
|
+
# @param key [Symbol] stat name to increment
|
|
263
|
+
# @param increment [Integer] amount to add (default 1)
|
|
264
|
+
# @return [nil]
|
|
265
|
+
def track_stat(key, increment = 1)
|
|
266
|
+
@stats[key] += increment
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# === Logging Interface ===
|
|
271
|
+
|
|
272
|
+
# Print formatted header with separator lines
|
|
273
|
+
# @param message [String] header text to display
|
|
274
|
+
# @return [void]
|
|
275
|
+
def header(message)
|
|
276
|
+
info ''
|
|
277
|
+
info separator
|
|
278
|
+
info(message.upcase)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Log informational message
|
|
282
|
+
# @param message [String] message to log
|
|
283
|
+
# @return [void]
|
|
284
|
+
def info(message = nil)
|
|
285
|
+
Familia.logger.info { message } if message
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Log debug message
|
|
289
|
+
# @param message [String] message to log
|
|
290
|
+
# @return [void]
|
|
291
|
+
def debug(message = nil)
|
|
292
|
+
Familia.logger.debug { message } if message
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Log warning message
|
|
296
|
+
# @param message [String] message to log
|
|
297
|
+
# @return [void]
|
|
298
|
+
def warn(message = nil)
|
|
299
|
+
Familia.logger.warn { message } if message
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Log error message
|
|
303
|
+
# @param message [String] message to log
|
|
304
|
+
# @return [void]
|
|
305
|
+
def error(message = nil)
|
|
306
|
+
Familia.logger.error { message } if message
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Generate separator line for visual formatting
|
|
310
|
+
# @return [String] dash separator line
|
|
311
|
+
def separator
|
|
312
|
+
'-' * 60
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Progress indicator for long operations
|
|
316
|
+
#
|
|
317
|
+
# Displays progress updates at specified intervals to avoid
|
|
318
|
+
# overwhelming the log output during bulk operations.
|
|
319
|
+
#
|
|
320
|
+
# @param current [Integer] current item number
|
|
321
|
+
# @param total [Integer] total items to process
|
|
322
|
+
# @param message [String] operation description
|
|
323
|
+
# @param step [Integer] progress reporting frequency (default 100)
|
|
324
|
+
# @return [void]
|
|
325
|
+
def progress(current, total, message = 'Processing', step = 100)
|
|
326
|
+
return unless current % step == 0 || current == total
|
|
327
|
+
|
|
328
|
+
info "#{message} #{current}/#{total}..."
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Display migration summary with custom content block
|
|
332
|
+
#
|
|
333
|
+
# Automatically adjusts header based on run mode and yields
|
|
334
|
+
# the current mode to the block for conditional content.
|
|
335
|
+
#
|
|
336
|
+
# @param title [String, nil] custom summary title
|
|
337
|
+
# @yield [Symbol] :dry_run or :actual_run for conditional content
|
|
338
|
+
# @return [void]
|
|
339
|
+
def print_summary(title = nil)
|
|
340
|
+
if dry_run?
|
|
341
|
+
header(title || 'DRY RUN SUMMARY')
|
|
342
|
+
yield(:dry_run) if block_given?
|
|
343
|
+
else
|
|
344
|
+
header(title || 'ACTUAL RUN SUMMARY')
|
|
345
|
+
yield(:actual_run) if block_given?
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Handle case where migration is not needed
|
|
350
|
+
#
|
|
351
|
+
# Called automatically when {#migration_needed?} returns false.
|
|
352
|
+
# Provides standard messaging about migration state.
|
|
353
|
+
#
|
|
354
|
+
# @return [nil]
|
|
355
|
+
def handle_migration_not_needed
|
|
356
|
+
info('')
|
|
357
|
+
info('Migration needed? false.')
|
|
358
|
+
info('')
|
|
359
|
+
info('This usually means that the migration has already been applied.')
|
|
360
|
+
nil
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# === Schema Validation ===
|
|
364
|
+
|
|
365
|
+
# Validate an object against its schema
|
|
366
|
+
#
|
|
367
|
+
# Uses the SchemaRegistry to validate an object's data against
|
|
368
|
+
# its registered JSON schema. Returns validation results without
|
|
369
|
+
# raising exceptions.
|
|
370
|
+
#
|
|
371
|
+
# @param obj [Object] object with to_h method
|
|
372
|
+
# @param context [String, nil] context for error messages (e.g., 'before transform')
|
|
373
|
+
# @return [Hash] { valid: Boolean, errors: Array }
|
|
374
|
+
def validate_schema(obj, context: nil)
|
|
375
|
+
return { valid: true, errors: [] } unless schema_validation_enabled?
|
|
376
|
+
|
|
377
|
+
klass_name = obj.class.name
|
|
378
|
+
data = obj.respond_to?(:to_h) ? obj.to_h : obj
|
|
379
|
+
|
|
380
|
+
result = Familia::SchemaRegistry.validate(klass_name, data)
|
|
381
|
+
|
|
382
|
+
unless result[:valid]
|
|
383
|
+
context_msg = context ? " (#{context})" : ''
|
|
384
|
+
warn "Schema validation failed for #{klass_name}#{context_msg}: #{result[:errors].size} error(s)"
|
|
385
|
+
result[:errors].first(3).each do |e|
|
|
386
|
+
debug " - #{e['type'] || 'error'}: #{e['data_pointer'] || '/'}"
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
result
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Validate an object or raise SchemaValidationError
|
|
394
|
+
#
|
|
395
|
+
# Uses the SchemaRegistry to validate an object's data against
|
|
396
|
+
# its registered JSON schema. Raises an exception if validation fails.
|
|
397
|
+
#
|
|
398
|
+
# @param obj [Object] object with to_h method
|
|
399
|
+
# @param context [String, nil] context for error messages
|
|
400
|
+
# @return [true] if valid
|
|
401
|
+
# @raise [Familia::SchemaValidationError] if validation fails
|
|
402
|
+
def validate_schema!(obj, context: nil)
|
|
403
|
+
result = validate_schema(obj, context: context)
|
|
404
|
+
unless result[:valid]
|
|
405
|
+
raise Familia::SchemaValidationError.new(result[:errors])
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
true
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Check if schema validation is enabled for this migration
|
|
412
|
+
#
|
|
413
|
+
# Schema validation is enabled by default when SchemaRegistry is loaded.
|
|
414
|
+
# Use {#skip_schema_validation!} to disable for this migration instance.
|
|
415
|
+
#
|
|
416
|
+
# @return [Boolean]
|
|
417
|
+
def schema_validation_enabled?
|
|
418
|
+
@schema_validation != false && Familia::SchemaRegistry.loaded?
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Disable schema validation for this migration
|
|
422
|
+
#
|
|
423
|
+
# Call this in {#prepare} or at any point before validation to
|
|
424
|
+
# skip all schema validation for this migration run.
|
|
425
|
+
#
|
|
426
|
+
# @return [void]
|
|
427
|
+
def skip_schema_validation!
|
|
428
|
+
@schema_validation = false
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
protected
|
|
432
|
+
|
|
433
|
+
# Access to database client
|
|
434
|
+
#
|
|
435
|
+
# Provides a database connection for migrations
|
|
436
|
+
# that need to access data outside of Familia models.
|
|
437
|
+
#
|
|
438
|
+
# @return [Redis] configured Redis connection
|
|
439
|
+
def dbclient
|
|
440
|
+
@dbclient ||= Familia.dbclient
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Alias for dbclient for convenience
|
|
444
|
+
alias redis dbclient
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Familia
|
|
4
|
+
module Migration
|
|
5
|
+
module Errors
|
|
6
|
+
# Base class for all migration errors
|
|
7
|
+
class MigrationError < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Raised when attempting to rollback a migration without a down method
|
|
10
|
+
class NotReversible < MigrationError; end
|
|
11
|
+
|
|
12
|
+
# Raised when attempting to rollback a migration that hasn't been applied
|
|
13
|
+
class NotApplied < MigrationError; end
|
|
14
|
+
|
|
15
|
+
# Raised when a migration ID cannot be found
|
|
16
|
+
class NotFound < MigrationError; end
|
|
17
|
+
|
|
18
|
+
# Raised when a migration's dependencies haven't been applied
|
|
19
|
+
class DependencyNotMet < MigrationError; end
|
|
20
|
+
|
|
21
|
+
# Raised when attempting to rollback a migration that other migrations depend on
|
|
22
|
+
class HasDependents < MigrationError; end
|
|
23
|
+
|
|
24
|
+
# Raised when migration dependencies form a cycle
|
|
25
|
+
class CircularDependency < MigrationError; end
|
|
26
|
+
|
|
27
|
+
# Raised when migration preconditions are not met
|
|
28
|
+
class PreconditionFailed < MigrationError; end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|