Class: Songbird::SQLGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/songbird/sql_generator.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(migrations_path: nil) ⇒ SQLGenerator

Returns a new instance of SQLGenerator.



12
13
14
# File 'lib/songbird/sql_generator.rb', line 12

def initialize(migrations_path: nil)
  @discoverer = MigrationDiscoverer.new(migrations_path)
end

Instance Attribute Details

#discovererObject (readonly)

Returns the value of attribute discoverer.



10
11
12
# File 'lib/songbird/sql_generator.rb', line 10

def discoverer
  @discoverer
end

Instance Method Details

#generate_migration_sql(migration_info) ⇒ Object

Generate SQL for a single migration



66
67
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
# File 'lib/songbird/sql_generator.rb', line 66

def generate_migration_sql(migration_info)
  begin
    migration_class = @discoverer.load_migration_class(migration_info[:version])

    if migration_class.nil?
      return "-- ERROR: Could not load migration class for #{migration_info[:version]}"
    end

    # Intercept SQL at the connection execute level
    captured_sql = []
    connection = ActiveRecord::Base.connection

    # Store original execute method
    original_execute = connection.method(:execute)

    # Override execute to capture SQL
    connection.define_singleton_method(:execute) do |sql, name = nil|
      captured_sql << sql.to_s
      # Return empty result to prevent actual execution
      ActiveRecord::Result.new([], [])
    end

    # Suppress Rails migration logging
    original_verbose = ActiveRecord::Migration.verbose
    original_logger = ActiveRecord::Base.logger

    ActiveRecord::Migration.verbose = false
    ActiveRecord::Base.logger = Logger.new(IO::NULL)

    # Run the migration to generate SQL
    migration_instance = migration_class.new
    migration_instance.migrate(:up)

    # Format captured SQL
    if captured_sql.empty?
      "-- No SQL statements captured for migration #{migration_info[:version]}"
    else
      captured_sql.map { |sql| format_sql_statement(sql) }.join("\n")
    end

  rescue => e
    "-- ERROR in migration #{migration_info[:version]}: #{e.message}"
  ensure
    # Restore original execute method and logging
    if connection && original_execute
      connection.define_singleton_method(:execute, original_execute)
    end

    if defined?(original_verbose)
      ActiveRecord::Migration.verbose = original_verbose
    end

    if defined?(original_logger)
      ActiveRecord::Base.logger = original_logger
    end
  end
end

#generate_schema_migration_sql(version) ⇒ Object

Generate the schema_migrations INSERT statement



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/songbird/sql_generator.rb', line 125

def generate_schema_migration_sql(version)
  result_sql = nil
  if defined?(ActiveRecord::SchemaMigration)
    begin
      # Create SchemaMigration instance with connection pool
      pool = ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool
      schema_migration = ActiveRecord::SchemaMigration.new(pool)

      # Use the instance's arel_table and replicate Rails' create_version logic
      arel_table = schema_migration.arel_table
      insert_manager = Arel::InsertManager.new(arel_table)
      insert_manager.insert(arel_table[schema_migration.primary_key] => version)
      result_sql = insert_manager.to_sql
    rescue => e
      puts "-- Using default INSERT command after error generating schema migration SQL: #{e.message}."
      result_sql = nil
    end
  else
    result_sql = nil
  end
  if result_sql.nil?
    # Fallback
    result_sql = "INSERT INTO schema_migrations (version) VALUES ('#{version}')"
  end
  result_sql
end

#process(version: nil, output_format: :raw) ⇒ Object

Process migration request - generate SQL for a specific migration or show usage



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/songbird/sql_generator.rb', line 17

def process(version: nil, output_format: :raw)
  unless @discoverer.migrations_exist?
    raise "Migrations directory not found: #{@discoverer.migrations_path}"
  end

  if version
    # Generate SQL for specific migration
    migration_info = @discoverer.find_migration_by_version(version)
    if migration_info.nil?
      return "-- Migration #{version} not found"
    end

    migration_sql = generate_migration_sql(migration_info)
    schema_sql = generate_schema_migration_sql(migration_info[:version])
    sql_statement = format_migration_block(migration_info, migration_sql, schema_sql, output_format)

    case output_format
    when :commented
      format_with_comments([sql_statement])
    else
      sql_statement
    end
  else
    # Show usage with recent migrations when no version specified
    recent_migrations = @discoverer.load_all_migrations.last(3)

    usage_text = <<~USAGE
      -- Songbird: Generate SQL for Rails migrations
      --
      -- Usage: rake db:migrate:sql[VERSION]
      -- Example: rake db:migrate:sql[20241201000001]
      --
      -- This will generate the SQL commands for the specified migration
      -- that you can execute manually in your database.
    USAGE

    if recent_migrations.any?
      usage_text += "\n-- Recent migrations:\n"
      recent_migrations.reverse.each do |migration|
        name = humanize_migration_name(migration[:name])
        usage_text += "-- #{migration[:version]} - #{name}\n"
      end
    end

    return usage_text
  end
end