Class: Tina4::Migration
- Inherits:
-
Object
- Object
- Tina4::Migration
- Defined in:
- lib/tina4/migration.rb
Constant Summary collapse
- TRACKING_TABLE =
"tina4_migration"
Instance Attribute Summary collapse
-
#db ⇒ Object
readonly
Returns the value of attribute db.
-
#migrations_dir ⇒ Object
readonly
Returns the value of attribute migrations_dir.
Class Method Summary collapse
-
.create_migration(description, migrations_dir: "migrations", kind: "sql") ⇒ Object
Create a migration file — static helper for parity with Python/Node.
Instance Method Summary collapse
-
#create(description, kind = "sql") ⇒ Object
Create a new migration file.
-
#get_applied ⇒ Object
Get list of applied migration records (public alias for completed_migrations).
-
#get_applied_migrations ⇒ Object
Alias for get_applied — parity with PHP/Node.
-
#get_files ⇒ Object
Get all migration files on disk, excluding .down files.
-
#get_pending ⇒ Object
Get list of pending migration filenames (public alias for pending_migrations).
-
#initialize(db, migrations_dir: nil) ⇒ Migration
constructor
A new instance of Migration.
-
#migrate ⇒ Object
(also: #run)
Run all pending migrations.
-
#record_migration(name, batch, passed: 1) ⇒ Object
Insert a record into the migration tracking table.
-
#remove_migration_record(name) ⇒ Object
Delete a migration record from the tracking table by filename.
-
#rollback(steps = 1) ⇒ Object
Rollback last batch (or N steps).
- #status ⇒ Object
Constructor Details
#initialize(db, migrations_dir: nil) ⇒ Migration
Returns a new instance of Migration.
10 11 12 13 14 |
# File 'lib/tina4/migration.rb', line 10 def initialize(db, migrations_dir: nil) @db = db @migrations_dir = migrations_dir || resolve_migrations_dir ensure_tracking_table end |
Instance Attribute Details
#db ⇒ Object (readonly)
Returns the value of attribute db.
8 9 10 |
# File 'lib/tina4/migration.rb', line 8 def db @db end |
#migrations_dir ⇒ Object (readonly)
Returns the value of attribute migrations_dir.
8 9 10 |
# File 'lib/tina4/migration.rb', line 8 def migrations_dir @migrations_dir end |
Class Method Details
.create_migration(description, migrations_dir: "migrations", kind: "sql") ⇒ Object
Create a migration file — static helper for parity with Python/Node.
147 148 149 |
# File 'lib/tina4/migration.rb', line 147 def self.create_migration(description, migrations_dir: "migrations", kind: "sql") new(nil, migrations_dir: migrations_dir).create(description, kind) end |
Instance Method Details
#create(description, kind = "sql") ⇒ Object
Create a new migration file
kind="ruby" — creates timestamp_description.rb with MigrationBase subclass (default) kind="sql" — creates timestamp_description.sql + .down.sql kind="python" — alias for "ruby" (class-based scaffold for cross-framework parity)
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 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 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/tina4/migration.rb', line 68 def create(description, kind = "sql") FileUtils.mkdir_p(@migrations_dir) = Time.now.strftime("%Y%m%d%H%M%S") created_at = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC") safe_name = description.gsub(/[^a-z0-9]+/i, "_").downcase.gsub(/^_|_$/, "") # MEASURED 2026-08-06: the accepted kind differed in every framework - # python "python", php "php", ruby "ruby" OR "python", node "class" - and # NONE validated it, so create_migration(..., kind="python") produced a # code migration in Python and Ruby and a SILENT .sql file in PHP and # Node. "code" is now the canonical spelling in all four; each keeps its # own language name as a legacy alias; anything else raises. # Ruby also accepted "python", which was a copy-paste from the master # and is dropped: a Ruby project never wants a .py migration. kind = (kind || "sql").to_s.strip.downcase unless %w[sql code ruby].include?(kind) raise ArgumentError, "Unknown migration kind #{kind.inspect}. Use 'sql' (default) or " \ "'code' (alias: 'ruby'). An unrecognised kind used to produce a " \ ".sql file silently, which is why this now raises." end if %w[code ruby].include?(kind) filename = "#{}_#{safe_name}.rb" filepath = File.join(@migrations_dir, filename) File.write(filepath, <<~RUBY) # frozen_string_literal: true # Migration: #{description} # Created: #{created_at} class #{classify(description)} < Tina4::MigrationBase def up(db = nil) # db.execute("CREATE TABLE ...") end def down(db = nil) # db.execute("DROP TABLE IF EXISTS ...") end end RUBY Tina4::Log.info("Created migration: #{filename}") return filepath end # Default: SQL up_filename = "#{}_#{safe_name}.sql" down_filename = "#{}_#{safe_name}.down.sql" up_path = File.join(@migrations_dir, up_filename) down_path = File.join(@migrations_dir, down_filename) File.write(up_path, "-- Migration: #{description}\n-- Created: #{created_at}\n\n") File.write(down_path, "-- Rollback: #{description}\n-- Created: #{created_at}\n\n") Tina4::Log.info("Created migration: #{up_filename}") up_path end |
#get_applied ⇒ Object
Get list of applied migration records (public alias for completed_migrations)
152 153 154 |
# File 'lib/tina4/migration.rb', line 152 def get_applied completed_migrations end |
#get_applied_migrations ⇒ Object
Alias for get_applied — parity with PHP/Node
157 158 159 |
# File 'lib/tina4/migration.rb', line 157 def get_applied_migrations get_applied end |
#get_files ⇒ Object
Get all migration files on disk, excluding .down files
167 168 169 170 171 |
# File 'lib/tina4/migration.rb', line 167 def get_files migration_files = Dir.glob(File.join(@migrations_dir, "*.sql")).reject { |f| f.end_with?(".down.sql") } migration_files += Dir.glob(File.join(@migrations_dir, "*.rb")) migration_files.map { |f| File.basename(f) }.sort end |
#get_pending ⇒ Object
Get list of pending migration filenames (public alias for pending_migrations)
162 163 164 |
# File 'lib/tina4/migration.rb', line 162 def get_pending pending_migrations.map { |f| File.basename(f) } end |
#migrate ⇒ Object Also known as: run
Run all pending migrations
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/tina4/migration.rb', line 17 def migrate pending = pending_migrations if pending.empty? Tina4::Log.info("No pending migrations") return [] end batch = next_batch_number results = [] pending.each do |file| result = run_migration(file, batch) results << result # Stop on failure break if result[:status] == "failed" end results end |
#record_migration(name, batch, passed: 1) ⇒ Object
Insert a record into the migration tracking table.
132 133 134 |
# File 'lib/tina4/migration.rb', line 132 def record_migration(name, batch, passed: 1) _record_migration(name, batch, passed: passed) end |
#remove_migration_record(name) ⇒ Object
Delete a migration record from the tracking table by filename.
139 140 141 |
# File 'lib/tina4/migration.rb', line 139 def remove_migration_record(name) _remove_migration_record(name) end |
#rollback(steps = 1) ⇒ Object
Rollback last batch (or N steps)
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/tina4/migration.rb', line 38 def rollback(steps = 1) completed = completed_migrations_with_batch return [] if completed.empty? # Get the last N unique batches batches = completed.map { |m| m[:batch] }.uniq.sort.reverse batches_to_rollback = batches.first(steps) results = [] completed.select { |m| batches_to_rollback.include?(m[:batch]) } .sort_by { |m| -m[:id] } .each do |migration| result = rollback_migration(migration[:migration_name]) results << result end results end |
#status ⇒ Object
56 57 58 59 60 61 |
# File 'lib/tina4/migration.rb', line 56 def status { completed: completed_migrations, pending: pending_migrations.map { |f| File.basename(f) } } end |