Skip to content

Instantly share code, notes, and snippets.

@kddnewton
Created March 31, 2022 18:26
Show Gist options
  • Save kddnewton/02ba1c07290ee2af6bcd9c713be470e4 to your computer and use it in GitHub Desktop.
Save kddnewton/02ba1c07290ee2af6bcd9c713be470e4 to your computer and use it in GitHub Desktop.
Migration stuff
# frozen_string_literal: true
module ActiveRecord
class Migration
class MigrationError < StandardError
end
RenameColumn = Struct.new(:table_name, :column_name, :new_column_name, keyword_init: true)
RemoveColumn = Struct.new(:table_name, :column_name, keyword_init: true)
AddColumn = Struct.new(:table_name, :column_name, :type, :options, keyword_init: true)
attr_reader :changes
def initialize
@changes = []
end
def rename_column(table_name, column_name, new_column_name)
changes << RenameColumn.new(table_name: table_name, column_name: column_name, new_column_name: new_column_name)
end
def remove_column(table_name, column_name)
changes << RemoveColumn.new(table_name: table_name, column_name: column_name)
end
def add_column(table_name, column_name, type, options = {})
changes << AddColumn.new(table_name: table_name, column_name: column_name, type: type, options: options)
end
def method_missing(method, *args, &block)
raise MigrationError, method
end
# You need this method so that you can run ActiveRecord::Migration[7.0] and
# get the same object back.
def self.[](version)
self
end
end
end
# frozen_string_literal: true
class MyMigration < ActiveRecord::Migration[7.0]
def change
rename_column :my_table, :old_column, :new_column
add_column :my_table, :foobars, :string
end
end
#!/usr/bin/env ruby
# frozen_string_literal: true
# USAGE: ./runner.rb <path_to_migration_file>
filepath = ARGV.first
# Start a separate process in which to run our custom ActiveRecord::Migration
# object. This is going to record all of the changes that are made within
# structs that will then serialize and validate.
rd, wr = IO.pipe
pid = Process.fork do
rd.close
# Require our library and the migration(s).
require_relative "./library"
require_relative filepath
# TODO: get constant name from filepath
# Create an instance of the migration and call the change method.
migration = MyMigration.new
begin
migration.change
rescue ActiveRecord::Migration::MigrationError => error
# Serialize the error
wr.puts("error")
wr.puts(error.message)
else
# Serialize the changes to Marshal and write them to the pipe.
wr.puts("success")
wr.puts(Marshal.dump(migration.changes))
end
wr.close
end
# Close the write end of the pipe in the parent process and wait for the child
# process to exit.
wr.close
Process.wait
# Now that the child process has exited, read the changes from the pipe and load
# them back into their respective structs.
require_relative "./library"
if rd.readline.chomp == "success"
changes = Marshal.load(rd.read)
rd.close
# TODO: require the application in order to do any validation.
# For an example here, we're just going to loop through the various changes.
changes.each.with_index(1) do |change, index|
puts "CHANGE %d: #{change}" % index
end
else
error = rd.read.chomp
rd.close
warn("You tried to call #{error}, but that's not allowed. Silly.")
exit(1)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment