SQLite on Railsシリーズ(10)ULIDを主キーにする(翻訳)
Ruby on RailsのデータベースにSQLiteを使っている場合に、UUIDやULIDなどを主キーとして利用する方法を知りたいと思うかもしれません。
Twitterで以下の質問をいただいたのですが、これに回答するには記事1本分かかります。
Any resources about using UUIDs (or ULIDs) as primary keys in Rails?
I’ve gotten used to that with PostgreSQL but it seems a little wonky with SQLite3.
Enjoying your series on SQLite! Thanks.
— Clayton (@claytonlz) September 14, 2023
RailsでUUID(あるいはULID)を主キーとして利用する方法に関するリソースはどこかにありますか?PostgreSQLでは慣れているのですが、SQLite3では少し不安定なようです。
@claytonlz
🔗 Railsでカスタム主キー型を利用する方法
最初に、Railsでカスタム主キー型を利用するよう設定する方法を見ていきましょう。PostgreSQLのネイティブUUIDデータ型については、Paweł Urbanekが公開している良記事で、テーブルの主キーとして利用するする手順が解説されています。記事を最後まで読むことをおすすめしますが、ここでは以下の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として利用できます。
拡張機能の読み込みに関する過去記事で詳しく解説したように、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
ステートメントをサポートしたことで実現した機能を本記事で楽しんでいただければ幸いです。
概要
原著者の許諾を得て翻訳・公開いたします。
参考: Rails 8はSQLiteで大幅に強化された「個人が扱えるフレームワーク」(翻訳)|YassLab 株式会社
日本語タイトルは内容に即したものにしました。また、適宜見出しを追加しています。