Class: Tina4::Migration

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/migration.rb

Constant Summary collapse

TRACKING_TABLE =
"tina4_migration"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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

#dbObject (readonly)

Returns the value of attribute db.



8
9
10
# File 'lib/tina4/migration.rb', line 8

def db
  @db
end

#migrations_dirObject (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.

Parameters:

  • description (String)

    Human-readable migration name

  • migrations_dir (String) (defaults to: "migrations")

    Directory for migration files (default: ‘migrations’)

  • kind (String) (defaults to: "sql")

    File kind: ‘sql’ or ‘ruby’ (default: ‘sql’)



131
132
133
# File 'lib/tina4/migration.rb', line 131

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
# File 'lib/tina4/migration.rb', line 68

def create(description, kind = "sql")
  FileUtils.mkdir_p(@migrations_dir)
  timestamp = 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(/^_|_$/, "")

  if kind == "ruby" || kind == "python"
    filename = "#{timestamp}_#{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 = "#{timestamp}_#{safe_name}.sql"
  down_filename = "#{timestamp}_#{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_appliedObject

Get list of applied migration records (public alias for completed_migrations)



136
137
138
# File 'lib/tina4/migration.rb', line 136

def get_applied
  completed_migrations
end

#get_applied_migrationsObject

Alias for get_applied — parity with PHP/Node



141
142
143
# File 'lib/tina4/migration.rb', line 141

def get_applied_migrations
  get_applied
end

#get_filesObject

Get all migration files on disk, excluding .down files



151
152
153
154
155
# File 'lib/tina4/migration.rb', line 151

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_pendingObject

Get list of pending migration filenames (public alias for pending_migrations)



146
147
148
# File 'lib/tina4/migration.rb', line 146

def get_pending
  pending_migrations.map { |f| File.basename(f) }
end

#migrateObject 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.

Parameters:

  • name (String)

    Migration filename (e.g. “20240101000000_create_users.sql”)

  • batch (Integer)

    Batch number this migration belongs to

  • passed (Integer) (defaults to: 1)

    1 if successful (default), 0 if failed



116
117
118
# File 'lib/tina4/migration.rb', line 116

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.

Parameters:

  • name (String)

    Migration filename to remove



123
124
125
# File 'lib/tina4/migration.rb', line 123

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

#statusObject



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