【Rails】URLでidの代わりにランダムな文字列を表示させる

# Update
gemを使わずに自分で実装する方法を記事におこしました。 --> [【Rails】ランダムな文字列をPrimary keyとして使う - Qiita](https://qiita.com/at-946/items/f495442a8a9bfdae5a6b)

# Introduction
Railsのscaffoldを使うと、デフォルトで`/users/:id/`のように`id`を使ったURLが生成されます。
しかしながら、システムでシーケンシャルに払い出していて主キーとして使っている`id`をURLというユーザーが見えるところで使うのって少し抵抗ありません？
ということでURLには`id`ではなくランダムな文字列（Qiitaの記事はそうなってますね）が表示されるようにする方法をメモります。

# Environment
* Rails 5.2.2
* Ruby 2.6.3

# 1. `public_uid` gemをインストール
便利そうなgemを作っている方がいらっしゃいました。データをDBに登録するときに、ランダムな文字列を生成してくれるgemです。デフォルトだとHexString（16進数）で生成されます。
[GitHub - equivalent/public_uid: Automatically generates random unique public id for record.](https://github.com/equivalent/public_uid)
今回はこちらを使ってみます。

```ruby:Gemfile
gem 'public_uid'
```

```bash
$ bundle install
```

今回は、`User`モデルをつくろうとしている前提で物語を進めていきます。

# 2. `public_uid`カラムを追加
対象のモデルに`public_uid`カラムを追加していきます。

## 2-1. `User`モデルをこれから作る場合
```bash
$ rails g scaffold User name:string public_uid:string
```
でscaffoldが作成されます。この中にマイグレーションファイルも含まれますが、`public_uid`は検索対象のカラムになるので検索速度をあげるためにindexをはりました。

```rails:/db/migrate/YYYYMMDDhhmmss_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string  :name,       null: false
      t.string  :public_uid, null: false

      t.timestamps null: false
    end
    add_index :users, :public_uid, unique: true
  end
end
```
マイグレーションを適用します。

```bash
$ rails db:migrate
```

## 2-2. `User`モデルがすでに存在する場合
```bash
$ rails g migration AddColumnPublicUidToUsers public_uid:string
```
マイグレーションファイルに検索性能をあげるために`public_uid`へのindexをはります。

```ruby:/db/migrate/YYYYMMDDhhmmss_add_column_public_uid_to_users.rb
class AddColumnPublicUidToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :public_uid, :string
    add_index  :users, :public_uid, unique: true
  end
end
```

マイグレーションを適用します。
```bash
$ rails db:migrate
```

# 3. `User`モデルに`public_uid`を自動生成するコードを付与
対象のモデルファイルに`generate_public_uid`というコードを１行追加するだけです。これによってモデルがDBに登録されるタイミングで自動的に`public_uid`に8桁のランダムな文字列が付与されるようになります。

```ruby:/app/models/user.rb
class User < ApplicationRecord
  generate_public_uid
end
```

# 4. `to_param`メソッドをオーバーライド
`to_param`メソッドを使ってURLの`:id`を`:public_uid`に変更します。

```ruby:/app/models/user.rb
class User < ApplicationRecord
  generate_public_uid

  def to_param
    public_uid
  end
end
```

# 5. コントローラ側で`public_uid`で検索
scaffoldでモデルを作成すると、コントローラファイルで以下のようにモデル検索がなされていると思います。

```ruby:/app/controllers/users_controller.rb
private
  def set_user
    @user = User.find(params[:id])
  end
```
`to_param`をオーバーライドしたため、`params[:id]`には`public_uid`の値が入るようになっており、`find`メソッドでは該当の`id`を持つUserモデルのレコードが存在しないためエラーになってしまいます。
そこで、以下のように編集して`public_uid`でUserモデルを検索するように変更します。

```ruby:/app/controllers/users_controller.rb
private
  def set_user
    @user = User.find_by(public_uid: params[:id])
  end
```

以上で、URLに`id`の代わりにランダムな文字列を表示させることができます。

# おまけ
## おまけ 1：ランダムな文字列でなくランダムな数字にしたい
`public_uid gem`では、ランダムな数字を生成する仕組みとして、`NumberRandom`と`NumberSecureRandom`という設定を用意してくれています。設定の方法は`3`で記載した`generate_public_uid`に`generator`を指定してあげるだけです。

```ruby:/app/models/user.rb
class User < ApplicationRecord
  # NumberRandomの場合
  generate_public_uid generator: PublicUid::Generators::NumberRandom.new
  # Number　SecureRandomの場合
  generate_public_uid generator: PublicUid::Generators::NumberSecureRandom.new
end
```

## おまけ 2：public_uidの文字数を指定したい
8文字だとすぐ枯渇しかねないモデルの場合は`public_uid`の文字数を増やしたくなります。この場合も、`generator`で指定してあげるだけです。例えば20文字を生成したい場合は以下の通りです。

```ruby:/app/models/user.rb
class User < ApplicationRecord
  generate_public_uid generator: PublicUid::Generators::HexStringSecureRandom.new(20)
end
```

# Conclusion
便利。
他にも、全くランダムな文字列（a-z,A-Z,0-9）にする方法や、そもそも`id`をランダム値にする方法などもあるみたいです。詳しくは`gem`のREADMEをご覧いただければと思います。
私は、ここまでで満足！

# Reference
- [GitHub - equivalent/public_uid: Automatically generates random unique public id for record.](https://github.com/equivalent/public_uid)