時々とおまわり

プログラミング雑記

Railsにdeviseを使ってユーザーページを追加する

前回の記事ではRailsアプリにdeviseでログイン機能を実装しました。 今回はプラクティスの終了条件である次の3つのページを追加します。

  • ユーザー詳細: users#show
  • ユーザー編集: users#edit
  • ユーザー一覧: users#index

実装対象のアプリはdevise gemのインストールする記事で使用したアプリです。

実装は次の手順で行います。

  1. ユーザー詳細、ユーザー一覧のルーティングを追加
  2. Userモデルのコントローラー、ビューを追加
  3. ユーザー詳細、ユーザー一覧ページへのリンクを追加
  4. usersテーブルへのカラム追加
  5. ユーザー詳細ページでユーザー情報を表示
  6. 追加したカラムをユーザー編集ページで編集可能にする
  7. ページネーション付きでユーザー一覧を表示
    1. ユーザー編集フォームに追加したカラムの入力フォームを追加する
    2. 追加したカラムをstrong_parametersに追加する
  8. ユーザ編集後のリダイレクト先を変更する
    1. カスタマイズ用contollerを設定
    2. リダイレクト先の変更用メソッドをオーバーライド

1. ユーザー詳細、ユーザー一覧のルーティングを追加

deviseのデフォルトでは認証機能付きモデルのshowindex アクションは設定されないので独自にルーティングの追加が必要になります。 config/routes.rb:onlyでindexとshowアクションのみ追加します。 :only についてはRailsガイドを参照してください。

# config/routes.rb

Rails.application.routes.draw do
  devise_for :users
  # users#index, users#show を追加
  resources :users, only: %i[index show]
  resources :books
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

ルーティングの追加を確認します。

$ rails route
Prefix Verb       URI Pattern               Controller#Action
user GET          /users/:id(.:format)      users#show
users GET         /users(.:format)          users#index
...

以上でusers#indexusers#showのルーティングを追加は完了です。

2. Userモデルのコントローラー、ビューを追加

追加したusers#indexusers#showの2つのルーティング用のコントローラーとビューを追加します。

rails generate controller モデル名複数形 アクション名 でcontrollerファイルとviewファイルを作成できます。 showindex アクション用にルーティングを既に追加済みなので--skip-routes オプションで不要なルーティング作成をスキップしてコマンドを実行します。

rails g controller で不要なルーティングの追加をスキップしたい - karlley's tech blog

今回はusers#indexusers#show アクションが追加されたcontrollerファイルとそれぞれのルーティング用のviewファイルを作成します。

$ rails g controller users index show --skip-routes
Running via Spring preloader in process 13318
      create  app/controllers/users_controller.rb
      invoke  erb
       exist    app/views/users
      create    app/views/users/index.html.erb
      create    app/views/users/show.html.erb
      invoke  test_unit
      create    test/controllers/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/users.scss

作成したcontrollerファイルとviewファイルを確認します。

$ ls -1 app/controllers/
application_controller.rb
books_controller.rb
concerns
users
users_controller.rb # 追加したcontrollerファイル

$ ls -1 app/views/users/
confirmations
index.html.erb # 追加したviewファイル
mailer
passwords
registrations
sessions
shared
show.html.erb # 追加したviewファイル
unlocks

以上でUserモデルのコントローラー、ビューの追加は完了です。

3. ユーザー詳細、ユーザー一覧ページへのリンクを追加

追加したユーザー詳細、ユーザー一覧ページへのリンクを全viewファイルで読み込まれるapp/views/application.html.erb に追加し、全ページでリンクを表示させます。 ログアウトのリンクはdevise gemのsession モジュールに用意されているルーティングを指定します。

  • ユーザ詳細: users#show
  • ユーザ一覧: users#index
  • ログアウト: devise/sessions#destroy

追加するルーティングを確認します。

$ rails route
...
Prefix               Verb       URI Pattern                   Controller#Action
destroy_user_session DELETE     /users/sign_out(.:format)     devise/sessions#destroy # ログアウト
user                 GET        /users/:id(.:format)          users#show              # ユーザ詳細
users                GET        /users(.:format)              users#index             # ユーザ一覧
...

app/views/application.html.erblink_toメソッドでリンクを追加します。

# app/views/application.html.erb

<!DOCTYPE html>
<html>
...
<body>
  ...
  # 下記を追加
  <% if user_signed_in? %>
  <p>User: <%= current_user.email %></p>
  <%= link_to 'Profile', user_path(current_user) %>
  <%= link_to 'All Users', users_path %>
  <%= link_to 'Logout', destroy_user_session_path, method: :delete %>
  <% end %>

  <%= yield %>
</body>
</html>

ユーザー詳細ページへのリンクについて

  • user_path のみidを引数として渡す必要があります。引数にcurrent_user でログインユーザの情報を渡す事でRailsがidを取得してくれます。
  • 引数はuser_path(id: current_user.id) としなくてもcurrent_user だけでokです。
  • ユーザ詳細はhttp://localhost:3000/users/ユーザのid のようなURLになります。

ログアウトのリンクについて

method: delete で指定したパスにHTTPメソッドをDELETE として送信します。

form タグにGET/POST以外のHTTPメソッドを指定する - karlley's tech blog

以上でusers#indexusers#show ページへのリンクを追加は完了です。

4. usersテーブルへのカラム追加

今回作成するアプリではプロフィールページでメールアドレスとパスワードに加えて、郵便番号、住所と自己紹介文を表示できることが求められます。 郵便番号、住所、自己紹介文を保存するカラムが必要なのでusersテーブルへ下記カラムを追加します。

  • 郵便番号: zipcode、integer型
  • 住所: address、string型
  • 自己紹介文: profile、text型

Railsでテーブルにカラムを追加する - karlley's tech blog

カラム追加用マイグレーションファイルを作成します。

$ rails g migration AddColumnsToUsers zipcode:integer address:string profile:text
Running via Spring preloader in process 41547
      invoke  active_record
      create    db/migrate/20220903210311_add_columns_to_users.rb

マイグレーションしてカラムを追加します。

$ rails db:migrate
Running via Spring preloader in process 42834
== 20220903210311 AddColumnsToUsers: migrating ================================
-- add_column(:users, :zipcode, :integer)
   -> 0.0052s
-- add_column(:users, :address, :string)
   -> 0.0004s
-- add_column(:users, :profile, :text)
   -> 0.0003s
== 20220903210311 AddColumnsToUsers: migrated (0.0061s) =======================

カラムの追加を確認します。

$ rails c
Running via Spring preloader in process 46344
Loading development environment (Rails 6.1.6)
irb(main):001:0> User.column_names
   (0.9ms)  SELECT sqlite_version(*)
=>
["id",
 "email",
 "encrypted_password",
 "reset_password_token",
 "reset_password_sent_at",
 "remember_created_at",
 "created_at",
 "updated_at",
 # ↓追加したカラム
 "zipcode",
 "address",
 "profile"]

5. ユーザー詳細ページでユーザー情報を表示

ユーザー詳細ページにDBから取得したユーザー情報を表示します。 rails g controller users index show --skip-routes コマンドで作成した次の2つのファイルに追記します。

  • app/controllers/users_controller.rb
  • app/views/users/show.html.erb
# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def index; end

  def show
    # 下記を追記
    @user = User.find(params[:id])
  end
end
# app/views/users/show.html.erb

# 下記に書き換え
<h1>Profile</h1>
<p>Email: <%= @user.email %></p>
<p>Zipcode: <%= @user.zipcode %></p>
<p>Address: <%= @user.address %></p>
<p>Profile: <%= @user.profile %></p>

<% if @user == current_user %>
<%= link_to 'Edit', edit_user_registration_path %>
<%= link_to 'Delete', user_registration_path, data: { confirm: 'are you sure?' }, method: :delete %>
<% end %>
  • params[:id] で送信されたパラメータからidを取得して該当ユーザをfind メソッドで検索後、@user でビューに渡して各カラムの値を表示させています。
  • if @user == current_user とすることでユーザー編集、ユーザー削除のリンクを自分のプロフィールページのみ表示するようにしています。
  • data: { confirm: 'are you sure?' }とすることで削除時にare you sure? が表示されます。

追加したユーザー編集ページへのリンクとユーザー削除のリンクは以下のルーティングを指定しました。

$ rails routes
                  Prefix Verb   URI Pattern             Controller#Action
...
edit_user_registration GET    /users/edit(.:format)   users/registrations#edit # ユーザー編集
     user_registration PATCH  /users(.:format)        users/registrations#update
                       PUT    /users(.:format)        users/registrations#update
                       DELETE /users(.:format)        users/registrations#destroy # ユーザー削除
                       POST   /users(.:format)        users/registrations#create
                 users GET    /users(.:format)        users#index
                  user GET    /users/:id(.:format)    users#show
...

6. 追加したカラムをユーザー編集ページで編集可能にする

追加したカラムをユーザ編集ページで編集できるようにします。 ユーザ編集ページの表示にはdeviseが生成したviewファイルを使用します。

  1. ユーザー編集フォームに追加したカラムの入力フォームを追加する
  2. 追加したカラムをstrong_parametersに追加する

1. ユーザー編集フォームに追加したカラムの入力フォームを追加する

app/views/users/registrations/edit.html.erb に追加したカラムの入力フォームを追加します。

# app/views/users/registrations/edit.html.erb

<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "users/shared/error_messages", resource: resource %>

<div class="field">
  <%= f.label :email %><br />
  <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>

...

# ここからを追加↓
<div class="field">
  <%= f.label :zipcode %><br />
  <%= f.text_field :zipcode, autocomplete: "zipcode" %>
</div>

<div class="field">
  <%= f.label :address %><br />
  <%= f.text_area :address, autocomplete: "address" %>
</div>

<div class="field">
  <%= f.label :profile %><br />
  <%= f.text_area :profile, autocomplete: "profile" %>
</div>
# ここまで追記↑

<div class="actions">
  <%= f.submit "Update" %>
</div>
<% end %>
...

ちなみに、autofocus: true はフォーム内に1つしか指定できません。

2. 追加したカラムをstrong_parametersに追加する

追加したカラム(zipcodeaddressprofile)をstrong_parametersに追加しなとUPDATE処理がUnpermitted parameters: で弾かれてしまうので設定を追加します。 configure_permitted_parameters メソッドを定義してstrong_parametersにカラム追加を行います。

heartcombo/devise: Flexible authentication solution for Rails with Warden.

  • app/controllers/application_controller.rbconfigure_permitted_parameters メソッドを定義します。
  • before_action で全てのアクションが実行される前にメソッドを実行します。
# app/controllers/application_controller.rb

# frozen_string_literal: true

class ApplicationController < ActionController::Base
  before_action :authenticate_user!
  # ここから追記↓
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:account_update, keys: %i[zipcode address profile])
  end
  # ここまで追記↑
end

configure_permitted_parameters メソッド内で指定している:account_update を変更することで他のアクションにも対応することができます。

  • :sign_in: ログイン
  • :sign_up: サインアップ
  • :account_update: アップデート

devise導入からユーザーのプロフィール画面を作成するまで - Qiita

7. ページネーション付きでユーザー一覧を表示

kaminari gemを使ってページネーション付きでユーザー一覧を表示します。

kaminari gemについての雑なまとめ - karlley's tech blog

rails g controller users index show --skip-routes コマンドで作成した次の2つのファイルへ追記します。

  • app/controllers/users_controller.rb
  • app/views/users/index.html.erb
# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def index
    # 下記を追記
    @users = User.order(:id).page(params[:page])
  end

  def show
    @user = User.find(params[:id])
  end
end
# app/views/users/index.html.erb

# 下記に書き換え
<h1>All Users</h1>

<table>
  <thead>
    <tr>
      <th>Email</th>
      <th>Zipcode</th>
      <th>Address</th>
      <th>Profile</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @users.each do |user| %>
    <tr>
      <td><%= user.email %></td>
      <td><%= user.zipcode %></td>
      <td><%= user.address %></td>
      <td><%= user.profile %></td>
      <td><%= link_to 'Show', user_path(user) %></td>
      <% if user == current_user %>
      <td><%= link_to 'Edit', edit_user_registration_path %></td>
      <% end %>
    </tr>
    <% end %>
  </tbody>
</table>

<%= paginate @users %>
  • if user == current_user とすることで自分のアカウントのみユーザー編集へのリンクを表示しています。
  • <%= paginate @users %> でkaminari gemを使ったページネーションが表示されます。

8. ユーザー編集後のリダイレクト先を変更する

deviseのデフォルトではユーザーのUPDATE処理後にルート(books#index)へリダイレクトするようになっています。 このリダイレクト先をユーザー詳細ページ(users#show)に変更します。

  1. カスタマイズ用コントローラーを設定
  2. リダイレクト先の変更用メソッドを定義

heartcombo/devise: Flexible authentication solution for Rails with Warden.

【Devise】アカウント登録、ログイン/ ログアウト、アカウント編集後のリダイレクト先の変更 - Qiita

1. カスタマイズ用コントローラーを設定

rails generate devise:controllers users コマンドで生成したapp/controllers/users/ 内の必要なcontrollerファイルを使えるように設定します。

未設定の場合だとルーティングではdeviseデフォルトのコントローラーを使用しているのを確認できます。

$ rails routes
                  Prefix Verb   URI Pattern                         Controller#Action
                    root GET    /                                   books#index
                   users GET    /users(.:format)                    users#index
                    user GET    /users/:id(.:format)                users#show
                                                                    ↓devise/...になっている
        new_user_session GET    /users/sign_in(.:format)            devise/sessions#new
            user_session POST   /users/sign_in(.:format)            devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)           devise/sessions#destroy
       new_user_password GET    /users/password/new(.:format)       devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format)      devise/passwords#edit
           user_password PATCH  /users/password(.:format)           devise/passwords#update
                         PUT    /users/password(.:format)           devise/passwords#update
                         POST   /users/password(.:format)           devise/passwords#create
cancel_user_registration GET    /users/cancel(.:format)             devise/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)            devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)               devise/registrations#edit
       user_registration PATCH  /users(.:format)                    devise/registrations#update
                         PUT    /users(.:format)                    devise/registrations#update
                         DELETE /users(.:format)                    devise/registrations#destroy
                         POST   /users(.:format)                    devise/registrations#create

使用するコントローラーをdevise/registrations からusers/registrationsへ変更する設定をconfig/routes/rb に追加します。 この記述はモジュール毎に行う必要があるので他のモジュールでもカスタマイズ用コントローラーを使う場合は別途設定を追加してください。

Rails.application.routes.draw do
  root 'books#index'
  # 下記に修正
  devise_for :users, controllers: { registrations: 'users/registrations' }
  resources :users, only: %i[index show]
  resources :books
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

使用するコントローラーが変更されたか確認します。

$ rails routes
                  Prefix Verb   URI Pattern                         Controller#Action
                    root GET    /                                   books#index
                   users GET    /users(.:format)                    users#index
                    user GET    /users/:id(.:format)                users#show
                                                                    ↓ 指定していないモジュールはそのまま
        new_user_session GET    /users/sign_in(.:format)            devise/sessions#new
            user_session POST   /users/sign_in(.:format)            devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)           devise/sessions#destroy
       new_user_password GET    /users/password/new(.:format)       devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format)      devise/passwords#edit
           user_password PATCH  /users/password(.:format)           devise/passwords#update
                         PUT    /users/password(.:format)           devise/passwords#update
                         POST   /users/password(.:format)           devise/passwords#create
                                                                    ↓ user/...に変更されている
cancel_user_registration GET    /users/cancel(.:format)             users/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)            users/registrations#new
  edit_user_registration GET    /users/edit(.:format)               users/registrations#edit
       user_registration PATCH  /users(.:format)                    users/registrations#update
                         PUT    /users(.:format)                    users/registrations#update
                         DELETE /users(.:format)                    users/registrations#destroy
                         POST   /users(.:format)                    users/registrations#create

これでカスタマイズ用コントローラーが使えるようになりました。

2. リダイレクト先の変更用メソッドをオーバーライド

作成したカスタマイズ用コントローラーでdeviseが用意しているリダイレクト先変更用メソッドをオーバーライドして動作を変更します。

  • オーバーライドとは「継承した親クラスのメソッドの内容を子クラスで再定義すること」です。
  • after_update_path_for メソッドをapp/controllers/users/registrations_controller.rb 内でオーバーライドします。

[devise]after_update_path_forメソッドをオーバーライドを有効にする方法

Class: Devise::RegistrationsController — Documentation for heartcombo/devise (main)

# app/controllers/users/registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController
...
  # 下記を追記
  protected

  def after_update_path_for(resource)
    user_path(resource)
  end
  ...
end

これでユーザ編集後のリダイレクト先がユーザー詳細ページ(users#show) に変更されます。

以上でdeviseを使ってユーザーページの追加は完了です。

まとめ

ユーザーページを追加するだけでものすごく時間が掛かってしまいました。 一つずつ公式のドキュメントを確認しながら実装するのはとても大変でしたが学びも多かったので良かったです。

参照

Rails のルーティング - Railsガイド

rails g controller で不要なルーティングの追加をスキップしたい - karlley's tech blog

link_to | Railsドキュメント

form タグにGET/POST以外のHTTPメソッドを指定する - karlley's tech blog

Railsでテーブルにカラムを追加する - karlley's tech blog

HTML 選択要素 - HTML: HyperText Markup Language | MDN

heartcombo/devise: Flexible authentication solution for Rails with Warden.

devise導入からユーザーのプロフィール画面を作成するまで - Qiita

kaminari gemについての雑なまとめ - karlley's tech blog

heartcombo/devise: Flexible authentication solution for Rails with Warden.

【Devise】アカウント登録、ログイン/ ログアウト、アカウント編集後のリダイレクト先の変更 - Qiita

[devise]after_update_path_forメソッドをオーバーライドを有効にする方法

Class: Devise::RegistrationsController — Documentation for heartcombo/devise (main)