Skip to content

Instantly share code, notes, and snippets.

@drewB
Created December 11, 2019 01:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drewB/2bcb5448828b2ce95021f63b4dccd5d5 to your computer and use it in GitHub Desktop.
Save drewB/2bcb5448828b2ce95021f63b4dccd5d5 to your computer and use it in GitHub Desktop.
Migration for upgrading existing delayed_job records when moving from Rails 4 to Rails 5.
require 'ruby-progressbar'
class MigrateDjToRails5 < ActiveRecord::Migration[5.2]
include MigrationHelper
def up
execute create_backup('delayed_jobs')
migrator = DelayedJobMigrator.new(Delayed::Job.all)
migrator.migrate
end
def down
execute copy_field_from_backup('delayed_jobs', 'handler')
execute load_dropped_records_from_backup('delayed_jobs')
end
class DelayedJobMigrator
def initialize(jobs = Delayed::Job.all)
@exceptions = []
@corrected_job_ids = []
@deleted_job_ids = []
@jobs = jobs
@progress_bar = ProgressBar.create(total: jobs.count, format: "%t: |%w| %e")
end
def migrate_job(job)
begin
data = YAML.load_dj(job.handler)
rescue
begin
job.payload_object = TempToRuby.create.accept(Psych.parse(job.handler))
rescue => exception
if exception.message =~ /Couldn't find (.+) with 'id'=/
# job is no longer valid
@deleted_job_ids << job.id
job.delete
end
@exceptions << exception
return
end
job.save
@corrected_job_ids << job.id
end
end
def migrate
@jobs.find_each do |job|
migrate_job(job)
@progress_bar.increment
end
@corrected_job_ids.each do |id|
puts "Corrected job_id: #{id}"
end
@exceptions.each do |exception|
puts "Exceptions:: #{exception}"
end
puts "#{@corrected_job_ids.count} jobs corrected"
puts "#{@exceptions.count} exceptions encountered (should be same as jobs deleted)"
puts "#{@deleted_job_ids.count} jobs deleted"
end
class TempToRuby < Delayed::PsychExt::ToRuby
def visit_Psych_Nodes_Mapping(object)
if %r{^!ruby/object:ActiveRecord::AttributeSet}.match(object.tag.to_s)
{}
elsif %r{^!ruby/object:(.+)$}.match(object.tag.to_s)
klass = resolve_class(Regexp.last_match[1])
if klass < ActiveRecord::Base
payload = Hash[*object.children.map { |c| accept c }]
return super unless payload['raw_attributes']
id = payload['raw_attributes'][klass.primary_key]
klass.unscoped.find(id)
else
super
end
else
super
end
end
end
end
end
# Helper module for commonly used migration related methods
module MigrationHelper
def create_backup(table_name, suffix='_copy')
table_name_copy = table_name + suffix
<<-SQL
DROP TABLE IF EXISTS #{table_name_copy};
CREATE TABLE #{table_name_copy} AS TABLE #{table_name};
SQL
end
def copy_field_from_backup(table_name, field, suffix='_copy')
table_name_copy = table_name + suffix
records_to_update = <<-SQL
UPDATE #{table_name}
SET #{field} = #{table_name_copy}.#{field}
FROM #{table_name_copy}
WHERE #{table_name_copy}.id = #{table_name}.id
SQL
end
def load_dropped_records_from_backup(table_name, suffix='_copy')
table_name_copy = table_name + suffix
<<-SQL
INSERT INTO #{table_name}
SELECT #{table_name_copy}.* FROM #{table_name_copy}
LEFT JOIN #{table_name} on #{table_name}.id = #{table_name_copy}.id
where #{table_name}.id is null
SQL
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment