Help us understand the problem. What is going on with this article?

[Ruby on Rails] 旧システムから新システムへのデータマイグレーション

More than 1 year has passed since last update.

概要

目的

システムのリプレースを行うことになりました。旧(現行)システムのデータベーススキーマは、度重なる継ぎ接ぎの被害を受けたり、未使用のままの列があったり等の問題があり、新システムではスキーマの見直しを行い、ゼロからマイグレーションファイルを書き直すことにしました。

しかし、旧システムのデータベースの中身は新システムにリプレース後も引き続き使用するため、新システムのデータベーススキーマのフォーマットに合わせながら移行する必要があります。そこで、旧システムのデータベースと新システムのデータベースの両方に接続しつつ、旧システムのデータを加工しながら新システムのデータベースに詰めていく簡易的な rake タスクを書くことにしました。

環境

  • Ruby 2.5.1
  • Rails 5.2.1

事前準備

移行スクリプトから直接現行環境のデータベースへ接続するのは恐ろしいので、dump を取ってローカル(新システムと同じマシン)にインポートしておきます。

そして、そのデータベースへの接続設定ファイルを作成します。database.yml と同様です。

config/old_system_database.yml(例)
adapter: mysql2
encoding: utf8
database: appname_production
host: 127.0.0.1
username: root
password:
port: 3306

git 管理している場合は、 database.yml でよくやるように、 old_system_database.yml.gitignore しておいて、 old_system_database.yml.sample をリポジトリに含めておくと良いでしょう。

移行スクリプト実装

完成例

lib/tasks/migrate_from_old_system.rake
desc 'Migrate from old system'

task migrate_from_old_system: :environment do
  class OldSystemModel < ApplicationRecord
    establish_connection(YAML.safe_load(IO.read('config/old_system_database.yml')))
  end

  # そのまま移行して良いテーブルの移行例
  class OldPicture < OldSystemModel
    self.table_name = :pictures
  end
  OldPicture.find_each { |old| Picture.create!(old.attributes) }

  # テーブル名が変わるテーブルの移行例
  class OldCameraman < OldSystemModel
    self.table_name = :cameramen
  end
  OldCameraman.find_each { |old| Photographer.create!(old.attributes) }

  # 列の内容が変わるテーブルの移行例
  class OldUser < OldSystemModel
    self.table_name = :users
  end
  OldUser.find_each do |old|
    attributes = old.attributes
    attributes.delete('avatar')
    attributes['photographer_id'] = attributes.delete('cameraman_id')
    User.create!(attributes)
  end
end

解説

旧データベースと接続するモデルクラス

ApplicationRecord を継承したモデルクラスは、デフォルトで config/database.yml に記述されているデータベース、なければ ENV['DATABASE_URL'] を見に行きますが、 ActiveRecord::Base.establish_connection に接続情報を与えることで、任意のデータベースと接続するモデルクラスを作ることができます。ここで、事前準備で作成した old_system_database.yml を使います。

旧データベースと接続する全てのモデルクラスで establish_connection(YAML.safe_load(IO.read('config/old_system_database.yml'))) と書くのはしんどいので、ベースクラスを作ることにします。ここでは、旧システムのデータベースと接続するモデルクラスは OldSystemModel クラスを継承することにします。サブクラス側では、 ActiveRecord::Base.table_name を呼んで旧システムでのテーブル名を指定します。

  class OldSystemModel < ApplicationRecord
    establish_connection(YAML.safe_load(IO.read('config/old_system_database.yml')))
  end

  class OldPicture < OldSystemModel
    self.table_name = :pictures
  end

そのまま移行して良いテーブルの移行例

まずは最も簡単な、テーブル名も列の内容も変わっておらず、何も気にせずそのまま移行して良い場合のスクリプト例です。 pictures テーブルには何の変更も行われなかったので、id , created_at , updated_at も含む全ての属性をまとめて移行します。全ての属性は attributes メソッドで取得することができます。

  class OldPicture < OldSystemModel
    self.table_name = :pictures
  end
  OldPicture.find_each { |old| Picture.create!(old.attributes) }

テーブル名が変わるテーブルの移行例

「カメラマン」という呼び名をやめて「フォトグラファー」という呼び名を使いましょう、という話になったのでテーブル名も cameramen から photographers に変えることになった場合です。旧システムと接続するモデルクラスの table_name 次第なので影響は受けません。

  class OldCameraman < OldSystemModel
    self.table_name = :cameramen
  end
  OldCameraman.find_each { |old| Photographer.create!(old.attributes) }

列の内容が変わるテーブルの移行例

旧システムではユーザーのアバター画像のアップローダーに carrierwave を使っていたため、 users テーブルの avatar という列にファイル名を保存していましたが、新システムでは Rails 5.2 系の ActiveStorage が使えるため、 avatar 列は削除することにしました。

また、上記で「カメラマン」という呼び名をやめて「フォトグラファー」という呼び名を使うことに決まった関係で、 users テーブルの持つ外部キーの名前が cameraman_id から photographer_id に変わったので属性の名前を差し替えます。

このような形で、Ruby で属性の内容をゴリゴリ書き換えてやることで任意のフォーマットに移行することができます。

  # 列の内容が変わるテーブルの移行例
  class OldUser < OldSystemModel
    self.table_name = :users
  end
  OldUser.find_each do |old|
    attributes = old.attributes
    attributes.delete('avatar')
    attributes['photographer_id'] = attributes.delete('cameraman_id')
    User.create!(attributes)
  end

以上。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away