namespace :data_fix do
DATAFIX_DIR = File.join("db", "migrate-data") def puts_and_log message, level = :info puts "** #{message}" logger = Rails.logger logger.send(level) { |msg| "data_fix: #{message}" } end # strip characters and whitespace to create valid filenames, also lowercase def sanitize_filename(name) if(name.is_a? Integer) return name.to_s end return name.tr( "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÑñÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž", "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz" ).downcase.strip.gsub(' ', '-').gsub(/[^\w.-]/, '') end def exit_if_not_installed if not ActiveRecord::Base.connection.table_exists? :data_fixes puts_and_log "ERROR: it seems like data_fix:init has not been run yet", :error exit(1) end end desc 'Add a :data_fixes table to the DB (run the first time only)' task :init => :environment do unless ActiveRecord::Base.connection.table_exists? :data_fixes ActiveRecord::Base.connection.create_table :data_fixes do |t| t.string :name t.timestamps end end Dir.mkdir DATAFIX_DIR if not Dir.exists? DATAFIX_DIR end desc 'Create a new data fix' task :create, [:name] => :environment do |_, args| exit_if_not_installed time = DateTime.now.strftime("%Y%m%d%H%M%S") f = File.new(File.join(DATAFIX_DIR, sanitize_filename("#{time}_#{args.name}.rb")), "w") f.write("") f.close end def backup_database dry_run config = Rails.configuration.database_configuration[Rails.env] puts_and_log "Backing up #{Rails.env} database" case config["adapter"] when "sqlite3" db_name = config["database"] backup_db_name = db_name.gsub(/\.sqlite3$/, "-#{Date.today.strftime('%Y-%m-%d')}.sqlite3") cmd = "cp \"#{db_name}\" \"#{backup_db_name}\"" when "mysql2" db_host = config["host"] db_name = config["database"] db_username = config["username"] db_password = config["password"] backup_db_name = "db/#{db_name}-#{Rails.env}-#{Date.today.strftime('%Y-%m-%d')}.sql" cmd = "mysqldump -u #{db_name} -p #{db_password} #{db_name} > \"#{backup_db_name}\"" end if dry_run puts_and_log "WOULD RUN: #{cmd}" else system cmd end puts_and_log "backup performed and available as #{backup_db_name}." end def df_status files = Dir.glob("*.rb", base: DATAFIX_DIR).sort puts "** data_fix status" printf "%3s %-60s %-10s\n", "run", "file in #{DATAFIX_DIR}", "when" files.each do |file| run = DataFix::DataFix.find_by(name: file) printf "%-3s %-60s %-10s\n", run ? "Y" : "N", file, run ? run.created_at : "" end puts "" end def df_run dry_run = true, steps = nil, no_backup = false df_status files = Dir.glob("*.rb", base: DATAFIX_DIR).sort already_run = DataFix::DataFix.all.map { |x| x.name }.sort to_run = steps ? (files - already_run)[0..(steps.to_i - 1)] : (files - already_run) puts_and_log "data fix to run: #{to_run.join(", ")}" # if we are doing things for real, backup the DB, unless # explicitly asked not to do it backup_database(dry_run) unless no_backup # sort filenames by date (same as sorting alphabetically, since # the date is in the name to_run.sort.each do |file| puts_and_log "starting data migration '#{file}'" if dry_run puts_and_log "WOULD RUN: ${File.join(DATAFIX_DIR, file)}" else load File.join(DATAFIX_DIR, file) DataFix::DataFix.create(name: file) end puts_and_log "done!" end end desc 'Dry run the required data fixes (STEP= to specify the optional number of steps)' task dry_run: :environment do exit_if_not_installed df_run true, ENV["STEP"] end desc 'Run the required data fixes (STEP= to specify the optional number of steps)' task run: :environment do exit_if_not_installed df_run false, ENV["STEP"] end desc 'Status: show which data fixes have been run' task status: :environment do exit_if_not_installed df_status end desc 'Reconcile: declare that all data fixes have been run' task reconcile: :environment do exit_if_not_installed DataFix::DataFix.destroy_all files = Dir.glob("*.rb", base: DATAFIX_DIR).sort files.each do |file| DataFix::DataFix.create(name: file) end puts_and_log "all data fixes have now been *declared* as run" end desc 'Reconcile up to: declare that data fixes up to given filename (included) have been run' task :reconcile_up_to, [:filename] => :environment do |task, args| exit_if_not_installed stop_file = args[:filename] DataFix::DataFix.destroy_all files = Dir.glob("*.rb", base: DATAFIX_DIR).sort files.each do |file| DataFix::DataFix.create(name: file) exit if file == stop_file end end desc 'Rollback: declare that the latest STEP data fixes have not been run (default 1)' task rollback: :environment do exit_if_not_installed step = ENV["STEP"] || 1 step.times do DataFix::DataFix.last.destroy end end
end