devise_token_authを利用したシステムへのユーザーデータの移行手順について

更新日2022-01-15
投稿日2021-10-12
タグfootlog, devise_token_auth
投稿者 @amount86

こんばんは。

footlogを開発している@amount86です。



railsでdevise_token_authを利用して実装しているシステムへのユーザーデータの移行手順について、少しハマりましたので記事にまとめます。


この記事は正攻法ではないと思いますが、色々自分で調べた際にこういった記事がヒットしなかったので、こういう方法もあるんだ程度で見ていただければと思います。



1. 前提

1-1. 移行元のシステム

ビューも含めてruby on railsで実装していて、DBMSはPostgreSQLです。

railsでのログイン機能の実装には、deviseを利用することが多いと思いますが、勉強のためにdeviseは使わずに、自分達で実装しています。
ログイン機能に利用しているGemは、bcryptというパスワードをハッシュ化して保存するためのもののみです。

ユーザー情報が格納されているテーブルは、以下の構造になっています(一部省略)。

  create_table "users", force: :cascade do |t|
    t.string "name"            # ユーザー名
    t.string "mail"            # メールアドレス
    t.string "password_digest" # bcryptでハッシュ化したパスワード
    t.string "biography"       # 自己紹介文
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end


1-2. 移行先のシステム

バックエンドはruby on railsのAPIモード、フロントエンドはReact.jsです(今回の記事にフロントエンドは無関係です)。
また、移行元同様、利用しているDBMSはPostgreSQLです。

ログイン機能の実装にはdevise_token_authを利用します。

ユーザー情報が格納されているテーブルは、以下の構造になっています。

  create_table "users", force: :cascade do |t|

    # devise_token_authが作成するカラム
    t.string "provider", default: "email", null: false
    t.string "uid", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.boolean "allow_password_change", default: false
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.string "confirmation_token"
    t.datetime "confirmed_at"
    t.datetime "confirmation_sent_at"
    t.string "unconfirmed_email"

    # 自分たちが定義したカラム
    t.string "name"
    t.string "image"
    t.string "header_image"
    t.string "email", null: false
    t.text "biography", default: "", null: false
    t.json "tokens"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false

    # インデックス
    t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
    t.index ["uid", "provider"], name: "index_users_on_uid_and_provider", unique: true
  end



2. まず試したこと

移行元からusersテーブルの情報をCSVに出力して、移行先にrubyのスクリプトでユーザー作成できないかという方法を考えました。


devise_token_authでは、パスワードの暗号化にbcryptを使っているので、rubyのスクリプトから流せれば一番簡単だと思ったからです。


なので、まず、移行元のusersテーブルの情報を抽出して、移行先のrailsのコンソールから、

User.create(name: '{ユーザー名}', email: '{メールアドレス}', encrypted_password: '{パスワード(bcryptで暗号化済み)}', biography: '{自己紹介文}')

を試しました。


が、ユーザー作成されませんでした。


ここについては、devise_token_authの提供しているAPIを利用しないと、ユーザーが作成されないと理解しました(間違っていたらどなたか教えて下さい)。


3. 解決策

rails側では、devise_token_authの提供しているAPIを叩かないと作れないのであれば、PostgreSQLに直接データを流せばいいのでは?と考えました(※)。


ポスグレにログイン後、INSERT文を実行して、移行ユーザーアカウントでログインしてみところ、無事ログインすることができました。

psql {データベース名}

INSERT INTO users (provider,uid,encrypted_password,allow_password_change,sign_in_count,confirmed_at,name,email,biography,created_at,updated_at) VALUES ('email','{メールアドレス}','{bcryptで暗号化したパスワード}',FALSE,0,current_timestamp,'{ユーザー名}','{メールアドレス}','{自己紹介文}',current_timestamp,current_timestamp);

気をつけないといけないのは、encrypted_passwordに、bcryptでハッシュ化したパスワードを指定しないといけないことくらいです。


あとは、以下のコマンドでダンプファイルを作成し、本番環境のDBにリストアすれば完成です。

pg_dump -Fc {データベース名} > {バックアップファイル名}

自分の場合、デプロイ先にHerokuを使っているので、作成したダンプファイルをAWS S3にアップロードして、以下のコマンドを打つことでリストアすることができます。

heroku pg:backups:restore {AWS S3で発行した署名済みURL} DATABASE_URL

詳細は、公式のHeroku Postgres データベースのインポートとエクスポートを確認してください。


以上です。



※ データ移行にdevise_token_authを提供しているAPIを利用することは、以下の理由から避けました。

  • bcryptで暗号化済みのハッシュを直接登録する方法が不明

調べれば解決可能なのかもしれませんが、データ移行よりも機能の実装に時間を使いたかったので、このような力技の方法を選択しました。




【2022-01-15 追記】

ActiveRecord::Base.connection.executeを使うことで、わざわざポスグレにログインしてSQLを実行せずともrubyで完結できることに気づきました。


ActiveRecord::Base.connection.executeは、生のSQLを実行できるメソッドです。


生のSQL記載したテキストファイル(migration_sql.txt)を準備した上で、seedファイルに以下のように記載します。

text_sql = "db/seed/migration_sql.txt"

f = open(text_sql)
f.each do |sql|
  ActiveRecord::Base.connection.execute(sql.sub(/\n/, ""))
end
f.close

migration_sql.txtには、以下のようにSQL文を記載します。

INSERT INTO users (provider,uid,encrypted_password,allow_password_change,sign_in_count,confirmed_at,name,email,biography,created_at,updated_at) VALUES ('email','{メールアドレス1}','{bcryptで暗号化したパスワード1}',FALSE,0,current_timestamp,'{ユーザー名1}','{メールアドレス1}','{自己紹介文1}',current_timestamp,current_timestamp);
INSERT INTO users (provider,uid,encrypted_password,allow_password_change,sign_in_count,confirmed_at,name,email,biography,created_at,updated_at) VALUES ('email','{メールアドレス2}','{bcryptで暗号化したパスワード2}',FALSE,0,current_timestamp,'{ユーザー名2}','{メールアドレス2}','{自己紹介文2}',current_timestamp,current_timestamp);

本来であれば、直接SQL文を記載するのではなく、CSV等に値だけを記載し、SQL文に埋め込んでいくべきかと思いますが。

すでにSQLを記載したファイルを準備していましたし、一度しか使わないスクリプトのため、横着しました。