idをランダムな文字列にする

はじめに

Railsを使っているとデータのidは、デフォルトでは整数の連番になります。
この場合、URLにパラメータとしてidを含む場合
(例えば、https://hostname/api/v1/article/123)
に、idの数字を前後の数字に変えることで、ほかのデータに簡単にアクセスできてしまいます。

また、idの最大値でエントリ数がわかってしまいます。
一つ例を挙げると、ユーザーのidの最大値がわかれば、そのサービスのユーザー数がわかってしまいます。

このような理由から、idとして、ランダムな文字列を使いたいという場面があるかと思います。
ということで、Rails でidをランダムな文字列にする方法について調べたのでまとめていきます。

SecureRandom

RubyにはSecureRandomというモジュールが存在しています。
これは、安全な乱数生成器を用いてランダムな文字列を生成するモジュールです。

ランダムな整数や文字列を生成するメソッドをいくつか含んでいます。
そのうちのいくつかを実行してみました。

uuid

ランダムなuuidを生成します。
長さは128ビットです。
詳細はRFC 4122に書いてあるようです。

SecureRandom.uuid
irb(main):004:0> SecureRandom.uuid
=> "3fd0d5a9-02e9-4b96-847d-faa27122e498"
irb(main):005:0> SecureRandom.uuid
=> "43b0c332-7261-4817-8c02-ce3f1be44a6e"
irb(main):006:0> SecureRandom.uuid
=> "87eaa2eb-c8f1-4e8b-8ee2-05810416dab8"

hex

ランダムな16進数文字列を生成します。
長さを引数で指定します。生成される文字列の長さは、引数の二倍になります。

irb(main):007:0> SecureRandom.hex(10)
=> "815dfc2185445638d6af"
irb(main):008:0> SecureRandom.hex(10)
=> "cf626bac24f51532922e"
irb(main):009:0> SecureRandom.hex(10)
=> "228604cbf74ec76f49f3"

16進数ですので、0~9a~fしか使われていません。

base64

ランダムなbase64文字列を生成します。
長さを引数で指定します。生成される文字列の長さは、引数の約4/3倍になります。

irb(main):010:0> SecureRandom.base64(10)
=> "4xo/so3ueJtwvw=="
irb(main):011:0> SecureRandom.base64(10)
=> "1wXexvRipR9vFQ=="
irb(main):012:0> SecureRandom.base64(10)
=> "y+ip3FPZah0rcw=="

base64なので、末尾が==になっています。

Railsでidをランダムな文字列にする

Railsでモデルのidをランダムな文字列にする方法をまとめておきます。

まずはモデルを作成します。
ここでは、nameだけをもつ、Userモデルを作成します。

$ docker-compose run rails rails g model User name:string

生成されたmigrationファイルを編集します。

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users, id: :string do |t|
      t.string :name

      t.timestamps
    end
  end
end

create_table :users do |t|に、idをstring型にするよう追記しています。

次に、モデルのファイル(user.rb)を編集します。
生成時(create)に、idをSecureRandomで生成するようにします。
ここでは、uuidをidとして使うようにしました。
before_createを使っています。

class User < ApplicationRecord
  before_create :set_uuid

  private
  def set_uuid
    while self.id.blank? || User.find_by(id: self.id).present? do
      self.id = SecureRandom.uuid
    end
  end
end

railsコンソールで確認してみます。

irb(main):001:0> user = User.create(name: "Ken")
  TRANSACTION (0.3ms)  BEGIN
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", "16d5da21-eabf-4c41-9935-f6316e32e96b"], ["LIMIT", 1]]
  User Create (0.5ms)  INSERT INTO "users" ("id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["id", "16d5da21-eabf-4c41-9935-f6316e32e96b"], ["name", "Ken"], ["created_at", "2021-06-29 13:41:31.799194"], ["updated_at", "2021-06-29 13:41:31.799194"]]
  TRANSACTION (1.4ms)  COMMIT
=> 
#<User:0x0000561f8008a4a8
...
irb(main):002:0> user
=> 
#<User:0x0000561f8008a4a8
 id: "16d5da21-eabf-4c41-9935-f6316e32e96b",
 name: "Ken",
 created_at: Tue, 29 Jun 2021 13:41:31.799194000 UTC +00:00,
 updated_at: Tue, 29 Jun 2021 13:41:31.799194000 UTC +00:00>

userのidが16d5da21-eabf-4c41-9935-f6316e32e96bとなっています。

終わり