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~9
とa~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
となっています。
終わり