こんばんは。
footlogを開発している@amount86です。
railsでdevise_token_authを利用して実装しているシステムへのユーザーデータの移行手順について、少しハマりましたので記事にまとめます。
この記事は正攻法ではないと思いますが、色々自分で調べた際にこういった記事がヒットしなかったので、こういう方法もあるんだ程度で見ていただければと思います。
ビューも含めて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
バックエンドは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
移行元からusersテーブルの情報をCSVに出力して、移行先にrubyのスクリプトでユーザー作成できないかという方法を考えました。
devise_token_authでは、パスワードの暗号化にbcryptを使っているので、rubyのスクリプトから流せれば一番簡単だと思ったからです。
なので、まず、移行元のusersテーブルの情報を抽出して、移行先のrailsのコンソールから、
User.create(name: '{ユーザー名}', email: '{メールアドレス}', encrypted_password: '{パスワード(bcryptで暗号化済み)}', biography: '{自己紹介文}')
を試しました。
が、ユーザー作成されませんでした。
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を利用することは、以下の理由から避けました。
調べれば解決可能なのかもしれませんが、データ移行よりも機能の実装に時間を使いたかったので、このような力技の方法を選択しました。
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を記載したファイルを準備していましたし、一度しか使わないスクリプトのため、横着しました。