Railsアプリへのuuidの適用方法

Railsアプリへのuuid適用方法などは、弊社の技術記事[Rails] モデルのIDにUUIDを使って玄人感を出すで既に書かれているので省略します。
今回は既存テーブルのidを変更するため、どのようにマイグレーションするか?がメインです。
uuidをrailsアプリに適用するコードだけ記します。

postgresqlのuuid拡張機能を有効化

rails g migration enable_extension_for_uuid

initializers内でprimary keyのデフォルト値をuuid化

Rails.application.config.generators do |g|
  g.orm :active_record, primary_key_type: :uuid
end

Railsアプリへの詳しいuuid適用方法は下記を参照してください! [Rails] モデルのIDにUUIDを使って玄人感を出す

バージョン

  • Ruby 2.7.0
  • Rails 6.0.3.4
  • PostgreSQL 13.0

スキーマ

下記構成で記述します

  • テーブル名

    • アソシエーション
  • users

    • has_many event_participants
  • events

    • has_many event_participants
  • event_participants

    • belongs_to users
    • belongs_to events

users

id name
1 hoge
2 huga

events

id name
1 hoge
2 huga

event_participants

id event_id user_id
1 1 1
2 2 1
3 2 2

上の構成ですべてですが、ざっくり日本語で書くと、ユーザーがイベントに参加できて、イベントの参加者を多対多の中間テーブルで管理する。みたいな感じです。

マイグレーション

まずusersテーブルからマイグレーションしていきます。

class ChangeUserIdTypeToUuid < ActiveRecord::Migration[6.0]
  def up
    # usersテーブルにuuid追加
    add_column :users, :uuid, :uuid, null: false, default: 'gen_random_uuid()'

    # 関連テーブルにuuid追加
    add_column :event_participants, :user_uuid, :uuid

    # 関連テーブルのuuidを更新(これでusersのuuidとevent_participantsのuser_uuidが紐づく)
    execute <<~SQL
      UPDATE event_participants SET user_uuid = users.uuid
      FROM users WHERE event_participants.user_id = users.id;
    SQL

    # usersの元々のprimary keyであるidを消す
    # 外部キーとして参照されていると消せないので、関連テーブルからの参照を切る
    remove_foreign_key :event_participants, :users
    remove_reference :event_participants, :user, index: true

    # usersのidを消して、追加したuuidのカラム名をidに変更
    change_table :users do |t|
      t.remove :id
      t.rename :uuid, :id
    end

    # usersのuuid化したidをprimary keyとして設定
    execute 'ALTER TABLE users ADD PRIMARY KEY (id);'

    # 関連テーブルに追加したuuidを元々あった名前に変更(元々あったuser_idはusersテーブルのidを消した時に消えてるはずなので競合しない)
    rename_column :event_participants, :user_uuid, :user_id

    # 関連テーブルのuuid化したuser_idを外部キーとして設定
    add_foreign_key :event_participants, :users
    add_index :event_participants, :user_id
    change_column_null :event_participants, :user_id, false

  end

  def down
    # idに戻すことは無いと思うのでroleback不可を明示的にする
    raise ActiveRecord::IrreversibleMigration
  end
end

これでusersのidと関連テーブルであるevent_participansのuser_idがuuidに変更できたと思います。

eventsテーブルも同じ要領でできますね。

最後にevent_participantsのidを変更していきます。

class ChangeEventParticipantsIdTypeToUuid < ActiveRecord::Migration[6.0]
  def up
    # event_partivipantsテーブルにuuid追加
    add_column :event_participants, :uuid, :uuid, null: false, default: 'gen_random_uuid()'

    # event_participantsのidを消して、追加したuuidのカラム名をidに変更
    change_table :event_participants do |t|
      t.remove :id
      t.rename :uuid, :id
    end

    # event_participantsのuuid化したidをprimary keyとして設定
    execute 'ALTER TABLE event_participants ADD PRIMARY KEY (id);'

  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end

外部キーとして参照されてないidをuuidに変更するのはシンプルですね!

まとめ

やはり作成済みのテーブルのidを変更するにはひと手間かかりますね。 常にユーザーがアクセスしている本番環境で途中から変更するのは中々現実的ではないとは思いますが、 ローンチ前かつデータがちょっと入ってて消したくない(消えてもそんな問題じゃない)。みたいな時には役立つかなと思いました。

やっぱidよりuuidの方がかっちょいい!