Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

SQLite on Railsシリーズ(10)ULIDを主キーにする(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

参考: Rails 8はSQLiteで大幅に強化された「個人が扱えるフレームワーク」(翻訳)|YassLab 株式会社

日本語タイトルは内容に即したものにしました。また、適宜見出しを追加しています。

SQLite on Railsシリーズ(10)ULIDを主キーにする(翻訳)

Ruby on RailsのデータベースにSQLiteを使っている場合に、UUIDULIDなどを主キーとして利用する方法を知りたいと思うかもしれません。

ulid/spec - GitHub


Twitterで以下の質問をいただいたのですが、これに回答するには記事1本分かかります。

RailsでUUID(あるいはULID)を主キーとして利用する方法に関するリソースはどこかにありますか?PostgreSQLでは慣れているのですが、SQLite3では少し不安定なようです。
@claytonlz

🔗 Railsでカスタム主キー型を利用する方法

最初に、Railsでカスタム主キー型を利用するよう設定する方法を見ていきましょう。PostgreSQLのネイティブUUIDデータ型については、Paweł Urbanekが公開している良記事で、テーブルの主キーとして利用するする手順が解説されています。記事を最後まで読むことをおすすめしますが、ここでは以下の2つの重要な詳細について説明します。

  1. マイグレーションで主キー型を設定する方法
  2. ジェネレーターの主キーとしてUUIDを使うようRailsを設定する方法

マイグレーションについては、以下のようにcreate_tableメソッドにオプションとしてid: :uuidを渡すだけで完了します。

create_table :comments, id: :uuid  do |t|
  t.string :content
  t.uuid :user_id
  t.timestamps
end

なお、Railsで生成されるすべてのマイグレーションでこのオプションを自動的に設定するには、以下を設定します。

# config/initializers/generators.rb
Rails.application.config.generators do |g|
  g.orm :active_record, primary_key_type: :uuid
end

この根本的な変更は、カスタム主キーを有効にするためにRailsアプリケーションに加える必要があります。しかし、これをSQLiteではどのように実行できるでしょうか?

🔗 SQLiteでもカスタム主キーを使えるようになった

正直に申し上げると、今日までは、SQLiteを使っているRailsではカスタム主キーは利用不可能でした。
私は、この機能をサポートするべく、SQLiteのRETURNINGで非idカラムのサポートを追加するプルリクをオープンしました(#49290)。そして全SQLite愛好家にとって幸運なことに、マージされました!つまり、アプリケーションでRailsのmainブランチを使っていれば、今日にでもカスタム主キーにアクセスできます。それでは、その方法を詳しく見ていきましょう。

個人的にはUUIDよりもULIDの方が好みです。ULIDの方が短く、選択やコピーも簡単で、並べ替えも可能です。また、SQLiteの世界では、Alex Garciaのsqlite-ulid拡張機能がRuby gemとして利用できます。

asg017/sqlite-ulid - GitHub

拡張機能の読み込みに関する過去記事で詳しく解説したように、Railsのデータベースアダプターを拡張して拡張機能の読み込みをサポートできます。sqlite-ulid拡張機能が読み込まれると、ulid()関数とulid_bytes()関数がSQLiteデータベースに追加されます。これで、これらの関数の1つをカスタム主キーで利用可能になります。

SQLiteアダプタはulidデータ型をサポートしていないため、カスタム主キーを少し異なる方法で定義する必要があります。具体的には、create_tableコマンドにid: :ulidオプションを渡す代わりに、id: falseを渡す必要があります。
これで、Railsのデフォルトの主キーメカニズムがデータベースエンジンで無効になります。

次に、t.primary_keyマクロを定義して、カスタム主キーの詳細を設定できます。この場合、以下のようにULIDでカスタム主キーを定義できます。

create_table :posts, force: true, id: false do |t|
  t.primary_key :id, :string, default: -> { "ULID()" }
end

このマイグレーションを実行すると、ULID主キーを持つテーブルが作成されます。

CREATE TABLE "posts" (
  "id" varchar DEFAULT (ULID()) NOT NULL PRIMARY KEY
)

これで、Post.create!を呼び出せば、#<Post id: "01hayj8d41d5e4hx0fdfbvja76">のようなモデルインスタンスが返されるようになります。

残念ながら、カスタムulidデータ型がないため、Railsジェネレータでこのようなマイグレーションを自動作成する設定は行えませんが、この程度の手動作業で済むなら悪くありません。

🔗 マイグレーションを変更する

カスタム主キーを作成したら、外部キーが適切に一致するようにしておく必要があります。外部キーがカスタム主キーに正しくバインドされるには、以下のようにマイグレーションに少し手を加える必要があります。

create_table :comments, force: true, id: false do |t|
  t.primary_key :id, :string, default: -> { "ULID()" }
  t.belongs_to :post, null: false, foreign_key: true, type: :string
end

主キーがどんなデータ型であっても、外部キーにその型を設定しておく必要があります。やっておかなければならない作業はこれだけです。

belongs_toまたはreferencesメソッドを使うと、残りの外部キーがカスタム主キーで自動的に設定されます。上記のようなcreate_tableによって以下のSQLが生成されます。

CREATE TABLE "comments" (
  "id" varchar DEFAULT (ULID()) NOT NULL PRIMARY KEY,
  "post_id" varchar NOT NULL, CONSTRAINT "fk_rails_2fd19c0db7"
  FOREIGN KEY ("post_id") REFERENCES "posts" ("id")
)

これで完了です。SQLiteをデータベースエンジンとして使っているRailsアプリケーションで、カスタムの主キーと外部キーを設定するために必要な作業は以上ですべてです。

今後、この設定をさらにシンプルに行えるよう、カスタムulidデータ型を登録する方法を調査する予定です。今後の記事にご注目ください。

それまでの間、SQLiteアダプタがRETURNINGステートメントをサポートしたことで実現した機能を本記事で楽しんでいただければ幸いです。

関連記事

SQLite on Railsシリーズ(01)Gitブランチごとにデータベースを切り替える(翻訳)

SQLite on Railsシリーズ(02)SQLiteをチューニングで強化する(翻訳)

SQLite on Railsシリーズ(03)SQLite拡張機能を読み込む(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。