[Rails]Model(ActiveRecord)のid(PK)にULIDを用いる


概要

なぜULIDを使うか?というモチベーションはこちらの記事をご覧ください。

やり方

1. Gem ulid をインストールする

Gem ulid

Gemfile

gem 'ulid'

( gem 'ulid', '~> 1.3.0' のようにバージョン指定する方が好ましいです)

これで bundle install すると、以下のように ULID が生成できるようになります。

> ULID.generate
=> "01FZR8MM88EG6Q35H854F4F5B3"

2. ULID を PK として扱いたいテーブルのマイグレーションファイルを作成する

通常のマイグレーションファイルの作成・記述・実行方法は割愛します。

マイグレーションファイルでは String の PK にして長さ 26 文字※1とします。

db/migrate/yyyymmddhhmmss_create_user.rb

class CreateUser < ActiveRecord::Migration[6.1]
  def change
    create_table :users, id: false do |t|
      t.string :id, null: false, limit: 26, primary_key: true, comment: 'ID'
    end
  end
end

※1. ULID の長さは26文字です

3. ULID を PK として扱う Concern を作成する

複数のモデルクラスで同様のコードを書くことになるので、 Concern で ULID に関する処理をまとめておきます。
Concern ではなく、 ApplicationRecord にまとめておくこともできますが、他のIDを用いたいとなった場合※2に面倒なので、 Concern にしておきます。

app/models/concerns/ulid_pk.rb

module UlidPk
  extend ActiveSupport::Concern

  included do
    after_initialize :set_id
  end

  def set_id
    id = ULID.generate
  end
end

after_initialize で id をセットするように書いておくと、 create の際に他のカラムで id の値を扱えるので便利です。

※2. 例えば、Stripeなど外部のSaaSのオブジェクトのIDをDBで格納しておく場合などにidカラムの値をSaaS側のオブジェクトのidと揃えたいといったケースが考えられます。

3. id は readonly にしておく

id は readonly にしておきます。

app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  attr_readonly :id
end

上書きすることがないので、上書きできないようにしておきます。
各モデルで定義するのは手間なので、定義する場所は ApplicationRecord で良いと思います。

4. Concern を include する

app/models/user.rb

class User < ApplicationRecord
  include UlidPk
  attribute :id, :string
  validates :id, length: { is: 6 }, presence: true, uniqueness: true
end

id カラムのバリデーションはできれば半角英数字でULIDに用いられる可能性のある文字列のみで判定したいですし、 ActiveModel::EachValidator で ULID かどうか判定するバリデーションを定義するのがスマートですが、この記事ではそれは割愛します。


以上でModel(ActiveRecord)のid(PK)にULIDを用いることができます。

.find() や .order(:id) も AUTO INCREMENT のときと使用感変わらず使えます。