はじめに
この記事では、Railsで簡単にログイン認証システムを構築する方法を学びます。devise
というgemを使用し、ユーザー登録、ログイン、ログアウトなどの基本的な認証機能の設定から、カスタマイズ方法、アクセス制限までを学びます。
事前準備
ターミナルでmemo_app
に移動し、rails db:migrate:reset
を実行してデータベースを作り直しましょう。
1
cd ~/memo_app
1
rails db:migrate:reset
コンソールを起動して、学習メモのデータを挿入しましょう。
1
rails c
1
2
3
4
Memo.create([
{ title: 'URLを構成する基本的な要素', content: 'URLの基本的な構成要素には、プロトコル(Scheme)、ドメイン名(Host)、そしてパス(Path)の3つがある。' },
{ title: 'プロトコルについて', content: 'プロトコルは、インターネット上で情報をやり取りするときの「取り決め」や「ルール」を指す。URLの先頭にある「https」(または「http」)がこれに該当する。これはWebサイトとの情報のやり取りにhttpsというプロトコルを使用することを示す。' }
])
挿入後、以下のコマンドを実行して、挿入した学習メモの内容がデータベースに保存されているかを確認しましょう。
1
Memo.all
これで事前準備は全て完了しました。コンソールを閉じて次に進みましょう。
ログイン認証の基本ステップ
ログイン認証とは、ユーザーがアプリケーションににアクセスする際、正しいメールアドレス(またはユーザー名)とパスワードを入力し、自身の身元を証明するプロセスです。これは、「私は登録された正しいユーザーです」とシステムに伝える手続きを指します。
ログイン認証は、ユーザーのデータやプライバシーを保護し、不正アクセスから守るための基本的なセキュリティ対策です。
実装する前に、まずはログイン認証の基本ステップについて見ていきましょう。メモアプリケーションの最終完成の画面をみながら確認していきましょう。
1.メールアドレスとパスワードの入力
ユーザーは、登録時に設定したメールアドレス(またはユーザー名)とパスワードを使用して、Webアプリケーションのログインページにアクセスします。このユーザー情報は、あらかじめWebアプリケーションに登録しておく必要があります。
2.情報の検証
Webアプリケーションは、入力されたメールアドレスとパスワードをデータベースに保存されている情報と照合します。一般的に、データベースに保存されるパスワードは、「平文」ではなく「ハッシュ化」された状態です。
パスワードの平文
平文とは、暗号化や変換を施されていない、そのままの形式のパスワードのことです。例えば、ユーザーが「password123」というパスワードを設定した場合、この文字列そのものが平文のパスワードになります。
平文のパスワードは読み取り可能なため、もし不正な人物がこれを入手した場合、直接ログイン情報として使用できてしまいます。そのため、セキュリティ上、平文のパスワードをデータベースに保存することは非常に危険です。
パスワードのハッシュ化
ハッシュ化は、パスワードなどを元の値が推測できない一意の文字列に変換します。通常、ユーザーのパスワードは、このハッシュ化された形でデータベースに保存されます。
例えば、ユーザーが「password123」というパスワードを設定した場合、ハッシュ化すると「5f4dcc3b5aa765d61d8327deb882cf99」のような文字列に変換されます。この変換後の文字列をハッシュ値と呼びます。
メモアプリケーションでは、以下のように「000000」というパスワードを設定した場合、データベースでは、変換されたハッシュ値がカラムに保存されます。
この変換は一方通行で、ハッシュ化されたパスワードから元のパスワードを復元することは実質的に不可能です。認証時には、ユーザーが入力したパスワードをハッシュ化し、保存されているハッシュ値と比較することで、パスワードが正しいかどうかを確認します。
ハッシュ化はセキュリティを強化するために非常に重要で、万が一データベースが侵害されても、ハッシュ化されたパスワードからは元の平文を特定することが困難なため、ユーザーのパスワードはより安全に保護されます。
3.アクセスの承認または拒否
ユーザー名とパスワードが一致した場合、システムはユーザーを正しい人物であると判断し、アクセスを許可します。一致しない場合、アクセスは拒否され、ユーザーに対してエラーメッセージが表示されます。
ログイン失敗時に表示されるメッセージの例
ログイン画面では、このメッセージのようにメールアドレスとパスワードのどちらが誤っているかを明かさないことがセキュリティ上重要であり、悪意ある第三者が情報を得ることを防ぎます。
4.セッションの確立
セッションとは、サービス利用時にユーザーの情報を一時的に記憶しておく仕組みです。簡単に言うと、ユーザーがログインしてからログアウトするまでの間、Webアプリケーションがユーザーの情報を「覚えている」状態のことです。
アクセスが承認されると、システムはユーザーのセッションを開始します。これにより、ユーザーはログアウトするまで、または一定時間が経過するまで、再度ログインすることなくサービスを利用できるようになります。
5.ログアウトプロセス
ユーザーがログアウトを選択するとセッションは終了し、再度ログインしない限りサービスを利用することができません。
ログアウト時のイメージ画面
ログイン認証の基本的なステップから分かるように、ログイン機能の実装には、セキュリティなどいろいろなことを考えなければなりません。しかし、Railsではdeviseのような認証ライブラリを利用して、簡単にログイン機能を作ることができます。
deviseによる認証機能の基本実装
deviseはRailsで利用できるgemの一つで、ログイン認証機能を手軽に追加できます。
このgemを追加し、提供されるコマンドを使って設定を進めると、ユーザーの新規登録(アカウント登録)、ログイン、ログアウトの機能がアプリケーションに簡単に組み込めます。
deviseを使用すると、デフォルトで利用可能ないくつかの画面が提供されます。(リセットCSSの影響で入力欄の枠が消えています。)
デフォルトのユーザー新規(アカウント)登録画面
これらの画面の見た目をカスタマイズする方法は後ほど説明しますが、まずはdeviseが提供する基本的な機能を理解し、活用することから始めましょう。
1.deviseの導入と基本設定
まずはdeviseのGemを導入し、初期設定を行いましょう。
deviseのインストール
Gemfileに以下の2行を追加し、ターミナルでアプリケーションのルートディレクトリに移動して、bundle install
コマンドを実行し、deviseをインストールしましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.7.5'
gem 'rails', '~> 6.1.3', '>= 6.1.3.1'
gem 'mysql2', '0.5.3'
gem 'puma', '5.5.2'
gem 'sass-rails', '6.0.0'
gem 'webpacker', '5.4.3'
gem 'turbolinks', '5.2.1'
gem 'jbuilder', '2.11.5'
gem 'bootsnap', '1.10.1', require: false
gem 'net-http'
gem 'devise', '4.9.3'
gem 'devise-i18n', '1.12.0'
group :development, :test do
gem 'byebug', '11.1.3'
gem 'pry-rails', '0.3.9'
end
group :development do
gem 'web-console', '4.2.0'
gem 'rack-mini-profiler', '2.3.3'
gem 'listen', '3.7.1'
gem 'spring', '3.1.1'
end
group :test do
gem 'capybara', '3.36.0'
gem 'selenium-webdriver', '4.1.0'
gem 'webdrivers', '5.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Gemfileに追加した後は、ターミナルで以下を実行しましょう。
1
bundle install
サーバーを起動している場合は、再起動しておきましょう。
インストールが完了したら、rails g devise:install
コマンドを実行してdeviseの初期設定を行います。このコマンドを実行しないと、devise関連の機能は使用できないので注意しましょう。
ターミナルで以下を実行しましょう。
1
rails g devise:install
このコマンドを実行すると、deviseの設定ファイル(config/initializers/devise.rb)が生成され、デフォルト設定が自動的に記述されます。
コマンド実行時に表示されるガイドラインには、手動で行うべき設定に関する情報が含まれています。
今回は特定の機能の実装を行いませんので、1番目の設定はスキップします。次に、2,3番目の指示に従って、必要な設定を行います。4番目の指示に関しては、後ほど別セクションで取り扱います。
- 2.
config/routes.rb
にてroot_url
を定義する。 - 3.
app/views/layouts/application.html.erb
にフラッシュメッセージ表示用のコードを追記する。 - 4.
rails g devise:views
コマンドを実行し、deviseのビューをアプリケーションにコピーしてカスタマイズ可能にする。
初期設定
config/routes.rb
ファイルでroot_urlを定義するとは、アプリケーションにアクセスした際に最初に表示されるページを指定することです。root_urlはアプリケーションのルートページ、つまり最初に表示されるページのURLを指します。
例えば、Pikawakaのアプリケーションで「https://pikawaka.com
」とアクセスしたときに表示されるトップページがroot_urlに相当します。このURLには特定のパスが含まれておらず、アプリケーションの入り口となっています。
Pikawakaのアプリケーションのルートページ
具体的にはconfig/routes.rb
ファイルにroot
メソッドを使って、アクセス時に最初に表示したいページに対応するコントローラとアクションを設定します。ルーティングはファイル内で上から順に解釈されるため、root定義はファイルの最上部に記述するのが一般的です。
1
2
3
4
Rails.application.routes.draw do
root to: 'コントローラ名#アクション名'
# ...他のルーティング設定...
end
ルート設定がない場合、デフォルトのRails起動画面がルートページとして表示されます。
現在、ルート設定がされていないため、以下のデフォルト起動画面が表示されるはずです。サーバーを起動して確認してみましょう。
ルート設定がない場合、デフォルト起動画面が表示される
今回は、このルートのURLにアクセスした際に、メモ一覧画面を表示できるようにルートを設定していきます。
メモ一覧画面は、memos
コントローラのindex
アクションで表示されるため、これをアプリケーションのルートページに設定します。
config/routes.rb
ファイルを編集して、以下のようにルートを設定しましょう。
1
2
3
4
Rails.application.routes.draw do
root 'memos#index'
resources :memos
end
上記の設定後、先ほど表示したルートページを再読み込みすると、以下のようにアプリケーションにアクセスした際に最初にメモ一覧画面が表示されるようになります。
config/routes.rb
でルート定義した後、root_path
というルーティングヘルパーメソッドが使えるようになります。
root_path
はアプリケーションのルートページ、つまりmemos#index
アクションに対応するビューへのパスを返します。これにより、アプリケーション内でmemos_path
を使用していた箇所をroot_path
に置き換えることが可能です。
まずは、memosコントローラのmemos_path
をroot_path
に置き換えましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class MemosController < ApplicationController
before_action :set_memo, only: [:show, :edit, :update, :destroy]
def index
# 学習メモの一覧を取得する処理
@memos = Memo.all
end
def show
# 特定の学習メモの詳細情報を取得する処理
end
def new
# 新しい学習メモの作成フォームを表示する処理
@memo = Memo.new
end
def create
# 新しい学習メモを登録する処理
@memo = Memo.new(memo_params)
if @memo.save
redirect_to root_path, notice: '学習メモが投稿されました。'
else
render :new
end
end
def edit
# 既存の学習メモを編集するためのフォームを表示する処理
end
def update
# 既存の学習メモ情報を更新する処理
if @memo.update(memo_params)
redirect_to root_path, notice: '学習メモが更新されました。'
else
render :edit
end
end
def destroy
# 特定の学習メモを削除する処理
@memo.destroy
redirect_to root_path, notice: '学習メモが削除されました。'
end
private
def memo_params
params.require(:memo).permit(:title, :content)
end
def set_memo
@memo = Memo.find(params[:id])
end
end
次に、ヘッダーの「学習メモアプリ」のリンクをmemos_path
をroot_path
に置き換えましょう。
1
2
3
4
5
6
7
<header class="header">
<h1 class="header__title"><%= link_to '学習メモアプリ', root_path %></h1>
<div class="header__nav">
<%= link_to '新規作成', new_memo_path %>
</div>
</header>
上記の変更により、これまでリダイレクトやヘッダー内の「学習メモアプリ」リンクで/memos
にアクセスしていた箇所が、ルートページへのアクセスに変更されます。ルートページにアクセスすると、メモ一覧画面が表示されるようになります。
学習メモアプリケーションでは現在、notice
を使用してflashメッセージを表示していますが、deviseを導入すると、alert
も使用されるようになります。
notice
- 成功した操作や情報を伝える際に使われるalert
- 警告やエラーを伝える際に使われる
_flash-message.html.erb
に以下のコードを追加しましょう。
1
2
3
4
5
6
7
<% if notice.present? %>
<div class="notice"><%= notice %></div>
<% end %>
<% if alert.present? %>
<div class="alert"><%= alert %></div>
<% end %>
また、alert
メッセージのスタイルをlayout.scss
に追加しましょう。既に定義されている.notice
のスタイルの後に、以下の.alert
のスタイルを追記します。
1
2
3
4
5
6
7
8
9
.alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
これで、ガイドラインの2番目(root_urlの定義)と3番目(flashメッセージの設定)が完了しました。
deviseが提供する標準機能
deviseは、Railsアプリケーションに認証機能を簡単に追加できるように、以下のデフォルト機能を提供します。これらの機能はgem内に組み込まれており、アプリケーションのディレクトリ内で直接見ることはできません。
- デフォルトのビューファイル: サインアップ、ログイン、アカウント編集など
- コントローラ機能: ユーザーのサインアップ、サインイン、サインアウトなどの認証プロセスを管理するコントローラ
これらのデフォルト機能をそのまま使用する場合、ビューやコントローラの追加設定は不要です。そのため、この後に説明されるコマンドを実行するだけで、サインアップ、ログイン、ログアウトの基本機能がすぐに利用できるようになります。
2.Userモデルとusersテーブルの設定
Userモデルにdeviseを適用(rails g devise user)
rails g devise user
コマンドは、Userモデルにdeviseの認証機能を組み込むために使用します。このコマンドを実行することで、以下の作業が自動的に行われます。
- Userモデルファイルの作成。
- deviseが要求するカラムを含むデータベースマイグレーションファイルの作成。
- devise用のルーティング設定の追加。
手作業でこれらの設定を行う手間が省け、効率的に認証機能をアプリケーションに導入できます。
以下のコマンドをターミナルで実行しましょう。
1
rails g devise user
実行後、Userモデルファイル、マイグレーションファイルが生成され、ルーティングに設定が追加されることが確認できます。
主要なファイルや設定をさっと見てみましょう!
最初にapp/models/user.rb
ファイルを見てみると、ファイル作成後、特定のdevise関連の設定が追加されていることがわかります。
以下の箇所で設定されており、データベース認証、ユーザー登録、パスワードの再設定、ログイン状態の記憶、バリデーション機能などを提供します。この設定があることで、コントローラーで別途これらの機能を実装する必要がなくなります。
生成されたマイグレーションファイルでは、deviseを使ってUser
モデルのためのusers
テーブルをデータベースに作成する定義が記述されています。
db/migrate
ディレクトリ内にあるこのファイルを開いてみましょう。ファイル内には多くの項目が記載されていますが、ここではいくつかの重要な部分だけを説明します。コメントアウトされた部分は現時点では重要ではないので、無視して構いません。
特に重要なのは、ユーザー識別とセキュリティに関わる以下の2つです。
カラム名 | 説明 |
---|---|
ユーザー識別のためのメールアドレスです。null: false はこのカラムが空であってはならず、default: "" はデフォルト値が空文字列であることを示しています。 |
|
encrypted_password | ユーザーのパスワードを暗号化して保存するカラムです。これもnull: false とdefault: "" の制約を持っています。 |
これらのカラムはユーザーがログインするために直接使用され、アプリケーションのセキュリティにおいて重要な役割を果たします。
最後に、config/routes.rb
ファイルでdevise_for :users
が自動で追加されているのを確認しましょう。
devise_for :users
は、Userモデルに関連する一連の認証機能に対するルーティングを自動的に設定するための記述です。
この記述により、サインアップ、ログイン、ログアウト、パスワードリセットなど、ログイン認証に必要なアクションへのルーティングが自動的に追加されます。後ほど、具体的なルーティングの内容を確認します。
自動追加されたdevise_for :users
は、ルート設定の上部に配置されていますが、通常はroot 'memos#index'
をファイルの最上部に置くことが推奨されます。
以下のようにファイルを編集しましょう。
1
2
3
4
5
Rails.application.routes.draw do
root 'memos#index'
devise_for :users
resources :memos
end
以上で、rails g devise user
コマンド実行で自動生成されたファイルと設定の確認が終わりました。このコマンドの実行によって、次の作業が自動で行われました。
- Userモデルファイルの作成。
- deviseが要求するカラムを含むデータベースマイグレーションファイルの作成。
- devise用のルーティング設定の追加。
これでdeviseによる基本的なログイン認証機能のためのファイルと設定の準備が整いました!
usersテーブルの作成
自動生成されたマイグレーションファイルを適用して、users
テーブルをデータベースに作成しましょう。
マイグレーションファイルで定義したテーブルをデータベースに適用させるためには、railsコマンドでマイグレーションファイルを実行させる必要があります。
アプリケーションのルートディレクトリに移動し、以下のコマンドを実行しましょう。
1
rails db:migrate
phpMyAdminを使用して、users
テーブルが正しく作成されているか確認しましょう。
以下のコマンドを順番に実行し、PHPビルトインサーバーを起動してください。
1
cd ~/environment/memo_app/public/phpMyAdmin-5.1.1-all-languages
1
php -S 127.0.0.1:8081
Cloud9のIDE上部にある「Preview」>「Preview Running Application」をクリックし、プレビューウィンドウのBrowserの右側にあるマークをクリックしてください。Webブラウザの別タブでプレビューが開きますので、URLの末尾に:8081
を追記し、Enterキーまたはreturnキーを押して検索してください。
phpMyAdminにログインしましょう。ユーザー名の欄に「root」と半角英字で入力し、実行ボタンをクリックしてください。
ログイン後は、memo_app_development
データベースを選択し、users
テーブルが表示されていることを確認しましょう。
usersテーブルの構造を見ると、rails g devise user
で自動生成されたカラムが含まれていることが確認できます。
特に、email
とencrypted_password
カラムはユーザー認証に直接関係しています。email
カラムはユーザー識別に使用され、encrypted_password
カラムにはユーザーのパスワードが暗号化されて保存されます。
3.deviseルーティングの詳細確認
config/routes.rb
に自動で追加されたdevise_for :users
は、deviseに関連する一連のルーティングが設定されています。この設定により、サインアップ、ログイン、ログアウトなど、ログイン認証に必要なページへのルートが自動的に追加されました。
ここで、deviseがどのようなルーティングを追加したのか具体的な内容を確認しましょう。
1
2
3
4
5
Rails.application.routes.draw do
root 'memos#index'
devise_for :users
resources :memos
end
具体的なルーティングの内容を確認するために、ターミナルでusersに関連するルーティング情報のみを抽出して表示してみましょう。
以下のコマンドを実行してください。
1
rails routes | grep users
上記のコマンド実行により、users
に関連するルーティングのみをフィルタリングして表示できます。これにより、特定のURLがどのコントローラやアクションに割り当てられているかを明確に確認できます。
devise_for :users
の一行で、devise関連の多くのルーティングが自動的に追加されていることがわかります。
このカリキュラムで主に使用するルーティングは、以下に紹介されているものです。
HTTPメソッドとパス | コントローラ#アクション | 説明 |
---|---|---|
GET /users/sign_in | devise/sessions#new | ユーザーログイン画面へのアクセス |
POST /users/sign_in | devise/sessions#create | ユーザーログイン処理 |
DELETE /users/sign_out | devise/sessions#destroy | ユーザーログアウト処理 |
GET /users/sign_up | devise/registrations#new | ユーザー新規登録画面へのアクセス |
GET /users/edit | devise/registrations#edit | ユーザー情報編集画面へのアクセス |
PATCH/PUT /users | devise/registrations#update | ユーザー情報の更新処理 |
POST /users | devise/registrations#create | ユーザー登録処理 |
次に、実際にユーザー登録やログインなどの画面にアクセスしてみましょう。
4.認証機能の画面アクセスと確認
これまでの手順で、デフォルトのサインアップ、ログイン、ユーザー情報編集画面にアクセスできるようになりました。
これらの画面へ実際にアクセスして、各機能を確認しましょう。
ユーザーログイン画面の表示
ユーザーログイン画面は、GET /users/sign_in
でアクセスできます。
ブラウザで、アプリケーションのURL末尾に/users/sign_in
を追加してアクセスし、ログイン画面を表示しましょう。
アクセスすると、以下のようなユーザーログイン画面が表示されます。リセットCSSの影響で入力欄の枠が消えて見づらいかもしれませんが、メールアドレス、パスワードの入力フォームがあります。
ユーザー登録をしていなければログインはできませんが、ログイン画面自体にはアクセス可能です。まだアカウントを作成していないので、ユーザー新規登録を行う必要があります。
ユーザー新規登録の機能確認
ユーザーの新規登録画面は、GET /users/sign_up
でアクセスできます。
ブラウザで、アプリケーションのURLに/users/sign_up
を追加してアクセスするか、ログイン画面の「アカウント登録」のリンクをクリックして、画面を確認してみましょう。
アクセスすると、以下のようにユーザー新規(アカウント)登録画面が表示されます。メールアドレス、パスワード、パスワード再確認の入力フォームがあります。
デフォルトのユーザー新規(アカウント)登録画面
メールアドレス、パスワード、パスワードの再確認を入力フォームに記入し、アカウント登録ボタンをクリックしてユーザーを新規登録してみましょう。
- メールアドレス:
hoge@example.com
- パスワード:
000000
- パスワードの再確認:パスワードを入力
アカウント登録ボタンをクリックすると、以下のようにルートページにリダイレクトされ、flashメッセージのnotice
(成功した操作や情報を伝える際に使われるメッセージ)で「アカウント登録が完了しました。」と表示されます。
deviseが自動でユーザーの新規登録処理や成功後のflashメッセージ表示、リダイレクトを管理してくれるのは便利だね!
deviseが提供するコントローラの詳細はgem内部に隠されているから、どのように動作しているのか少しわかりにくいかもしれないけど、画面上で確認することで、deviseがデフォルトで提供してくれる機能の理解を深められるから、一つ一つ確かめていこう!
ユーザー登録は、POST /users
のリクエストで行われます。deviseにはユーザー登録を管理するコントローラが内蔵されており、これがユーザー登録処理を自動で行います。アプリケーションのディレクトリ内ではこれらのコントローラを直接見ることはできませんが、deviseがユーザー登録のプロセスを裏で処理してくれます。
アカウント登録ボタンをクリックした後のログを確認すると、POST /users
のリクエストが送信され、usersテーブルにユーザー情報が保存されていることがわかります。
phpMyAdminでusersテーブルを開き、先ほど登録したユーザー情報が反映されていることを確認しましょう。
email
カラムにはhoge@example.com
が入っており、登録したメールアドレスが正しく保存されています。また、encrypted_password
カラムには、直接入力したパスワード000000
ではなく、それが暗号化された形で保存されていることが確認できます。
こうやってユーザーのパスワード情報が安全に管理されているんだね!
ログイン状態でのアクセス制御の確認
ユーザー登録完了後は、自動的にログイン状態になります。この状態でユーザー登録画面に再アクセスすると、どうなるか確認しましょう。
Webブラウザで、アプリケーションのURLに/users/sign_up
を追加してアクセスし、画面を確認してみましょう。
アクセスすると、ルートページにリダイレクトされ、以下の画像のようにflashメッセージのalert
(警告やエラーを伝える際に使われるメッセージ)で「すでにログインしています。」と表示されます。
このように既にログインしているユーザーが新規登録画面にアクセスしようとすると、特定のページにリダイレクトされて、アクセスできないようになっています。この挙動はdeviseがデフォルトで提供する機能の一つです。
ログイン状態で、ユーザーログイン画面に/users/sign_in
でアクセスした場合も同様にルートページにリダイレクトされ「すでにログインしています。」と表示されるはずです。ログイン画面にもアクセスして確かめてみましょう。
ユーザー情報編集と更新の確認
ユーザー情報の編集画面は、GET /users/edit
のリクエストで表示されます。ブラウザで、アプリケーションのURLに/users/edit
を追加してアクセスし、画面を確認してみましょう。
アクセスすると、以下のようにユーザー情報の編集画面が表示されるはずです。
ユーザー情報の編集時は、メモ編集と異なり特定のIDをURLに含める必要はありません。
メモ編集では、以下のように特定のメモのID(例: /memos/1/edit
)をURLパラメータとして使用しますが、ユーザー情報編集ではログイン状態のユーザー自身が編集対象となるため、IDは不要です。
deviseは自動で現在ログインしているユーザーを認識するため、URLにユーザーIDを含める必要はなく、ユーザー情報編集画面にアクセスする際は、シンプルに/users/edit
というURLを使用します。
メールアドレスをhoge@example.com
からfuga@example.com
に変更し、現在のパスワードに000000
を入力し、「更新」ボタンをクリックしましょう。
更新ボタンをクリックすると、以下のようにルートページにリダイレクトされ、flashメッセージのnoticeで「アカウント情報を変更しました。」と表示されます。
ユーザー情報の更新は、PUT /users
のリクエストで行われます。
更新ボタンをクリックした後のログを確認すると、PUT /users
のリクエストが送信され、usersテーブルにユーザー情報が更新されていることがわかります。
ログアウト機能の検証
ユーザーのログアウト処理を行うには、DELETE /users/sign_out
へのリクエストが必要です。アプリケーションのヘッダーにログアウトのリンクを追加します。
ユーザーのログアウト処理の名前付きルートは、destroy_user_session
です。
この名前付きルートを使用して、link_to
メソッドにmethod: :delete
オプションを指定し、*_path
メソッドでログアウトリンクを設置できます。
_header.html.erb
ファイルを開いて、以下のハイライト箇所に変更しましょう。
1
2
3
4
5
6
7
8
9
10
<header class="header">
<h1 class="header__title"><%= link_to '学習メモアプリ', root_path %></h1>
<div class="header__nav">
<ul>
<li><%= link_to '新規作成', new_memo_path %></li>
<li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete %></li>
</ul>
</div>
</header>
このコードにより、ヘッダーにログアウトのリンクが表示され、ユーザーはログアウトできるようになります。
layout.scss
ファイルを開いて、以下のハイライト箇所に変更しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.header {
align-items: center;
background-color: #fff;
box-shadow: 0 0 15px #e2e6ef;
display: flex;
justify-content: space-between;
padding: 16px 40px;
&__title {
font-size: 28px;
a {
color: #348560;
}
}
&__nav {
ul {
display: flex;
}
li {
padding-right: 10px;
}
}
}
アプリケーションのルートページにアクセスすると、以下のようにログアウトリンクが追加されています。
このリンクをクリックして、ログイアウトしてみましょう。
ログアウトのリンクをクリックすると、以下のようにルートページにリダイレクトされ、flashメッセージのnotice
で「ログアウトしました。」と表示されます。
ログアウト後は、再びアプリケーションを利用するためにログインが必要になります。
ユーザーログイン画面へのアクセスは、GET /users/sign_in
から行えます。
登録済みのメールアドレス、パスワードを入力フォームに記入し、ログインボタンをクリックしてログインを試みてみましょう。
- メールアドレス:
fuga@example.com
- パスワード:
000000
ログインボタンをクリックすると、ルートページへリダイレクトされ、flashメッセージに「ログインしました。」と表示されます。
これで、ユーザー登録からログアウトまでの一連の流れを確認することができましたね!deviseを使えば、こんなに多様な認証機能を簡単に実装できるのがわかりました!
5.ログイン状態でのリンク表示変更
現在、ブラウザのURLを直接操作して各画面にアクセスしていますが、アプリケーションの利便性を高めるためにヘッダーにログアウト以外にも、ログイン、ユーザー情報編集、ユーザー新規登録のリンクを追加しましょう。
各画面の名前付きルートは、以下の通りです。
この名前付きルートを使用して、リンクを追加していきます。
以下のハイライト箇所のように_header.html.erb
ファイルを編集し、必要なリンクをヘッダーに追加しましょう。ユーザー情報編集画面は「マイページ編集」、ユーザー新規登録画面は「アカウント登録」というテキストリンクにしてます。
1
2
3
4
5
6
7
8
9
10
11
12
13
<header class="header">
<h1 class="header__title"><%= link_to '学習メモアプリ', root_path %></h1>
<div class="header__nav">
<ul>
<li><%= link_to '新規作成', new_memo_path %></li>
<li><%= link_to 'マイページ編集', edit_user_registration_path %></li>
<li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete %></li>
<li><%= link_to 'アカウント登録', new_user_registration_path %></li>
<li><%= link_to 'ログイン', new_user_session_path %></li>
</ul>
</div>
</header>
コード追加後、以下のようにヘッダーに「マイページ編集」、「アカウント登録」、「ログイン」のリンクが追加されていることをブラウザで確認できます。
これでユーザーが必要な画面に簡単にナビゲートできるようになったね!
でも、まだ改善できるところがあるよ。ログイン状態によって表示するリンクを変えて、ユーザー体験をさらに向上させよう!
現在、すべてのリンクが常に表示されていますが、ユーザーのログイン状態に応じて表示するリンクを変えるべきです。
たとえば、deviseによるアクセス制限で、ログイン済みのユーザーがログイン画面にアクセスすることはできません。そのため、ログイン済みの状態で「ログイン」リンクが表示されるのは不自然です。
同様に、未ログインの状態で「ログアウト」リンクが表示されるのも適切ではありません。
ユーザーのログイン状態に基づいてリンクを動的に表示または非表示にすることが重要です。これを実現するには、user_signed_in?
メソッドを利用します。
user_signed_in?メソッド
user_signed_in?
メソッドは、ユーザーが現在ログインしているかどうかをチェックするためのdeviseのヘルパーメソッドです。ユーザーがログインしている場合はtrue
を、ログインしていない場合はfalse
を返します。
このメソッドをif
と組み合わせることにより、ユーザーのログイン状態に基づいて、表示するリンクを動的に切り替えることができます。
1
2
3
4
5
<% if user_signed_in? %>
<%# ユーザーがログインしている時に表示されるコンテンツ %>
<% else %>
<%# ユーザーがログインしていない時に表示されるコンテンツ %>
<% end %>
ユーザーがログインしている時は「新規作成」、「マイページ編集」、「ログアウト」のリンクを表示し、ログインしていない時は「アカウント登録」、「ログイン」のリンクを表示するようにします。
_header.html.erb
ファイルを以下のように編集して、ユーザーのログイン状態に応じて表示するリンクを変更しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<header class="header">
<h1 class="header__title"><%= link_to '学習メモアプリ', root_path %></h1>
<div class="header__nav">
<ul>
<% if user_signed_in? %>
<li><%= link_to '新規作成', new_memo_path %></li>
<li><%= link_to 'マイページ編集', edit_user_registration_path %></li>
<li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to 'アカウント登録', new_user_registration_path %></li>
<li><%= link_to 'ログイン', new_user_session_path %></li>
<% end %>
</ul>
</div>
</header>
ブラウザでアプリケーションにアクセスすると、現在ログイン状態にあるため、ヘッダーに「新規作成」、「マイページ編集」、「ログアウト」といったリンクが表示されていることを確認できます。
ログアウト後、ユーザーが未ログイン状態であることを確認します。未ログイン時には、ヘッダーに「アカウント登録」と「ログイン」のリンクのみ表示されるはずです。以下の手順でログアウトを行い、確認してみましょう。
ログアウトのリンクをクリックします。
ログアウトを実行すると、ヘッダーのリンクが「アカウント登録」と「ログイン」だけに変わっていることが確認できます。
これでdeviseの基本的な機能を使ったログイン認証の実装は完了だよ。ただし、現在のビューはまだデフォルトのままだから、アプリケーションの外観に合わせてカスタマイズが必要だよ。
なるほど、デフォルトだけでも十分強力だけど、やっぱり見た目も大事にしたいよね!
次はビューのカスタマイズを行って、アプリケーションの見た目を整えていこう。さらに、アプリケーションに必要な機能を追加するためのカスタマイズ方法も学んでいこう。
deviseビューファイルのカスタマイズ
deviseのデフォルト機能をカスタマイズして、アプリケーションの要件に応じたカスタマイズを行います。このセクションでは、deviseのビューファイルをカスタマイズするための手順を学びます。具体的には、ビューファイルのコピー方法、アプリケーションのデザインに合わせた見た目へのカスタマイズ、そしてエラーメッセージを表示できるようにします。
ビューファイルのカスタマイズ手順
deviseはユーザー登録、ログイン、ログアウトなどの機能に関連するビューファイルをデフォルトで提供しています。しかし、これらのファイルはgem内にあるため、直接編集することはできません。カスタマイズを行うには、deviseのビューファイルをアプリケーションのapp/views
ディレクトリにコピーする必要があります。
deviseビューファイルのコピー
deviseのビューファイルをアプリケーション内で編集可能にするためには、rails g devise:views
コマンドを実行し、これらのファイルをプロジェクト内にコピーします。
1
rails g devise:views
このコマンドを実行すると、app/views
ディレクトリにdevise
フォルダが新たに作成され、deviseに関連するビューファイル群がこのフォルダ内に展開されます。
これにより、アプリケーションのUIやデザインに合わせて、ビューファイルを自由にカスタマイズできるようになります。
deviseビューファイルとコントローラーの対応関係
deviseのコントローラーとアクションを確認することで、app/views
ディレクトリにコピーされたビューファイルがどの画面に対応しているかを理解できます。
例えば、ログイン機能はdevise/sessions#new
アクションによって制御されます。このアクションに対応するビューファイルは、以下のようにapp/views/
ディレクトリ内のdevise/sessions/new.html.erb
に配置されています。
このように、deviseのコントローラーとアクションを確認することで、コピーされたビューファイルがどの画面に対応しているのかがはっきりとします。
この確認方法を知っていれば、どのビューファイルを編集すればデフォルトの表示をカスタマイズできるかがわかるようになるね!
ビューファイルの詳細カスタマイズ
ユーザー新規登録、ユーザー情報編集、ログイン画面に対し、見た目の改善とエラーメッセージの表示機能を追加します。
以下のビューファイルを編集していきます。
- ユーザー新規登録画面:
app/views/devise/registrations/new.html.erb
- ユーザー情報編集画面:
app/views/devise/registrations/edit.html.erb
- ログイン画面:
app/views/devise/sessions/new.html.erb
見た目のカスタマイズをしよう
以下のように、ユーザー新規登録(アカウント登録)画面をアプリケーションのデザインに合わせて見た目をカスタマイズしましょう。
さらに、ログイン画面やユーザー情報編集画面において、デフォルトで提供されている機能(ログイン情報の記憶、パスワードのリセットなど)の中で、このカリキュラムで使用しない項目をビューファイルから削除します。
まずは、ビューファイルを開きましょう。
- ユーザー新規登録画面:
app/views/devise/registrations/new.html.erb
- ユーザー情報編集画面:
app/views/devise/registrations/edit.html.erb
- ログイン画面:
app/views/devise/sessions/new.html.erb
deviseのビューファイルはデフォルトでform_for
を使用していますが、Rails 5.1以降ではform_with
が推奨されています。より現代的なフォームヘルパーform_with
に書き換え、さらに以前に作成したCSSクラスを適用して見た目を整えましょう。
各ビューファイルを以下のコードに書き換えましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h2>ログイン</h2>
<%= form_with model: resource, url: session_path(resource_name), local: true, class: 'form' do |f| %>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード(必須):' %>
<%= f.password_field :password, autocomplete: 'current-password', class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit 'ログインする', class: 'form__button' %>
</div>
<% end %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<h2>アカウント登録</h2>
<%= form_with model: resource, as: resource_name, url: registration_path(resource_name), local: true, class: 'form' do |f| %>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード(必須):' %>
<%= f.password_field :password, autocomplete: "new-password", placeholder: '6文字以上で入力してください', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password_confirmation, 'パスワード確認(必須):' %>
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit '登録する', class: 'form__button' %>
</div>
<% end %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<h2>マイページ編集</h2>
<%= form_with model: resource, url: registration_path(resource_name), local: true, method: :put, class: 'form' do |f| %>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード:' %>
<%= f.password_field :password, autocomplete: 'new-password', placeholder: '空欄のままなら変更しません', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password_confirmation, 'パスワード確認:' %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :current_password, '現在のパスワード(必須):' %>
<%= f.password_field :current_password, autocomplete: 'current-password', placeholder: '変更を反映するには現在のパスワードを入力してください', class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit '更新する', class: 'form__button' %>
</div>
<% end %>
ユーザー新規登録画面、ユーザー情報編集画面のビューファイルではplaceholder
属性を使用しています。この属性により、フォームの入力欄に薄く表示されるテキスト(ユーザー新規登録のパスワードでは'6文字以上で入力してください'
)を指定できます。これは、フォームのラベルだけでは伝えきれない簡潔な指示や情報をユーザーに提供するために利用されます。
一方で、ログイン画面のビューファイルではパスワードの長さなどに関する情報を提示しないことがセキュリティ上望ましいため、placeholder
を使って具体的な指示をしていません。
まず、ユーザー新規登録画面(アカウント登録)にアクセスし、スタイルが適用されたフォームが表示されることを確認します。ログアウト状態から始めてください。
カスタマイズ後のユーザー新規登録画面
次に、ログイン画面にアクセスし、改善されたフォームが表示されることを確認します。
カスタマイズ後のログイン画面
メールアドレスにfuga@example.com
、パスワードに000000
を入力し、「ログインする」ボタンをクリックしてログインしましょう。
ログイン完了後に表示される画面
ログイン後、「マイページ編集」へ進み、ユーザー情報編集画面のフォームが変更されたことを確認します。
カスタマイズ後のユーザー情報編集画面
メールアドレスを変更してみましょう。
メールアドレスをfuga@example.com
からhoge@example.com
に変更し、現在のパスワード000000
を入力後、「更新する」をクリックしましょう。
正常に更新されると、以下の画面が表示されます。
最後に、再度「マイページ編集」にアクセスし、メールアドレスが正しく更新されたかをチェックしましょう。
これで、ユーザー新規登録、ユーザー情報編集、ログイン画面の見た目をアプリのデザインに合わせてカスタマイズできましたね。
エラーメッセージを表示しよう
デフォルトではdeviseビューには独自のエラーメッセージが設定されています。
カリキュラムでは、deviseのエラーメッセージを使わず、メモ機能の実装で使ったエラーメッセージスタイルを「ユーザー新規登録」と「ユーザー情報編集画面」に適用します。この変更により、統一感のあるエラーメッセージをユーザーに提示できるようになります。
適用するエラーメッセージの完成イメージ
ログイン画面では、エラーメッセージの追加を行いません。
なぜなら、以下のようにdeviseのデフォルトのflashメッセージ機能がログイン失敗時に「メールアドレスまたはパスワードが違います。」という通知を表示するため、追加のエラーメッセージは不要だからです。
ログイン失敗時に表示されるdeviseのデフォルトのflashメッセージ
ログイン画面では、このメッセージのようにメールアドレスとパスワードのどちらが誤っているかを明かさないことがセキュリティ上重要であり、悪意ある第三者が情報を得ることを防ぎます。deviseはこのセキュリティ対策をデフォルトで提供します。
- ユーザー新規登録画面:
app/views/devise/registrations/new.html.erb
- ユーザー情報編集画面:
app/views/devise/registrations/edit.html.erb
ユーザー新規登録とユーザー情報編集のビューファイルに、以下のコードを追加します。
1
2
3
4
5
6
7
8
9
10
<% if resource.errors.any? %>
<div class="form__errors">
<h3><%= resource.errors.count %>件のエラーが発生しました</h3>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
このコードは、form_with
内に追加します。以下のマイページ編集だけではなく、ユーザー新規登録のビューファイルに追加することを忘れないでください。
エラーメッセージを追加した後、マイページ編集画面にアクセスして、メールアドレスを空欄にし、現在のパスワードも入力しないで「更新する」ボタンをクリックしてみましょう。
メールアドレスが空であることと、現在のパスワードが入力されていないことによるエラーメッセージが表示されるはずです。
この段階では、エラーメッセージが英語で表示されるため、「Translation missing」というメッセージが表示されている部分があります。次のステップで日本語化を行います。
エラーメッセージを日本語化するために、翻訳ファイルを編集します。
config/locales/ja.yml
ファイルを開き、以下の翻訳内容を追加しましょう。編集後は、変更を反映させるためにRailsサーバーの再起動を行いましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
ja:
errors:
format: "%{attribute}%{message}"
activerecord:
attributes:
memo:
title: "タイトル"
content: "内容"
user:
email: "メールアドレス"
password: "パスワード"
password_confirmation: "パスワード確認"
errors:
models:
memo:
attributes:
title:
blank: "を入力してください。"
too_long: "は30文字以内で入力してください。"
content:
blank: "を入力してください。"
user:
attributes:
email:
blank: "を入力してください。"
password:
blank: "を入力してください。"
taken: "は既に使用されています。"
blank: "が入力されていません。"
too_short: "は%{count}文字以上に設定して下さい。"
too_long: "は%{count}文字以下に設定して下さい。"
invalid: "は有効でありません。"
confirmation: "が内容とあっていません。"
password_confirmation:
blank: "を入力してください。"
taken: "は既に使用されています。"
blank: "が入力されていません。"
too_short: "は%{count}文字以上に設定して下さい。"
too_long: "は%{count}文字以下に設定して下さい。"
invalid: "は有効でありません。"
confirmation: "がパスワードと一致しません。"
current_password:
blank: "を入力してください。"
Railsサーバーの再起動後、再度「マイページ編集」にアクセスし、メールアドレスを空欄にし、現在のパスワードも入力しないで「更新する」ボタンをクリックしてみましょう。
すると、以下のようにエラーメッセージが日本語で表示されることが確認できます。
エラーメッセージを部分テンプレートで管理しよう
エラーメッセージのコードは複数のビューファイルで共通していますが、それぞれ少し違うインスタンスを使っています。
例えば、先ほど追加したエラーメッセージでは、resource
です。
メモのフォームでは、以下のようにmemo
です。
このセクションでエラーメッセージの部分を部分テンプレート化し、インスタンス変数をinstance
として汎用化します。部分テンプレートを呼び出す際には、render
メソッドを使用して適切なインスタンスをinstance
として渡すように変更していきます。
1
<%= render 'shared/validation_error', instance: インスタンス名 %>
app/views/shared/
ディレクトリ内に_validation_error.html.erb
の部分テンプレートファイルを作成しましょう。
_validation_error.html.erb
には、以下のように記述しましょう。
1
2
3
4
5
6
7
8
9
10
<% if instance.errors.any? %>
<div class="form__errors">
<h3><%= instance.errors.count %>件のエラーが発生しました</h3>
<ul>
<% instance.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
この部分テンプレートを使用するときは、render
メソッドを使って、適切なインスタンスをinstance
として渡します。
例えば、ユーザー登録フォームであればinstance: resource
とします。
1
<%= render 'shared/validation_error', instance: インスタンス名 %>
1
<%= render 'shared/validation_error', instance: resource %>
メモ機能のフォーム、ユーザー新規登録画面、およびユーザー情報編集画面にて、エラーメッセージを部分テンプレートから呼び出して表示するように変更しましょう。
メモ機能ではmemo
インスタンス、ユーザー新規登録画面とユーザー情報編集画面ではresource
インスタンスをそれぞれ渡します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%= form_with model: memo, local: true, class: 'form' do |f| %>
<%= render 'shared/validation_error', instance: memo %>
<div class="form__group">
<%= f.label :title, 'タイトル(必須):' %>
<%= f.text_field :title, class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :content, '本文(必須):' %>
<%= f.text_area :content, class: 'form__textarea' %>
</div>
<div class="form__submit">
<%= f.submit button_text, class: 'form__button' %>
</div>
<% end %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<h2>マイページ編集</h2>
<%= form_with model: resource, url: registration_path(resource_name), local: true, method: :put, class: 'form' do |f| %>
<%= render 'shared/validation_error', instance: resource %>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード:' %>
<%= f.password_field :password, autocomplete: 'new-password', placeholder: '空欄のままなら変更しません', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password_confirmation, 'パスワード確認:' %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :current_password, '現在のパスワード(必須):' %>
<%= f.password_field :current_password, autocomplete: 'current-password', placeholder: '変更を反映するには現在のパスワードを入力してください', class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit '更新する', class: 'form__button' %>
</div>
<% end %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<h2>アカウント登録</h2>
<%= form_with model: resource, as: resource_name, url: registration_path(resource_name), local: true, class: 'form' do |f| %>
<%= render 'shared/validation_error', instance: resource %>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード(必須):' %>
<%= f.password_field :password, autocomplete: "new-password", placeholder: '6文字以上で入力してください', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password_confirmation, 'パスワード確認(必須):' %>
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit '登録する', class: 'form__button' %>
</div>
<% end %>
部分テンプレートを適用後、エラーメッセージが正しく表示されることを確認します。
まずは「マイページ編集」にアクセスして、メールアドレスを空欄にし、現在のパスワードを入力しない状態で「更新する」をクリックして、以下のようにエラーメッセージが表示されることを確認しましょう。
同様に、メモの編集機能でもエラーが適切に表示されるかを確認しましょう。
以下のようにメモ編集画面で必要な情報を空欄にして更新を試み、エラーメッセージが正しく表示されることを確認してください。
これでビューファイルの見た目の調整やエラーメッセージのカスタマイズが完了しましたね。部分テンプレートを使ってエラーメッセージを一元管理することができ、メンテナンスもしやすくなりました。
新しい属性の追加とカスタマイズ
このセクションでは、deviseを使ったUserモデルのカスタマイズについて学びます。具体的には、ユーザーネームの新しい属性をUserモデルに追加し、ユーザーが新規登録やアカウント情報を編集する際に、この新しい属性をフォームに含める方法を扱います。
ユーザーが新規登録する際にユーザーネームも一緒に登録できるように変更します。
ユーザー新規登録の完成イメージ
さらに、学習メモに投稿者の情報を追加し、メモ一覧画面で各メモがどのユーザーによって投稿されたかを表示させます。これにより、学習メモ一覧画面で「ユーザーネーム」として表示されている部分を、実際の投稿者のユーザーネームで表示するように変更します。
現段階の学習メモ一覧画面
この部分は、実装後に以下の画像のように、実際の投稿者のユーザーネームが表示されるように変更されます。
実装後の学習メモ一覧画面
この実装を行うためには、Userモデルに新しい属性の追加と、学習メモにユーザーネームを表示する機能の実装、この2つのステップが必要です。まずは新しい属性を追加する手順を確認しましょう。
新しい属性を追加する手順
Userモデルにdeviseが適用されているため、通常のモデルに新しい属性を追加する際と手順が異なります。deviseのデフォルト属性(メールアドレスやパスワードなど)は特別な設定なしで扱うことができますが、新しく追加する属性の場合、Applicationコントローラーでの明示的な許可設定が必要です。
以下の手順でユーザーネームを追加していきます。
マイグレーションファイルの作成: deviseに新しい属性を追加する際は、最初にマイグレーションファイルを生成し、データベースに新しいカラムを追加します。
マイグレーションの実行: マイグレーションファイルを作成した後、データベースに変更を適用します。
ビューファイルの編集: ユーザーが新規登録やアカウント情報を編集する際に、新しく追加した属性をフォームに含める必要があります。
翻訳ファイルの編集: 新しい属性の表示と、それに関連するエラーメッセージが適切に表示されるように必要な項目を追加します。
モデルファイルのバリデーション追加: Userモデルに新しい属性に対するバリデーションを追加します。
Applicationコントローラーでの設定: 新しい属性を安全に扱うために、Applicationコントローラーでストロングパラメータを設定します。
それでは実際に手を動かしながら、Userモデルに新しい属性を追加してみましょう。
ユーザーネーム属性を追加しよう
まずは、以下のコマンドを実行して、データベースをリセットしておきましょう。
1
rails db:migrate:reset
新しい属性をdeviseのユーザーモデルに追加するには、最初にマイグレーションファイルを作成して、データベースに新しいカラムを追加する必要があります。ここでは、usersテーブルにusername
という新しいカラムを追加します。
ターミナルで以下のコマンドを実行し、マイグレーションファイルを生成し、データベースに変更を適用しましょう。
1
2
rails generate migration AddUserNameToUsers username:string
rails db:migrate
ユーザーが新規登録やアカウント情報を編集する際に、新しく追加した属性をフォームに含める必要があります。新規登録画面とアカウント編集画面のビューファイルにユーザーネームの入力フィールドを追加しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<h2>アカウント登録</h2>
<%= form_with model: resource, as: resource_name, url: registration_path(resource_name), local: true, class: 'form' do |f| %>
<%= render 'shared/validation_error', instance: resource %>
<div class="form__group">
<%= f.label :username, 'ユーザーネーム(必須):' %>
<%= f.text_field :username, autofocus: true, autocomplete: 'username', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード(必須):' %>
<%= f.password_field :password, autocomplete: 'new-password', placeholder: '6文字以上で入力してください', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password_confirmation, 'パスワード確認(必須):' %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit '登録する', class: 'form__button' %>
</div>
<% end %>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<h2>マイページ編集</h2>
<%= form_with model: resource, url: registration_path(resource_name), local: true, method: :put, class: 'form' do |f| %>
<%= render 'shared/validation_error', instance: resource %>
<div class="form__group">
<%= f.label :username, 'ユーザーネーム(必須):' %>
<%= f.text_field :username, autofocus: true, autocomplete: 'username', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :email, 'メールアドレス(必須):' %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password, 'パスワード:' %>
<%= f.password_field :password, autocomplete: 'new-password', placeholder: '空欄のままなら変更しません', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :password_confirmation, 'パスワード確認:' %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form__input' %>
</div>
<div class="form__group">
<%= f.label :current_password, '現在のパスワード(必須):' %>
<%= f.password_field :current_password, autocomplete: 'current-password', placeholder: '変更を反映するには現在のパスワードを入力してください', class: 'form__input' %>
</div>
<div class="form__submit">
<%= f.submit '更新する', class: 'form__button' %>
</div>
<% end %>
新しい属性であるユーザーネームに関するエラーメッセージを適切に表示させるため、翻訳ファイルに必要な設定を追加します。config/locales/ja.yml
を編集して、ユーザーネームに対応する日本語の翻訳を追加しましょう。
編集が完了したら、変更を適用するためにRailsサーバーの再起動を行いましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ja:
errors:
format: "%{attribute}%{message}"
activerecord:
attributes:
memo:
title: "タイトル"
content: "内容"
user:
email: "メールアドレス"
password: "パスワード"
password_confirmation: "パスワード確認"
username: "ユーザーネーム"
errors:
models:
memo:
attributes:
title:
blank: "を入力してください。"
too_long: "は30文字以内で入力してください。"
content:
blank: "を入力してください。"
user:
attributes:
email:
blank: "を入力してください。"
password:
blank: "を入力してください。"
taken: "は既に使用されています。"
blank: "が入力されていません。"
too_short: "は%{count}文字以上に設定して下さい。"
too_long: "は%{count}文字以下に設定して下さい。"
invalid: "は有効でありません。"
confirmation: "が内容とあっていません。"
password_confirmation:
blank: "を入力してください。"
taken: "は既に使用されています。"
blank: "が入力されていません。"
too_short: "は%{count}文字以上に設定して下さい。"
too_long: "は%{count}文字以下に設定して下さい。"
invalid: "は有効でありません。"
confirmation: "がパスワードと一致しません。"
current_password:
blank: "を入力してください。"
username:
blank: "を入力してください。"
Userモデルに新たに追加されたユーザーネーム属性に対してバリデーションを設定します。
app/models/user.rb
を開き、ユーザーネームの存在を確認するバリデーションを追加します。これにより、ユーザーネームが必ず入力されるように設定します。以下のようにコードを追加してください。
1
2
3
4
5
6
7
8
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :username, presence: true
end
deviseでは、メールアドレスやパスワードのようなデフォルトの属性は追加の設定なしで扱えますが、新しく追加した属性はApplicationController内で明示的に許可する設定を行う必要があります。configure_permitted_parameters
メソッドを使用します。
以下の:アクション名
は、deviseによって提供されているアクションを指定し、keys:
では、許可するカラム名をシンボルの配列で指定します。
1
2
3
4
5
6
class ApplicationController < ActionController::Base
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:アクション名, keys: [:カラム名1, :カラム名2, ...])
end
end
これにより、指定したアクションでの処理中に、keys:
で指定したカラムのパラメータが許可され、データベースに保存されます。
例えば、ユーザーの新規登録時にはアクション名sign_up
を使用します。ユーザーが新規登録する際に「ユーザーネーム」のパラメータを許可し、データベースに保存できるようにするためには、以下のようにconfigure_permitted_parametersメソッド内で指定します。
1
2
3
4
5
6
class ApplicationController < ActionController::Base
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: :username])
end
end
以下のようにbefore_action
を使用してconfigure_permitted_parametersメソッドを呼び出すことで、deviseに関連するコントローラーでのみ特定のパラメータを許可します。
if: :devise_controller?
という条件を設定することで、deviseが関与するコントローラーのアクションが実行される時のみconfigure_permitted_parameters
メソッドが呼び出され、他のコントローラーには影響を与えません。
1
2
3
4
5
6
7
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: :username])
end
end
それでは実際にApplicationControllerに設定していきます。
app/controllers/application_controller.rb
ファイルに変更を加え、ユーザーが新規登録(sign_up)とアカウント情報を更新(account_update)する際に、ユーザーネームがデータベースに保存されるようにしましょう。
1
2
3
4
5
6
7
8
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
devise_parameter_sanitizer.permit(:account_update, keys: [:username])
end
end
これで新しいユーザー属性を追加する全ての設定が完了しました。次は、ユーザーの新規登録やアカウント情報の編集時にユーザーネームが適切に表示され、正しくデータベースに保存されるかを確認しましょう。
ユーザーの新規登録画面にユーザーネームが適切に表示されていること確認しましょう。
ユーザーネーム、メールアドレス、パスワード、パスワードの確認を入力フォームに記入し、登録するボタンをクリックしてユーザーを新規登録してみましょう。
- ユーザーネーム:
ありす
- メールアドレス:
hoge@example.com
- パスワード:
000000
- パスワードの確認:パスワードを入力
アカウント登録後、マイページ編集にアクセスして、フォームにユーザーネームが表示されることを確認しましょう。先程の登録した情報もあります。
ユーザーネームをありす
からももこ
に変更し、現在のパスワードに000000
を入力し、「更新する」ボタンをクリックしましょう。
再度「マイページ編集」にアクセスし、ユーザーネームが正しく更新されたかをチェックしましょう。
これでUserモデルにユーザーネーム属性の追加は完了しました。次は、学習メモにどのユーザーが投稿したかを表示するための作業に進みましょう。学習メモとユーザーを関連付け、メモ一覧画面にユーザーネームを表示できるように実装していきます。
ユーザーネームと学習メモの関連付けをしよう
まだ学習メモにどのユーザーが作成したかの情報が紐付いていません。ユーザーとメモ間の関連付けを行い、メモがどのユーザーに属しているかを特定できるようにしましょう。
memosテーブルへのユーザーIDの追加
最初に、memosテーブルにユーザーIDを参照する外部キーuser_id
カラムを追加します。これにより、メモがどのユーザーに属しているかをデータベースレベルで関連付けられます。
新しいマイグレーションを作成し、外部キー制約が設定されるuser_id
カラムをmemos
テーブルに追加します。
以下のコマンドを順番に実行しましょう。
1
2
rails generate migration AddUserToMemos user:references
rails db:migrate
user:references
は、memos
テーブルにuser_id
カラムを追加し、それがusersテーブルのidカラムを外部キーとして参照することを意味します。
上記実行後にphpMyAdminでmemosテーブルを確認すると、以下のようにuser_idカラムが追加されているのを確認できます。
モデル間のアソシエーション設定
モデル間での関連付けを定義します。この段階では、MemoモデルがUserモデルに属している(belongs_to)という関係を設定します。
app/models/memo.rb
ファイルを開いて、belongs_to :user
を追加しましょう。
1
2
3
4
5
6
class Memo < ApplicationRecord
belongs_to :user
validates :title, presence: true, length: { maximum: 30 }
validates :content, presence: true
end
学習メモに投稿者情報を追加する前に、current_userメソッドについて学びましょう。
current_userメソッド
current_user
はdeviseが提供するメソッドで、現在ログインしているユーザーのオブジェクトを返します。このメソッドを使うことで、ビューやコントローラーのどこからでも現在ログイン中のユーザーの情報にアクセスできます。
例えば、ユーザーがログインしている状態ではcurrent_user
はそのユーザーのUser
モデルのインスタンスを返し、ログインしていない状態ではnil
を返します。
1
2
3
4
5
6
7
8
9
10
11
12
13
# ログインしているユーザーの例
current_user
=> #<User id: 1, email: "pikawaka.test@example.com" ....>
current_user.email
=> "pikawaka.test@example.com"
current_user.id
=> 1
# ログインしていない場合の例
current_user
=> nil
current_user.id
でログイン中のユーザーのidが取得できるんだね!
current_user
メソッドを使用して現在ログイン中のユーザー情報を取得する方法を確認します。pryコンソールを開いて、current_user
メソッドの挙動を確認しましょう。
下記の情報でログイン中のユーザー、「ももこ」さんの情報がcurrent_user
メソッドで取得可能です。
- ユーザーネーム:
ももこ
- メールアドレス:
hoge@example.com
current_user
メソッドは、ビューやコントローラーで現在ログイン中のユーザー情報にアクセスできる便利なメソッドです。今回は、index.html.erb
にbinding.pry
を挿入し、実際にその動作を確かめてみましょう。
1
2
3
4
5
<h2>学習メモ一覧</h2>
<%= render @memos %>
<%= binding.pry %>
上記を追加後、メモ一覧画面にアクセスしてください。ターミナルにてpryコンソールが起動し、そこでcurrent_user
を実行します。
以下のように、現在ログイン中のユーザー「ももこ」さんの情報が表示されるはずです。current_user.id
やcurrent_user.email
も試してみましょう。
もしnil
が返る場合は、ブラウザが「504 Gateway Time-out」を表示していないか確認してください。表示されている場合はページを再読み込みしてください。
binding.pry
のコードは確認後、削除しておきましょう。
メモ登録時にログインユーザーのIDを保存
現在、memosコントローラのcreate
アクションは、memo_params
メソッドを使ってストロングパラメータを適用しています。この設定では、メモのタイトル(title)と内容(content)のみがデータベースに保存されます。
たとえば、ももこさんがタイトル「新しいメモ」、内容「メモの内容です。」のメモを登録した場合、memo_params
メソッドはメモのタイトルと内容のデータのみを含むハッシュを生成します。
しかし、先ほどmemos
テーブルにuser_id
という外部キーを追加しました。これは、どのユーザーがメモを作成したかを特定するためです。したがって、メモ登録時には、このuser_id
カラムにそのユーザーのIDを保存する処理を加える必要があります。
このuser_id
カラムに登録するユーザーIDは、現在ログインしているユーザーのIDを保存することで、メモがどのユーザーに属しているかを明確にすることができます。
現在ログイン中のユーザーのIDは、current_user.id
で取得します。また、merge
メソッドを使って、ユーザーIDを含む新たなハッシュを作成します。
1
params.require(:memo).permit(:title, :content).merge(user_id: current_user.id)
merge
メソッドは、2つのハッシュを組み合わせて1つの新しいハッシュを作成します。
例えば、ももこさんがタイトル「新しいメモ」、内容「メモの内容です。」のメモを登録する場合では、params.require(:memo).permit(:title, :content)
を実行すると、次のようなハッシュが生成されます。
1
2
3
4
{
"title" => "新しいメモ",
"content" => "メモの内容です。"
}
この時点では、user_id
が含まれていません。ももこさん(ユーザーIDが1
)がログインしているユーザーであるとき、current_user.id
は1
を返します。
memo_params
メソッドで.merge(user_id: current_user.id)
を実行すると、上記のハッシュに"user_id" => 1
を追加します。これにより、以下のようなハッシュが返されます。
1
2
3
4
5
{
"title" => "新しいメモ",
"content" => "メモの内容です。",
"user_id" => 1
}
このようにcurrent_user.id
とmerge
メソッドを活用することで、メモのタイトル(title)と内容(content)に加えて、user_id
カラムに現在ログインしているユーザーのIDを保存できます。
memos_controller.rb
ファイルを開いて、以下のように変更しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MemosController < ApplicationController
#...省略
def create
# 新しい学習メモを登録する処理
@memo = Memo.new(memo_params)
if @memo.save
redirect_to root_path, notice: '学習メモが投稿されました。'
else
render :new
end
end
private
def memo_params
params.require(:memo).permit(:title, :content).merge(user_id: current_user.id)
end
#...省略
end
次は、学習メモを新規に登録してみましょう。以下の情報を使ってメモを作成します。
- タイトル:
URLを構成する基本的な要素
- 本文:
URLの基本的な構成要素には、プロトコル(Scheme)、ドメイン名(Host)、そしてパス(Path)の3つがある。
メモの登録が完了すると、メモ一覧画面に新しいメモが追加されていることが確認できます。
phpMyAdminを使用して、memos
テーブルにおいて、新しく追加したメモのuser_id
カラムが正しく設定されているかを確認します。ももこさんがメモを作成した場合、そのメモのuser_id
カラムにはももこさんのユーザーID「1」が格納されているはずです。
さらに、user_idの「1」を選択すると、該当するユーザー(この場合はももこさん)の詳細情報に関連づけられていることが確認できます。
ユーザーネームの動的表示
メモ一覧画面で各メモに対応するユーザーネームを表示させます。既に設定したメモとユーザーのアソシエーションを活用し、ユーザーネームを動的に表示させましょう。
アソシエーションを設定しており、memo.user.username
のようにMemoモデル経由でユーザーネームを取得できます。
_memo.html.erb
ファイルを開いて、以下のように変更しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div class="memo-wrapper">
<div class="memo">
<div class="memo__header">
<div class="memo__user">
<%= image_tag 'user_icon.png', alt: 'ユーザーアイコン' %>
<span class="memo__username"><%= memo.user.username %></span>
</div>
<div class="memo__operations">
<ul>
<% unless action_name == 'show' %>
<li><%= link_to '詳細', memo_path(memo) %></li>
<% end %>
<li><%= link_to '編集', edit_memo_path(memo) %></li>
<li><%= link_to '削除', memo_path(memo), method: :delete, data: { confirm: '本当に削除してよろしいですか?' } %></li>
</ul>
</div>
</div>
<div class="memo__body">
<p class="memo__title">
<%= memo.title %>
</p>
<div class="memo__content">
<%= simple_format(memo.content) %>
</div>
</div>
</div>
</div>
変更後、ページをリロードして、ユーザーネームが正しく表示されていることを確認します。以下のように「ももこ」さんのユーザーネームが表示されるようになります。
現在のユーザー、ももこさんからログアウトし、新たなユーザー「たけし」さんを作成し、そのアカウントでメモを作成してみましょう。
以下の情報を使用して新規ユーザーを登録してください。
- ユーザーネーム:
たけし
- メールアドレス:
fuga@example.com
- パスワード:
000000
- パスワードの確認:パスワードを入力
ログイン後のたけしさんで、次の情報を持つメモを作成しましょう。
- タイトル:
プロトコルについて
- 本文:
プロトコルは、インターネット上で情報をやり取りするときの「取り決め」や「ルール」を指す。URLの先頭にある「https」(または「http」)がこれに該当する。これはWebサイトとの情報のやり取りにhttpsというプロトコルを使用することを示す。
メモを作成後、メモ一覧画面でたけしさんの作成したメモがどのように表示されるか確認しましょう。
deviseによるアクセス制限の実装
未ログインユーザーのアクセス制限
「5.ログイン状態でのリンク表示変更」の実装によって、未ログイン時にはメモの「新規作成」リンクは表示されないようにしましたね。
しかし、ログインしていない状態で直接 /memos/new
にアクセスすると、学習メモの登録画面に入ることができてしまいます。
ユーザーがログインしていなくても学習メモの登録画面にアクセスできる状態
このセクションでは、ユーザーがログインしていない場合、学習メモの登録、編集、削除、更新画面にアクセスした際に、ログインページへリダイレクトするように設定します。
これを実現するには、authenticate_user!
メソッドを利用します。
authenticate_user!メソッド
authenticate_user!
は、deviseが提供するヘルパーメソッドで、ユーザーが未ログインの場合に自動的にログインページへリダイレクトさせる機能を持っています。
このメソッドをコントローラのbefore_actionとして設定することで、指定したアクションをログイン済みのユーザーに限定することができます。
1
2
3
4
5
class SomeController < ApplicationController
before_action :authenticate_user!, only: [:action1, :action2]
# ... その他の処理...
end
この設定により、action1
やaction2
を実行しようとするユーザーがログインしていない場合、自動的にログインページにリダイレクトされます。
メモ関連アクションで未ログインユーザーのアクセスを制限しよう
authenticate_user!
メソッドを使用して、メモ関連アクション(new, create, edit, update, destroy)で未ログインユーザーのアクセスを制限します。
ログアウトした状態で、ブラウザのアドレスバーに /memos/new
を入力して、未ログイン時に学習メモの新規作成ページにアクセスできるかを確認してみましょう。
ユーザーがログインしていなくても学習メモの登録画面にアクセスできる状態
memosコントローラのnew
アクションでユーザーがログインしていない場合に、ログインページにリダイレクトさせる設定を追加します。
memos_controller.rb
ファイルを開いて、以下の1行を追加してください。
1
2
3
4
5
6
7
8
9
10
11
class MemosController < ApplicationController
before_action :authenticate_user!, only: [:new]
# 他のアクション定義は省略
def new
@memo = Memo.new
end
# 他のアクション定義は省略
end
これにより、未ログインのユーザーが学習メモの新規作成ページにアクセスしようとしたとき、自動的にログインページへ誘導されます。
設定後、再度ログアウト状態から学習メモの新規作成画面にアクセスし、自動的にログインページへリダイレクトされるかを確認してみましょう。
ログインページにリダイレクトされることを確認できれば、設定が成功しています。
ログアウトした状態で、ブラウザのアドレスバーに /memos/1/edit
を入力して、未ログイン時に学習メモの編集画面にアクセスできるかを確認してみましょう。
期待する動作は、ログインページへのリダイレクトですが、設定前は編集画面にアクセスできてしまうことが確認できます。
new
アクションに加えて、create
、edit
、update
、destroy
アクションでも未ログインユーザーのアクセスを制限しましょう。
1
2
3
4
5
6
7
8
9
10
11
class MemosController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
# 他のアクション定義は省略
def new
@memo = Memo.new
end
# 他のアクション定義は省略
end
設定後、再度ログアウト状態から/memos/1/edit
で学習メモの編集画面にアクセスし、自動的にログインページへリダイレクトされるかを確認してみましょう。
ログインページにリダイレクトされることを確認できれば、設定が成功しています。
メモ作成者以外のアクセス制限
現在、ログイン状態に関わらず他のユーザーの学習メモを「編集」や「削除」ができる状態です。たとえば、たけしさんがログインしていても、ももこさんの編集・削除リンクが表示されるケースがあります。
たけしさんがログインしている状態でも、ももこさんの編集・削除リンクが表示される
これを防ぐために、現在ログインしているユーザーとメモの作成者が一致する場合のみ、編集や削除を表示するようにビューを調整します。
以下の情報を使用してログインしておきましょう。
- メールアドレス:
fuga@example.com
- パスワード:
000000
現在ログインしているユーザーがメモの作成者である場合のみ、編集と削除リンクを表示するように設定します。これを実現するためには、if current_user == memo.user
条件を利用して、リンクの表示を制御します。
_memo.html.erb
ファイルで、編集と削除リンクの表示周りを以下のように変更しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div class="memo-wrapper">
<div class="memo">
<div class="memo__header">
<div class="memo__user">
<%= image_tag 'user_icon.png', alt: 'ユーザーアイコン' %>
<span class="memo__username"><%= memo.user.username %></span>
</div>
<div class="memo__operations">
<ul>
<% unless action_name == 'show' %>
<li><%= link_to '詳細', memo_path(memo) %></li>
<% end %>
<% if current_user == memo.user %>
<li><%= link_to '編集', edit_memo_path(memo) %></li>
<li><%= link_to '削除', memo_path(memo), method: :delete, data: { confirm: '本当に削除してよろしいですか?' } %></li>
<% end %>
</ul>
</div>
</div>
<div class="memo__body">
<p class="memo__title">
<%= memo.title %>
</p>
<div class="memo__content">
<%= simple_format(memo.content) %>
</div>
</div>
</div>
</div>
変更後、ページにアクセスすると、他のユーザー(たとえばももこさん)の編集・削除リンクが表示されなくなります。
たけしさんがログインしている状態では、ももこさんの編集・削除リンクが表示されない
ただし、ビューでのリンク非表示だけでは不十分です。URLを直接入力することで他のユーザーのメモの編集画面にアクセスできてしまうため、コントローラーレベルでのアクセス制御が必要です。
たけしさんがログイン中に、ももこさんのメモ「URLを構成する基本的な要素」の編集ページへ直接URL /memos/1/edit
を使用してアクセスしましょう。現状では、たけしさんがももこさんの編集画面にアクセスし、編集が可能な状態になっていることを確認できます。
先ほどmemosコントローラのbefore_actionで設定したauthenticate_user!
メソッドは、ユーザーがログインしているかどうかのみを判断し、ログインしていない状態で学習メモの編集画面にアクセスしようとすると、自動的にログインページへリダイレクトします。
しかし、現在のケースではユーザーがログインしているため、authenticate_user!
メソッドはこの状況ではアクセス制限を適用しません。
そのため、メモの作成者と現在のログインしているユーザーが一致しない場合には、ルートページへリダイレクトさせる必要があります。
redirect_unless_creator!
メソッドを定義して、メモ作成者とログインユーザーが一致しない場合はルートページにリダイレクトさせる処理を実装します。このメソッドを編集、更新、削除アクション実行前に適用するようにbefore_action
を設定します。
memos_controller.rb
ファイルを以下のように編集してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class MemosController < ApplicationController
before_action :set_memo, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
before_action :redirect_unless_creator!, only: [:edit, :update, :destroy]
def index
# 学習メモの一覧を取得する処理
@memos = Memo.all
end
def show
# 特定の学習メモの詳細情報を取得する処理
end
def new
# 新しい学習メモの作成フォームを表示する処理
@memo = Memo.new
end
def create
# 新しい学習メモを登録する処理
@memo = Memo.new(memo_params)
if @memo.save
redirect_to root_path, notice: '学習メモが投稿されました。'
else
render :new
end
end
def edit
# 既存の学習メモを編集するためのフォームを表示する処理
end
def update
# 既存の学習メモ情報を更新する処理
if @memo.update(memo_params)
redirect_to root_path, notice: '学習メモが更新されました。'
else
render :edit
end
end
def destroy
# 特定の学習メモを削除する処理
@memo.destroy
redirect_to root_path, notice: '学習メモが削除されました。'
end
private
def memo_params
params.require(:memo).permit(:title, :content).merge(user_id: current_user.id)
end
def set_memo
@memo = Memo.find(params[:id])
end
def redirect_unless_creator!
redirect_to root_path unless @memo.user == current_user
end
end
redirect_unless_creator!
メソッド自体は、現在のユーザー(current_user
)がメモの作成者(@memo.user
)と一致しない場合、ルートページへリダイレクトさせる処理を行います。これにより、ユーザーが他人のメモを編集、更新、削除しようとした際に、これを防ぎ、安全性を高めることができます。
コントローラーレベルでのアクセス制限が正しく機能しているかを確認するために、ももこさんのメモ編集画面(例:/memos/1/edit
)にたけしさんがログインした状態で直接アクセスを試みましょう。
もしもアクセス制限が正しく設定されていれば、たけしさんはももこさんのメモ編集画面にアクセスできず、代わりにルートページや指定したリダイレクト先に遷移されるはずです。これにより、ユーザーが他人のメモを編集できないことが保証されます。
ももこさんのメモ編集画面にアクセスできず、ルートページにリダイレクトされる
これでdeviseを利用したログイン認証機能の実装は完了です!長い道のりでしたが、よく頑張りましたね!
この記事で学んだことをTwitterに投稿して、アウトプットしよう!
Twitterの投稿画面に遷移します