【Rails】マイページのURL末尾を「ユーザー名」にする

&emsp;

Railsを使ったWebアプリの開発中、「マイページのURL末尾を、ユーザーIDじゃなくて＂ユーザー名＂にできたらかっけぇ...」と思い、実際にその機能をつけました。
&emsp;

マイページ（ユーザー詳細ページ：users/show.html.erb）のURL末尾をユーザーIDから「ユーザー名」に変更することは、とてつもなく重要です。なぜなら、ユーザー情報を共有する／されるときに、ユーザーを一瞬で識別できるようになるからです。実際、X（旧Twitter）やInstagram、そしてQiita、といった超人気WebサービスのマイページURLの末尾は、自身で設定したユーザー名になっています。
&emsp;

しかし、問題点があります。それは「何の追記もないまま（＝リソースフルにしたがったまま）usersのshowアクションをルーティングに設定すると、マイページのURL末尾がユーザーIDになってしまう」という問題です。
&emsp;

そこで、「マイページのURL末尾をユーザーIDから＂ユーザー名＂に変更する」ためのRails実装手順をまとめました。
&emsp;
&emsp;
&emsp;

マイページのURL末尾をユーザーIDから＂ユーザー名＂に変更する手順説明は、次の内容でまとめました。
* 開発環境
* 実装の準備
* 具体的な実装内容 《本編》

「開発環境」では、本記事で解説する機能が正しく挙動したことを実際に視認できた環境を整理しました。「実装の準備」では、マイページのURL末尾をユーザー名に変更する前段階で必要になるライブラリの導入・ルーティングの設定を整理しました。「具体的な実装内容 《本編》」では、ルーティング・コントローラー・ビューに分けて、本記事のテーマとなる実装を、なるべく噛み砕いて解説しました。
&emsp;
&emsp;
&emsp;

## 開発環境

| Category       | Value,Version       |
| :------------: | :-----------------: |
| OS             | MacOS Sonoma 14.1.2 |
| Ruby           | 3.2.0               |
| Ruby on Rails  | 7.0.8               |
| Gem            | devise              |

*Gem：Ruby専用拡張機能（Ruby用ライブラリ）
&emsp;
&emsp;
&emsp;

## 実装の準備

マイページのURL末尾を「ユーザー名」にするために、必要な準備内容をまとめました。

* 【devise】 ユーザー新規登録時のユーザー名を半角英数字にする
* users へ showアクションの設定
&emsp;

### 【devise】 ユーザー新規登録時のユーザー名を半角英数字にする
&emsp;

まず、Railsでのユーザー管理機能を簡単に実現するために、『devise』というGemを、以下の手順でインストールします。

```ruby:Gemfile
# Gemfile：アプリケーションで使用するGemの「名前」と「バージョン」の情報を管理するファイル

gem 'devise'   # Gemfile末尾に、この一行を記述
```

```bash:Terminal
# ターミナル上で、deviseを導入したいアプリケーションのディレクトリへ移動

% bundle install   # Gemfile.lockというファイルでインストール済みのGem情報を記録するためのコマンド

# サーバーが起動中の場合、サーバーを停止

% rails s   # インストールしたGemを反映させるために、サーバーを再起動

% rails g devise:install   # deviseの設定ファイルを作成するためのコマンド
```
&emsp;

次に、以下の操作で `User` モデルを作成します。

```bash:Terminal
% rails g devise user
```
このコマンド操作で、app/modelsディレクトリに `user.rb` というモデル用のファイルが生成されれば成功です。
&emsp;

usersテーブルに `:user_name` カラムを用意し、NOT NULL制約を付与します。
```ruby:db/migrate/2023〜〜〜.rb
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :user_name,          null: false # NOT NULL制約を付与
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
    end
  end
end
```
NOT NULL制約を付与しておくべき理由は、テーブルの属性値に空の値（NULL）が入らない（NOT）ように制限するためです。
&emsp;

続いて、「ユーザーを識別する」＝「同じユーザー名が存在しないようにする」という目的を達成するために、 `:user_name` カラムへ `add_index` を使って一意性制約を付与します。

```ruby:db/migrate/2023〜〜〜.rb
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :user_name,          null: false
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
    end

    add_index :users, :user_name,            unique: true   # 一意性制約を付与
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
  end
end
```
[参考：uniqueness < Rails公式ガイド](https://railsguides.jp/active_record_validations.html#uniqueness)
&emsp;

usersテーブルの設計が完了したので、マイグレーションを実行します。
```bash:Terminal
% rails db:migrate
```
&emsp;

次に、Userモデルの `:user_name` カラムに、「ユーザーはアカウント作成時にユーザー名を入力する際、半角英数字でないとアカウントを新規作成できない」というバリデーションを付与します。
```ruby:models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  validates :user_name, presence: true, uniqueness: true, format: { with: /\A[a-zA-Z0-9]+\z/ }
end
```
ユーザー名を半角英数字に限定する理由は、**日本語だとURLの見た目が悪くなってしまう（可能性が高い）から**です。
&emsp;

なぜ日本語だとURLの見た目が悪くなってしまうのかというと、**UTF-8 エンコードをはじめとする技術によって、文字列が冗長になってしまうから**です。

実際、GoogleはURL構造に対して、ASCIIに含まれない文字がURLに入る場合、UTF-8エンコードを使用することを推奨しています。

[参考：GoogleにおけるURL構造のベストプラクティス](https://developers.google.com/search/docs/crawling-indexing/url-structure?hl=ja)

[参考：ASCII文字](https://www.ibm.com/docs/ja/aix/7.2?topic=support-ascii-characters)

[参考：UCS-2およびUTF-8](https://www.ibm.com/docs/ja/aix/7.2?topic=support-ucs-2-utf-8)

つまり、Google Chromeなどのブラウザに表示したいURLに日本語が含まれていた場合、その文字列は「日本語以外の・パッと見て意味不明な・長〜い」文字列に置き換えられてしまう、ということです。

置き換える目的は、HTTPリクエストの解釈をより正確におこなうためです。


[参考：予約文字と除外文字](https://www.ibm.com/docs/ja/cics-ts/5.4?topic=concepts-reserved-excluded-characters)
&emsp;

...そもそも、マイページのURL末尾をユーザー名にする目的は、「**ユーザー情報を共有する／されるときに、ユーザーを一瞬で識別できるようにするため**」です。


なので、日本語など半角英数字以外でのユーザー名入力を許してしまうと「URLからユーザーを瞬時に識別することができない（できる方も少数いるかもですが）」ので、本末転倒になってしまいます...
&emsp;

したがって、ユーザー新規登録時のユーザー名を半角英数字に制限するバリデーションを準備しておくことは、大切です。
&emsp;
&emsp;

### users へ showアクションの設定
&emsp;

マイページを表示するため、usersコントローラーにshowアクションを定義する必要があります。

なので、先にルーティングの設定をします。
```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users # deviseコマンドでUserモデルを作成したときに自動で追記された行
  resources :users, only: :show
end
```
&emsp;

ただし、この時点ではブラウザのアドレスバーに表示されるURL末尾が `/users/:id` つまり「~/users/1」のように、ユーザーIDになってしまいます（この様子は、resourcesを宣言したら自動的に構成されるため「リソースフル」と表現されます）
```zsh:Terminal
# rails routes というコマンドで表示可能

Prefix   Verb   URI Pattern             Controller#Action
user     GET    /users/:id(.:format)    users#show
```

ということで、この `:id` 部分を先ほど用意したユーザー名（カラム） `:user_name` に変更します。
&emsp;
&emsp;
&emsp;

## 具体的な実装内容 《本編》
&emsp;

それでは、マイページのURL末尾をデフォルトの（リソースフルな）ユーザーIDからユーザー名に変更する方法を、次の順番で説明します。
* ルーティング
* コントローラー
* ビュー
&emsp;

### ルーティング
&emsp;

まず、ルーティングに次の行を追加します。
```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :users, only: :show
  get '/users/:user_name', to: 'users#show' # 追加
end

```

[参考：動的なセグメント < リソースフルでないルーティング](https://railsguides.jp/routing.html#%E5%8B%95%E7%9A%84%E3%81%AA%E3%82%BB%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88)

:::note warn
この追記内容は、ルーティング内で一番下に書いた方がいいかもしれません。理由は、一番下に書かないと、他のURLも同様に `get '/users/:user_name'` と処理されてしまう可能性があるからです。
:::
&emsp;

ここで、改めて `rails routes` というコマンドを叩くと、`/users/:user_name` のパスでリクエストした際に `users_controller.rb` のshowアクションを実行するルーティングが設定できていることがわかります。

```zsh:Terminal
Prefix   Verb   URI Pattern                    Controller#Action
user     GET    /users/:id(.:format)           users#show
         GET    /users/:user_name(.:format)    users#show # ルーティングが追加された！
```
しかし、このままではURL末尾をユーザーIDにして直接打ち込んで遷移しようとすると、遷移できてしまう可能性があります。
&emsp;

### コントローラー
&emsp;

ということで次に、コントローラーを実装します。
```ruby:
class UsersController < ApplicationController
  def show
    user = User.find_by(user_name: params[:id])
    @user_name = user.user_name
  end
end
```
`user = User.find_by(user_name: params[:id])` では、ユーザーidをparamsで取得し、そのユーザーのレコード（DBの横列）の中から `user_name:` カラム（DBの縦列）を見つけ、それを変数userに代入しています。
&emsp;
`@user_name = user.user_name` では、user_name情報をインスタンス変数として取り出しています。
&emsp;

### ビュー
&emsp;

最後に、マイページつまり`users/show.html.erb`を表示させるためのリンクを組んで完成です。
```ruby:
<div> <%= link_to 'マイページ', "/users/#{current_user.user_name}" %> </div>
```
current_userメソッドは、事前にdeviseを導入しておいた場合、使えるメソッドです。現在ログインしているユーザーの情報を取得できます。
&emsp;

オマケで、他人のユーザーページを表示させるためのリンクの組み方も紹介します。
```ruby:
<div> <%= link_to "#{user.user_name}さんのページ", "/users/#{user.user_name}" %> </div>
```
&emsp;
&emsp;
&emsp;

プログラミング学習のアウトプット、以上になります。
ご指摘あれば、お気軽にコメントください。
&emsp;
&emsp;
&emsp;
[参考 + 感謝：【Rails】ユーザーページのURLをユーザーネームにする（twitter.com/hanamichi10みたいにする）](https://qiita.com/Jumpei_Sogawa/items/c90d3eeac3414d2a5e7c)