人生シーケンスブレイク

シーケンスブレイクとは

Docker Compose + Rails + Postgresでいい感じにUUID v4を利用する

やりたかったこと

Docker Compose環境下のRailsでプライマリキーをUUIDにしたかった。
ググるとPostgresでUUIDを使う為のDockerfileが散見されたが、最適な手段で実装したかった。

Rails Guides の UUID Primary Keys を参考に実施していく。

PostgreSQL >= 9.4 を使う

UUIDの実装には、Postgresの pgcrypto モジュールを利用するのでPostgres >= 9.4を利用する。 MySQLでは同様の機能がなく、MySQL上で実現するには一手間必要なので今回はPostgresを利用することとした。

version: '3'

services:
  rails:
    build: .
    command: "bundle exec rails s -p 3000 -b '0.0.0.0' -e development"
    environment:
      - TZ=Asia/Tokyo
      - WEB_CONCURRENCY=1
      - RAILS_MAX_THREADS=5
    volumes:
      - ./rails:/app
      - bundle_volume:/usr/local/bundle
    ports:
      - '3000:3000'
    depends_on:
      - postgres
  postgres:
    image: postgres:11-alpine
    restart: always
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=app_development
    ports:
      - '5432:5432'
    volumes:
      - postgres_volume:/var/lib/postgresql/data

volumes:
  postgres_volume:
    driver: local
  bundle_volume:
    driver: local

pgcrypto か uuid-ossp どちらを利用すべきか?

Active Record and PostgreSQLRuby on Rails Guides](https://edgeguides.rubyonrails.org/active_record_postgresql.html#uuid-primary-keys) に従うと pgcrypto or uuid-ossp エクステンションを有効にする必要がある。

検索する限りだと、 uuid-ossp を利用している記事も多く見つかったが、 uuid-ossp に依ると、 uuid-ossp はあまりよく維持されておらず、

注意: ランダムに生成された(バージョン4)UUIDのみが必要な場合には、代わりにpgcryptoモジュールのgen_random_uuid()を利用すること検討してください。

とあるので pgcrypto を使う。

pgcryptoエクステンションの有効化

下記のようなのマイグレーションファイルを作って実行した。

class EnableExtensionPgcrypto < ActiveRecord::Migration[5.2]
  def change
    enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
  end
end

pgcryptoやuuid-osspを有効にするだけのshスクリプトを実行するよう拡張されたDockerfileも確認したが、ぶっちゃけ有効化にはSQLコマンドを実行するだけでよいので、マイグレーションファイルでの実行の方がDockerイメージを使い回せてスマート。

UUIDの利用する際にはマイグレーションファイルに下記のように書く。

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
<200b>    create_table :posts, id: :uuid, default: 'gen_random_uuid()' do |t|
<200b>      t.text :body

      t.timestamps
    end
  end
end

のように型は uuid になり、

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :comments, id: :uuid, default: 'gen_random_uuid()' do |t|
      t.references :post, type: :uuid
      t.text :body

      t.timestamps
    end
  end
end

参照する場合には、 t.references を使う。

プライマリキーのデフォルトをUUIDにする

application.rbに下記を書けばOK

    config.generators.orm :active_record, primary_key_type: :uuid

デメリット

至るところで言われているが、first, lastメソッドが意図通りには利用できなくなる。 first, lastメソッドの順番は、 rails/finder_methods.rb at 5-2-1 · rails/rails には、

def ordered_relation
  if order_values.empty? && primary_key
    order(arel_attribute(primary_key).asc)
  else
    self
  end
end

となっている為、時系列ではないランダムなUUID v4ではcreated_atを利用した取得方法になるだろう。

どうしてもIDの順序を保証する必要がある場合には、UUID v4以外の選択肢も検討した方がよさげ。

参考