Help us understand the problem. What is going on with this article?

Rails5.2.3+MySQL+Ridgepoleでテーブルの主キーにUUIDを使う

More than 1 year has passed since last update.

概要

主キーにUUIDを使用したいという場面は前からよくあるようで、ここではその是非には触れず、今回自分が採用した方法について残しておきます。

# ローカルでの実行
> ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

> mysql -uroot
# 〜省略〜
Server version: 5.7.20 Homebrew
# 〜省略〜

> rails -v
Rails 5.2.3

> ridgepole -v
ridgepole 0.7.7

tl;dr

schemaファイル

create_table "hoges", id: :string, limit: 36, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  t.integer "foo", comment: "foo!!"
  t.string "bar", comment: "bar!!"
end

モデル

app/models/hoge.rb
class Hoge < ApplicationRecord
  include PrimaryKeyUuidAutoGeneratable
end
concerns/primary_key_uuid_auto_generatable.rb
module PrimaryKeyUuidAutoGeneratable
  extend ActiveSupport::Concern

  included do
    before_create :generate_uuid
  end

  def generate_uuid
    return if self.id.present?

    self.id = SecureRandom.uuid
  end
end

他の方法?

activeuuid

RailsとUUIDでググるとactiveuuidが散見されるのだけど、bundle installすると全てのrailsコマンドが壊れる。

NoMethodError: undefined method `alias_method_chain' for ActiveRecord::ConnectionAdapters::Column:Class

なんじゃあそりゃあと思ったら、どうやらRails5でdeprecatedになったメソッドのようだ。
まじかい、最近メンテされてないんかなと思ってよく見たら

Latest commit 4f8e0ef on 2 Sep 2015

😭

Rails5から導入された、idの型がデフォルトでUUIDになる設定

github.com/rails/rails/pull/21762

ぽすぐれだったら良いかもだけど、MySQLでこれできてもUUID型が存在しないので意味がない...

Ridgepoleでの書き方

最初は

create_table "other_services_posts", id: :false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  t.string "id", limit: 36, null: false, primary_key: true, comment: "主キー"
  # 〜省略〜
end

と書いていて、applyもできるしこれでいいやと思っていたら、2度目のapply実行時に下記エラー。

Apply `db/Schemafile`
[WARNING] Primary key definition of `hoges` differ but `allow_pk_change` option is false
  from: {:id=>:string, :limit=>36}
    to: {:id=>false}

[ERROR] Mysql2::Error: Duplicate column name 'id': ALTER TABLE `hoges` ADD `id` varchar(36) NOT NULL PRIMARY KEY AFTER `id`

えー
どうもcreate_tableid: falseが2回目以降に矛盾を起こすらしい。

下記の様にcreate_table側の設定に書いたら2回目も問題なく実行されました。

create_table "hoges", id: :string, limit: 36, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  # 〜省略〜
end

レコード作成時のデフォルト値

MySQLにはデフォルト値に関数を指定できないので、ぽすぐれのようにDB側でUUIDのデフォルト値を持たせることができません。
ので、Rails側で値を入れる必要があります。

concerns/primary_key_uuid_auto_generatable.rb
module PrimaryKeyUuidAutoGeneratable
  extend ActiveSupport::Concern

  included do
    before_create :generate_uuid
  end

  def generate_uuid
    # 既に値が入っている場合は何もしない
    return if self.id.present?

    # ここでは作成したUUIDが既に使用されているか否かのチェックをしない、理由は以下。
    # - 毎回レコードの作成時にSQLが余分に発行されるのを避けたい
    # - UUIDの性質上その重複をアプリケーションレイヤーで調べる必要はない
    # - 仮に重複したとしてもDBの主キー制約によって保存はできない
    self.id = SecureRandom.uuid
  end
end

limit: 36

この設定が正しく動作しているかどうかについては、こう言う感じに細工すると確認できます。

id_generator.rb
  self.id = SecureRandom.uuid + "1"
> Hoge.create
   (0.2ms)  BEGIN
  Hoge Create (0.5ms)  INSERT INTO `hoges` (`id`, `created_at`, `updated_at`) VALUES ('e92a6b37-9bec-4452-9205-163e3dad98111', '2019-05-02 13:31:56', '2019-05-02 13:31:56')
   (0.2ms)  ROLLBACK
ActiveRecord::ValueTooLong: Mysql2::Error: Data too long for column 'id' at row 1: INSERT INTO `hoges` (`id`, `created_at`, `updated_at`) VALUES ('e92a6b37-9bec-4452-9205-163e3dad98111', '2019-05-02 13:31:56', '2019-05-02 13:31:56')
Caused by Mysql2::Error: Data too long for column 'id' at row 1

1文字足しただけでちゃんとエラーになっています。

参考

ryokkkke
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした