今回やること
これまでプライマリキーは特にいじらなかったので、idは整数型のオートインクリメント(1, 2, 3 ...)になっています。
これから作る予定のサイトはユーザー投稿型のサイトにしたいので、idをuuidにして/scores/1
のようなURLではなく/scores/7a670050-7d74-473c-b322-0322b622b232
のようなURLでコード譜それぞれにアクセスできるようにします。
こうすることによるメリットは、
- URLが推定できてしまうのを防ぐ
- 全体のレコード数が把握できてしまうのを防ぐ
などがあると思います。
特にサービス立ち上げ当初は「ひとけ」がないのがバレやすいのもあるかなと。
トランザクションがめちゃくちゃ増えそうなサービス内容でもないですし、PostgreSQLであればuuidを採用してもパフォーマンスがめちゃくちゃ落ちるということもなさそうなので、今回は全面的にuuidを採用します。(mysqlだと昇順のuuidを設定しないとかなりパフォーマンスが落ちるらしい)
DBをMySQLからPostgresQLに変更
MySQLでもuuidは使用できるらしいのですが、なんだかめんどくさそうなので簡単に実装できそうなPostgreSQLに乗り換えることにしました。(開発始めたばっかりなので今なら乗り換えも簡単)
今まで特にDBMSの違いについて意識することはなかったんですが、こういう違いもあるんですね…
まずdockerのvolumeを削除します。
$ docker volume ls
...
local mysql-data
$ docker volume rm mysql-data
docker-compose.yml
、database.yml
を編集
...
db:
image: postgres:9.6
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
volumes:
pg_data:
...
default: &default
adapter: postgresql
encoding: unicode
host: db
username: postgres
password: password
pool: 5
buildし直します
$ docker-compose build
Postgresがインストールされていないのでします(ついでにMysql使わないので削除)
gem 'pg'
インストールとDB作成
$ docker-compose run --rm web bundle install
$ docker-compose run --rm web bundle exec rails db:create
これでDBがPostgreSQLに変更できたと思います!
uuidを使用できるようにする
uuidを使用するための拡張機能を有効化します
$ docker-compose run --rm web bundle exec rails g migration enable_extension_for_uuid
以下のようなmigrationファイルが作成されます
class EnableExtensionForUuid < ActiveRecord::Migration[7.0]
def change
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
end
end
これをmigrateすればuuidは使用できるようになるのですが、前々回作成したScore
テーブルのmigrationファイルも一緒に編集してしまいます。
class CreateScores < ActiveRecord::Migration[7.0]
def change
create_table :scores, id: :uuid do |t|
t.string :title
t.timestamps
end
end
end
id: :uuid
という部分でプライマリキーをuuidにするのを指定しています。
ではmigration。
$ docker-compose run --rm web bundle exec rails db:migrate
エラーが出ました。
create_scores
の方のマイグレーションファイルに追記
class CreateScores < ActiveRecord::Migration[7.0]
def change
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
create_table :scores, id: :uuid do |t|
t.string :title
t.timestamps
end
end
end
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
をこちらにも記載。
再度migrationしたところ通りました!
consoleでデータを作成してみます。
irb(main):002:0> Score.create(title: "title")
TRANSACTION (0.4ms) BEGIN
Score Create (6.2ms) INSERT INTO "scores" ("title", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["title", "title"], ["created_at", "2022-09-19 12:46:38.505748"], ["updated_at", "2022-09-19 12:46:38.505748"]]
TRANSACTION (1.6ms) COMMIT
=>
#<Score:0x0000aaaaf824fe48
id: "7a670050-7d74-473c-b322-0322b622b232",
title: "title",
created_at: Mon, 19 Sep 2022 12:46:38.505748000 UTC +00:00,
updated_at: Mon, 19 Sep 2022 12:46:38.505748000 UTC +00:00>
ちゃんとidがid: "7a670050-7d74-473c-b322-0322b622b232"
とuuidになっていますね!
その他便利そうな設定
プライマリキーを自動的にuuidにする
マイグレーションファイルを作成すると自動的にプライマリキーがuuidになるように設定します。
config/initializers/generators.rb
ファイルを作成。以下のように編集します。
# frozen_string_literal: true
Rails.application.config.generators do |g|
g.orm :active_record, primary_key_type: :uuid
end
これで新たにマイグレーションファイルをコマンドで作るとid: :uuid
が自動的に挿入されます。
first、 lastをいつも通り使えるようにする
ActiveRecordのメソッドにfirst
、last
がありますが、uuidを使用すると問題が起こります。
first
はidを昇順に並べ、最初のレコードを取ってくるのでランダムに生成されたuuidだとうまく最初のレコードをとってこれません。
Score.order(created_at: :asc).first
というふうに書けば同じことはできるのですが、いちいち書くのはめんどくさいので、application_record.rb
を以下のように編集。
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
self.implicit_order_column = "created_at"
end
追記したのはself.implicit_order_column = "created_at"
の部分。これでuuidを使う以前と同じようにfirst
、last
のメソッドが使えます。
記事とか見るとapplication_record.rb
ではなく個別のモデルにself.implicit_order_column = "created_at"
を書いてるのだけど、モデルごとにメソッドの意味が変わると混乱しそうだしここに書いていいんじゃないかなぁと判断。
今日はここまで!
参考にさせていただいたサイト・記事
- RailsでPostgresqlを使用する
- [Rails] モデルのIDにUUIDを使って玄人感を出す
- Postgres と MySQL における id, created_at, updated_at に関するベストプラクティス
-
MySQLとPostgreSQLと主キー
- auto incrementとuuid、 MySQLとPostgreSQLを実測値で比べていて納得感があった。
- Active Record’s first and last may not mean what you think
- Rails 6: UUIDでfirstやlastを使う(翻訳)