The project is in a healthy, maintained state
Dekiru::DataMigration provides features for data migration tasks including progress display, transaction management, execution confirmation, side effect monitoring, and detailed logging.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

 Project Readme

Dekiru::DataMigration

A Ruby on Rails library for executing data migration tasks safely and efficiently.

Overview

Dekiru::DataMigration provides the following features for data migration tasks:

  • Progress Display: Real-time progress visualization during processing
  • Transaction Management: Automatic transaction control to ensure data safety
  • Execution Confirmation: Confirmation prompts before committing changes
  • Side Effect Monitoring: Tracking of database queries, job enqueuing, and email sending
  • Logging: Detailed execution logging

Installation

Add this line to your application's Gemfile:

gem 'dekiru-data_migration'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install dekiru-data_migration

Data Migration Operator

You can implement the necessary processing for data migration tasks with scripts like the following:

# scripts/demo.rb
Dekiru::DataMigration::Operator.execute('Grant admin privileges to users') do
  targets = User.where("email LIKE '%sonicgarden%'")

  log "Target user count: #{targets.count}"
  find_each_with_progress(targets) do |user|
    user.update!(admin: true)
  end

  log "Updated user count: #{User.where("email LIKE '%sonicgarden%'").where(admin: true).count}"
end

Data Migration Class (Recommended)

You can also define migration logic as a class, which makes testing easier:

# scripts/20230118_demo_migration.rb
class DemoMigration < Dekiru::DataMigration::Migration
  def migration_targets
    User.where("email LIKE '%sonicgarden%'").where(admin: false)
  end

  def migrate_record(user)
    user.update!(admin: true)
  end

  def migrate
    super
    log "Updated user count: #{User.where(admin: true).count}"
  end
end

DemoMigration.run

Testing Migration Classes

The class-based approach makes it easy to write unit tests:

# spec/migrations/demo_migration_spec.rb
RSpec.describe DemoMigration do
  let(:migration) { described_class.new }

  describe '#migration_targets' do
    it 'returns correct migration targets' do
      create_list(:user, 3, email: 'test@sonicgarden.jp', admin: false)
      create_list(:user, 2, email: 'other@example.com', admin: false)

      targets = migration.migration_targets
      expect(targets.count).to eq(3)
      expect(targets.all? { |u| u.email.include?('sonicgarden') }).to be true
    end
  end

  describe '#migrate_record' do
    it 'updates user to admin' do
      user = create(:user, admin: false)
      expect { migration.migrate_record(user) }
        .to change { user.reload.admin }.from(false).to(true)
    end
  end
end

Execution result:

$ bin/rails r scripts/demo.rb
Start: Grant admin privileges to users at 2019-05-24 18:29:57 +0900

Target user count: 30
Time: 00:00:00 |=================>>| 100% Progress
Updated user count: 30

Are you sure to commit? (yes/no) > yes

Finished successfully: Grant admin privileges to users
Total time: 6.35 sec

Side Effect Monitoring

By executing with the warning_side_effects: true option, side effects that occur during data migration tasks (database writes, job enqueuing, email sending, etc.) will be displayed.

Dekiru::DataMigration::Operator.execute('Grant admin privileges to users', warning_side_effects: true) do
  # Processing content...
end

Execution result:

$ bin/rails r scripts/demo.rb
Start: Grant admin privileges to users at 2019-05-24 18:29:57 +0900

Target user count: 30
Time: 00:00:00 |=================>>| 100% Progress
Updated user count: 30

Write Queries!!
30 call: Update "users" SET ...

Enqueued Jobs!!
10 call: NotifyJob

Delivered Mailers!!
10 call: UserMailer

Are you sure to commit? (yes/no) > yes

Generating Maintenance Scripts

You can generate maintenance scripts that use Dekiru::DataMigration::Migration with the generator. The filename will be prefixed with the execution date.

$ bin/rails g maintenance_script demo_migration

Generated file example:

# scripts/20230118_demo_migration.rb
# frozen_string_literal: true

class DemoMigration < Dekiru::DataMigration::Migration
  def migration_targets
    # 移行対象を返すActiveRecord::Relationを定義
    # 例: User.where(some_condition: true)
    raise NotImplementedError, 'migration_targets method must be implemented'
  end

  def migrate_record(record)
    # 個別レコードの更新処理を定義
    # 例: record.update!(some_attribute: new_value)
    raise NotImplementedError, 'migrate_record method must be implemented'
  end
end

DemoMigration.run

Legacy Block-based Approach

For backward compatibility, you can still use the block-based approach:

# scripts/legacy_demo.rb
Dekiru::DataMigration::Operator.execute('demo_migration') do
  # write here
end

Output Directory Configuration

The output directory for files is by default the scripts directory directly under the application root. You can change the output directory through configuration.

# config/initializers/dekiru.rb
Dekiru::DataMigration.configure do |config|
  config.maintenance_script_directory = 'scripts/maintenance'
end

Custom Transaction Management

For scripts using Dekiru::DataMigration::Operator, there are cases where the default ActiveRecord::Base.transaction transaction handling is insufficient, such as when writing to multiple databases is required.

You can modify the transaction handling behavior of Dekiru::DataMigration::Operator by customizing Dekiru::DataMigration::TransactionProvider.

Implementation Example

Here's an example configuration for applications using multiple databases.

Application-side Configuration

# app/models/legacy_record.rb
class LegacyRecord < ApplicationRecord
  connects_to database: { writing: :legacy, reading: :legacy }
end

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  connects_to database: { writing: :primary, reading: :primary }

  def self.with_legacy_transaction
    ActiveRecord::Base.transaction do
      LegacyRecord.transaction do
        yield
      end
    end
  end
end

Custom TransactionProvider Configuration

To configure Dekiru::DataMigration::Operator to also use ApplicationRecord.with_legacy_transaction for transaction handling, set up the following configuration:

# config/initializers/dekiru.rb
class MyTransactionProvider < Dekiru::DataMigration::TransactionProvider
  def within_transaction(&)
    ApplicationRecord.with_legacy_transaction(&)
  end
end

Dekiru::DataMigration.configure do |config|
  config.transaction_provider = MyTransactionProvider.new
end

Available Configuration Options

Basic Configuration

# config/initializers/dekiru.rb
Dekiru::DataMigration.configure do |config|
  # Output directory for maintenance scripts (default: "scripts")
  config.maintenance_script_directory = 'scripts/maintenance'

  # Custom transaction provider (default: Dekiru::DataMigration::TransactionProvider.new)
  config.transaction_provider = MyTransactionProvider.new
end

Runtime Options

Dekiru::DataMigration::Operator.execute('Title', options) do
  # Processing content
end

Available options:

  • warning_side_effects: Display side effects (default: true)
  • without_transaction: Don't use transactions (default: false)
  • logger: Custom logger (default: auto-generated)
  • output: Output destination (default: $stdout)

Key Methods

log(message)

Outputs log messages. Properly handled even during progress bar display.

find_each_with_progress(scope, options = {}, &block)

Executes find_each with a progress bar for ActiveRecord scopes.

each_with_progress(enum, options = {}, &block)

Executes processing with a progress bar for any Enumerable objects.