- バージョン
- UUIDとは何か
- なぜUUIDを使うのか
- 注意すべきポイント
- アプリにUUIDを適用してみる
- 拡張機能を有効化する
- デフォルトのキーをuuidにする設定を行う。
- モデルの作成
- おまけ:関連を付けたい場合
- まとめ
バージョン
Ruby 2.6.3 Rails 6.0.3 PostgreSQL 12.2
参考URL: ( Choose UUIDs for model IDs in Rails - Andy Croll)
UUIDとは何か
UUIDというのはuniversally unique identifierの略です。 128ビットの数値で16進数で表されることが多いです。
550e8400-e29b-41d4-a716-446655440000
のように表現されます。 詳細はUUID - Wikipedia を参照いただくとより理解が深まりますが、要は被らない一意の文字列であるということです。
今回はRailsを使用したアプリケーションにおけるUUIDの使用を紹介していきます。 Railsではデフォルトでidには1から始まる整数であるidがレコードの一意性を担保するカラムとして用意されており、
User.find(1)
のようにすることでidが1のユーザーを取得することができるようになっています。 今回はこの部分をUUIDにしていく方法をご紹介します。
なぜUUIDを使うのか
かっこいいからです。
Railsの標準として提供している整数値であるidを用いた場合、同じidを入れようとして何らかのエラーが起こってしまう場合があります。 また、整数値である以上人間がデータを推測することが容易になってしまい、様々なリスクが生じる可能性があります。
https://example.co.jp/users/123
例えば自分がログインした場合のユーザーページがこのようなURLになっているとします。このURLを見ることで自分が123の識別子を持つユーザーであること
が分かりますね。
また、(普通は制御をかけていますが)123の部分を別の数字に置き換えることで(本来見えてはいけないはずの)他のユーザーのページを見ることができるかも知れません。
問題なのはこういったことができるかどうか、ではなくユーザーにそのような思考を抱かせる推測の容易性にあります。 UUIDを使うことでこのような問題を回避し、
https://example.co.jp/users/550e8400-e29b-41d4-a716-446655440000
このようなurlを利用者に提供することができるようになります。 こういったURLであれば推測は非常に難しくなりますし、サービスを使用するユーザーも安心して使用できるという心理的なメリットも提供することができます。
注意すべきポイント
そんなにいいことづくめであるのならUUIDをデフォルトにすれば良いのではないか?という意見もあるかと思います。
確かにメリットもありますが、例えばRailsで使用できるActiveRecordのfirst
,last
というスコープを使用する場合は注意しなくてはいけません。
User.first
このシンプルなコードで発行されるクエリは
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1
ですが、idの値は 550e8400-e29b-41d4-a716-446655440000
このようになっています。これはつまり、新規作成したUserを
User.last
で取得できるとは限らないということです。
もし既に稼働しているプロジェクトにUUIDを採用しようとしている場合はIDによるorder
,first
, last
のような制御を行っていないかを十分に確認するようにしてください。
アプリにUUIDを適用してみる
以上を踏まえてUUIDを適用したtableを作成していきましょう。 今回はUserモデルを作成し、そのIDをUUIDで登録できるようにしていきます。 今回はPostgreSQLのUUID拡張サポートを利用します。 また、記述量を減らすためにRails5.0以降で利用できるデフォルトのキーをuuidにする設定を行います。
拡張機能を有効化する
rails g migration enable_extension_for_uuid
を実行して、EnableExtensionForUuid
用のmigrationを作成します。
中身は下記のようにしましょう。
# frozen_string_literal: true class EnableExtensionForUuid < ActiveRecord::Migration[6.0] def change enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') end end
デフォルトのキーをuuidにする設定を行う。
config/initializers/generators.rb
ファイルを作成します。中身を下記のようにしましょう。
# frozen_string_literal: true Rails.application.config.generators do |g| g.orm :active_record, primary_key_type: :uuid end
この 設定でmodel作成時にidがuuidであるというオプションがmigrationファイルに自動で付与されるようになります。なくても都度記載すれば動作するので、以降説明する記述を追加して実行しても構いません。
モデルの作成
Userモデルを作成しましょう。特別な操作をする必要はありません。何もないのは味気ないのでnameカラムを作っておきます。
$ rails g model user name:string
この時点で作成されるmigrationはこのようになっています。
class CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users, id: :uuid do |t| t.string :name t.timestamps end end end
注目するべきはcreate_table :usersの後にあるid: :uuid
です。この記述があることで、idがuuidとして設定されます。config/initializers/generators.rb
の記述によってmigrationの際に自動でuuidの設定が上記のように記載されますが、自分で都度書いていただいても大丈夫です。
migrate実行後のschemaファイルは
schema.rb
ActiveRecord::Schema.define(version: 2020_06_24_101030) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end end
このようになっています。ここで注目する部分は
id: :uuid, default: -> { "gen_random_uuid()" }
です。
この記述で一意のuuidが生成されます。
この状態でコンソールなどからUser.createしてみましょう。
#<User id: "e96939eb-7a1b-46c9-b5f6-38c79fe8a4e7", name: nil, created_at: "2020-06-24 10:26:10", updated_at: "2020-06-24 10:26:10">
こんな感じでidの部分がuuidとして生成されれば成功です!
おまけ:関連を付けたい場合
関連を作成したい場合です。先ほどUserモデルを作成したので、Userモデルに紐づくPostモデルを作成し、関連を付けましょう。
$ rails g model post title:string user:references
PostモデルはタイトルとUserとの関連を持ちます。 通常の整数のインクリメント方式の場合はこのままmigrateしてしまえば問題ないのですが、今回は関連に使用するのがuuidになるので少し修正が必要です。
# frozen_string_literal: true class CreatePosts < ActiveRecord::Migration[6.0] def change create_table :posts, id: :uuid do |t| t.string :title t.references :user, null: false, foreign_key: true t.timestamps end end end
migrationファイルの関連の記述部分に
type: :uuid
を記載しましょう。
# frozen_string_literal: true class CreatePosts < ActiveRecord::Migration[6.0] def change create_table :posts, id: :uuid do |t| t.string :title t.references :user, type: :uuid, null: false, foreign_key: true t.timestamps end end end
このような形式になります。 この状態でmigration を行いましょう。成功すればschemaファイルは下記のようになります。
ActiveRecord::Schema.define(version: 2020_06_24_104946) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" create_table "posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "title" t.uuid "user_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["user_id"], name: "index_posts_on_user_id" end create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end add_foreign_key "posts", "users" end
関連のidの部分が t.uuid
になっています。PostgreSQLのuuid型の関連はこのようにする必要があるので、UUIDを使用する場合はこの部分も忘れないようにしましょう。
まとめ
Railsが提供するデフォルトのidシステムの代わりにUUIDを利用する方法をご紹介しました。 ちょっとした注意点を考慮すればほとんどコストもかからず、サービスの利用者に安全性を提供することが可能になりますので、新規プロジェクトを進める場合は採用を検討してみてはいかがでしょうか?