すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

deviseでログイン機能を実装しよう

この記事で出来るようになること

はじめに

この記事では、Railsで簡単にログイン認証システムを構築する方法を学びます。deviseというgemを使用し、ユーザー登録、ログイン、ログアウトなどの基本的な認証機能の設定から、カスタマイズ方法、アクセス制限までを学びます。

事前準備

データベースを作り直しましょう

ターミナルでmemo_appに移動し、rails db:migrate:resetを実行してデータベースを作り直しましょう。

ターミナル
1
cd ~/memo_app
ターミナル(~/environment/memo_app) | データベースを作り直す
1
rails db:migrate:reset
コンソールでデータを挿入しましょう

コンソールを起動して、学習メモのデータを挿入しましょう。

ターミナル(~/environment/memo_app) | コンソールを起動する
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」というパスワードを設定した場合、データベースでは、変換されたハッシュ値がカラムに保存されます。

この変換は一方通行で、ハッシュ化されたパスワードから元のパスワードを復元することは実質的に不可能です。認証時には、ユーザーが入力したパスワードをハッシュ化し、保存されているハッシュ値と比較することで、パスワードが正しいかどうかを確認します。

ハッシュ化はセキュリティを強化するために非常に重要で、万が一データベースが侵害されても、ハッシュ化されたパスワードからは元の平文を特定することが困難なため、ユーザーのパスワードはより安全に保護されます。

ぴかわかさん
ポイント
  1. 平文は暗号化や変換を施されていない、そのままの形式のパスワードのこと
  2. ハッシュ化は、元の値が推測できない一意の文字列に変換すること
  3. 通常、パスワードは、ハッシュ化された形でデータベースに保存される

3.アクセスの承認または拒否

ユーザー名とパスワードが一致した場合、システムはユーザーを正しい人物であると判断し、アクセスを許可します。一致しない場合、アクセスは拒否され、ユーザーに対してエラーメッセージが表示されます。

ログイン失敗時に表示されるメッセージの例

ログイン画面では、このメッセージのようにメールアドレスとパスワードのどちらが誤っているかを明かさないことがセキュリティ上重要であり、悪意ある第三者が情報を得ることを防ぎます。

4.セッションの確立

セッションとは、サービス利用時にユーザーの情報を一時的に記憶しておく仕組みです。簡単に言うと、ユーザーがログインしてからログアウトするまでの間、Webアプリケーションがユーザーの情報を「覚えている」状態のことです。

アクセスが承認されると、システムはユーザーのセッションを開始します。これにより、ユーザーはログアウトするまで、または一定時間が経過するまで、再度ログインすることなくサービスを利用できるようになります。

5.ログアウトプロセス

ユーザーがログアウトを選択するとセッションは終了し、再度ログインしない限りサービスを利用することができません。

ログアウト時のイメージ画面

ログイン認証の基本的なステップから分かるように、ログイン機能の実装には、セキュリティなどいろいろなことを考えなければなりません。しかし、Railsではdeviseのような認証ライブラリを利用して、簡単にログイン機能を作ることができます。

ポイント
  1. ログインページでメールアドレスとパスワードを入力して情報を照合する
  2. 情報が一致すればアクセスを許可し、セッションが開始する
  3. ユーザーはログアウトするまで、または一定時間が経過するまで、再度ログインせずにサービスを利用できる

deviseによる認証機能の基本実装

deviseはRailsで利用できるgemの一つで、ログイン認証機能を手軽に追加できます。

このgemを追加し、提供されるコマンドを使って設定を進めると、ユーザーの新規登録(アカウント登録)、ログイン、ログアウトの機能がアプリケーションに簡単に組み込めます。

deviseを使用すると、デフォルトで利用可能ないくつかの画面が提供されます。(リセットCSSの影響で入力欄の枠が消えています。)

デフォルトのユーザー新規(アカウント)登録画面

これらの画面の見た目をカスタマイズする方法は後ほど説明しますが、まずはdeviseが提供する基本的な機能を理解し、活用することから始めましょう。

1.deviseの導入と基本設定

まずはdeviseのGemを導入し、初期設定を行いましょう。

deviseのインストール

deviseを導入しよう

Gemfileに以下の2行を追加し、ターミナルでアプリケーションのルートディレクトリに移動して、bundle installコマンドを実行し、deviseをインストールしましょう。

Gemfile
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に追加した後は、ターミナルで以下を実行しましょう。

ターミナル(~/environment/memo_app)
1
bundle install

サーバーを起動している場合は、再起動しておきましょう。

コマンドを実行しdeviseの初期設定を行いましょう

インストールが完了したら、rails g devise:installコマンドを実行してdeviseの初期設定を行います。このコマンドを実行しないと、devise関連の機能は使用できないので注意しましょう。

ターミナルで以下を実行しましょう。

ターミナル(~/environment/memo_app)
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を定義する方法を確認しましょう

config/routes.rbファイルでroot_urlを定義するとは、アプリケーションにアクセスした際に最初に表示されるページを指定することです。root_urlはアプリケーションのルートページ、つまり最初に表示されるページのURLを指します。

例えば、Pikawakaのアプリケーションで「https://pikawaka.com」とアクセスしたときに表示されるトップページがroot_urlに相当します。このURLには特定のパスが含まれておらず、アプリケーションの入り口となっています。

Pikawakaのアプリケーションのルートページ

具体的にはconfig/routes.rbファイルにrootメソッドを使って、アクセス時に最初に表示したいページに対応するコントローラとアクションを設定します。ルーティングはファイル内で上から順に解釈されるため、root定義はファイルの最上部に記述するのが一般的です。

config/routes.rb | rootの定義方法
1
2
3
4
Rails.application.routes.draw do
root to: 'コントローラ名#アクション名'
# ...他のルーティング設定... end
現在のアプリケーションのルートページをチェックしましょう

ルート設定がない場合、デフォルトのRails起動画面がルートページとして表示されます。

現在、ルート設定がされていないため、以下のデフォルト起動画面が表示されるはずです。サーバーを起動して確認してみましょう。

ルート設定がない場合、デフォルト起動画面が表示される

今回は、このルートのURLにアクセスした際に、メモ一覧画面を表示できるようにルートを設定していきます。

ぴかわかさん
アプリケーションのルートページをメモ一覧に設定しましょう

メモ一覧画面は、memosコントローラのindexアクションで表示されるため、これをアプリケーションのルートページに設定します。

config/routes.rbファイルを編集して、以下のようにルートを設定しましょう。

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を使用しましょう

root_pathはアプリケーションのルートページ、つまりmemos#indexアクションに対応するビューへのパスを返します。これにより、アプリケーション内でmemos_pathを使用していた箇所をroot_pathに置き換えることが可能です。

まずは、memosコントローラのmemos_pathroot_pathに置き換えましょう。

app/controllers/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
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_pathroot_pathに置き換えましょう。

app/views/shared/_header.html.erb | 変更後
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にアクセスしていた箇所が、ルートページへのアクセスに変更されます。ルートページにアクセスすると、メモ一覧画面が表示されるようになります。

flashメッセージ表示用のコードを追記しましょう

学習メモアプリケーションでは現在、noticeを使用してflashメッセージを表示していますが、deviseを導入すると、alertも使用されるようになります。

  • notice - 成功した操作や情報を伝える際に使われる
  • alert - 警告やエラーを伝える際に使われる

_flash-message.html.erbに以下のコードを追加しましょう。

app/views/shared/_flash-message.html.erb | alertメッセージ用のコードを追加する
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のスタイルを追記します。

app/assets/stylesheets/layout.scss
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の認証機能を組み込むために使用します。このコマンドを実行することで、以下の作業が自動的に行われます。

  1. Userモデルファイルの作成。
  2. deviseが要求するカラムを含むデータベースマイグレーションファイルの作成。
  3. devise用のルーティング設定の追加。

手作業でこれらの設定を行う手間が省け、効率的に認証機能をアプリケーションに導入できます。

ぴかわかさん
rails g devise user コマンドを実行しましょう

以下のコマンドをターミナルで実行しましょう。

ターミナル(~/environment/memo_app)
1
rails g devise user

実行後、Userモデルファイル、マイグレーションファイルが生成され、ルーティングに設定が追加されることが確認できます。

ぴっかちゃん

主要なファイルや設定をさっと見てみましょう!

生成されたUserモデルファイルを確認しましょう

最初にapp/models/user.rbファイルを見てみると、ファイル作成後、特定のdevise関連の設定が追加されていることがわかります。

以下の箇所で設定されており、データベース認証、ユーザー登録、パスワードの再設定、ログイン状態の記憶、バリデーション機能などを提供します。この設定があることで、コントローラーで別途これらの機能を実装する必要がなくなります。

生成されたマイグレーションファイルを確認しましょう

生成されたマイグレーションファイルでは、deviseを使ってUserモデルのためのusersテーブルをデータベースに作成する定義が記述されています。

db/migrateディレクトリ内にあるこのファイルを開いてみましょう。ファイル内には多くの項目が記載されていますが、ここではいくつかの重要な部分だけを説明します。コメントアウトされた部分は現時点では重要ではないので、無視して構いません。

特に重要なのは、ユーザー識別とセキュリティに関わる以下の2つです。

カラム名 説明
email ユーザー識別のためのメールアドレスです。null: falseはこのカラムが空であってはならず、default: ""はデフォルト値が空文字列であることを示しています。
encrypted_password ユーザーのパスワードを暗号化して保存するカラムです。これもnull: falsedefault: ""の制約を持っています。

これらのカラムはユーザーがログインするために直接使用され、アプリケーションのセキュリティにおいて重要な役割を果たします。

自動的に追加されたルーティングを確認しましょう

最後に、config/routes.rbファイルでdevise_for :usersが自動で追加されているのを確認しましょう。

devise_for :usersは、Userモデルに関連する一連の認証機能に対するルーティングを自動的に設定するための記述です。

この記述により、サインアップ、ログイン、ログアウト、パスワードリセットなど、ログイン認証に必要なアクションへのルーティングが自動的に追加されます。後ほど、具体的なルーティングの内容を確認します。

ルート定義を一番上に移動しましょう

自動追加されたdevise_for :usersは、ルート設定の上部に配置されていますが、通常はroot 'memos#index'をファイルの最上部に置くことが推奨されます。

以下のようにファイルを編集しましょう。

config/routes.rb | root定義を一番上に移動する
1
2
3
4
5
Rails.application.routes.draw do
root 'memos#index'
devise_for :users resources :memos end

以上で、rails g devise user コマンド実行で自動生成されたファイルと設定の確認が終わりました。このコマンドの実行によって、次の作業が自動で行われました。

  1. Userモデルファイルの作成。
  2. deviseが要求するカラムを含むデータベースマイグレーションファイルの作成。
  3. devise用のルーティング設定の追加。

これでdeviseによる基本的なログイン認証機能のためのファイルと設定の準備が整いました!

ぴかわかさん

usersテーブルの作成

自動生成されたマイグレーションファイルを適用して、usersテーブルをデータベースに作成しましょう。

マイグレーションファイルを実行しよう

マイグレーションファイルで定義したテーブルをデータベースに適用させるためには、railsコマンドでマイグレーションファイルを実行させる必要があります。

アプリケーションのルートディレクトリに移動し、以下のコマンドを実行しましょう。

ターミナル(~/environment/memo_app)| 未実行のマイグレーションを実行する
1
rails db:migrate

phpMyAdminでusersテーブルを確認してみましょう

phpMyAdminを使用して、usersテーブルが正しく作成されているか確認しましょう。

以下のコマンドを順番に実行し、PHPビルトインサーバーを起動してください。

ターミナル | カレントディレクトリを変更する
1
cd ~/environment/memo_app/public/phpMyAdmin-5.1.1-all-languages
ターミナル(~/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キーを押して検索してください。

:8081を追記する

phpMyAdminにログインしましょう。ユーザー名の欄に「root」と半角英字で入力し、実行ボタンをクリックしてください。

phpMyAdminのログイン画面

ログイン後は、memo_app_developmentデータベースを選択し、usersテーブルが表示されていることを確認しましょう。

usersテーブルの構造を見ると、rails g devise userで自動生成されたカラムが含まれていることが確認できます。

特に、emailencrypted_passwordカラムはユーザー認証に直接関係しています。emailカラムはユーザー識別に使用され、encrypted_passwordカラムにはユーザーのパスワードが暗号化されて保存されます。

3.deviseルーティングの詳細確認

config/routes.rbに自動で追加されたdevise_for :usersは、deviseに関連する一連のルーティングが設定されています。この設定により、サインアップ、ログイン、ログアウトなど、ログイン認証に必要なページへのルートが自動的に追加されました。

ここで、deviseがどのようなルーティングを追加したのか具体的な内容を確認しましょう。

config/routes.rb
1
2
3
4
5
Rails.application.routes.draw do
  root 'memos#index'
devise_for :users
resources :memos end
devise関連のルーティングを確認しましょう

具体的なルーティングの内容を確認するために、ターミナルでusersに関連するルーティング情報のみを抽出して表示してみましょう。

以下のコマンドを実行してください。

ターミナル(~/environment/memo_app)| 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テーブルのデータを確認しましょう

phpMyAdminでusersテーブルを開き、先ほど登録したユーザー情報が反映されていることを確認しましょう。

emailカラムにはhoge@example.comが入っており、登録したメールアドレスが正しく保存されています。また、encrypted_passwordカラムには、直接入力したパスワード000000ではなく、それが暗号化された形で保存されていることが確認できます。

ぴっかちゃん

こうやってユーザーのパスワード情報が安全に管理されているんだね!

ログイン状態でのアクセス制御の確認

ユーザー登録完了後は、自動的にログイン状態になります。この状態でユーザー登録画面に再アクセスすると、どうなるか確認しましょう。

ログイン済み状態でユーザー登録画面に再度アクセスしてみましょう

Webブラウザで、アプリケーションのURLに/users/sign_upを追加してアクセスし、画面を確認してみましょう。

アクセスすると、ルートページにリダイレクトされ、以下の画像のようにflashメッセージのalert(警告やエラーを伝える際に使われるメッセージ)で「すでにログインしています。」と表示されます。

このように既にログインしているユーザーが新規登録画面にアクセスしようとすると、特定のページにリダイレクトされて、アクセスできないようになっています。この挙動はdeviseがデフォルトで提供する機能の一つです。

ログイン済み状態でログイン画面にもアクセスしてみましょう

ログイン状態で、ユーザーログイン画面に/users/sign_inでアクセスした場合も同様にルートページにリダイレクトされ「すでにログインしています。」と表示されるはずです。ログイン画面にもアクセスして確かめてみましょう。

ポイント
  1. ユーザー登録を完了すると、自動的にログイン状態になる。
  2. ログイン済みの状態でユーザー新規登録画面やログイン画面にアクセスしようとすると、特定のページにリダイレクトされ、deviseによってアクセス制限がかけられる。

ユーザー情報編集と更新の確認

ユーザー情報の編集画面にアクセスしてみましょう

ユーザー情報の編集画面は、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ファイルを開いて、以下のハイライト箇所に変更しましょう。

app/views/shared/_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ファイルを開いて、以下のハイライト箇所に変更しましょう。

app/assets/stylesheets/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ファイルを編集し、必要なリンクをヘッダーに追加しましょう。ユーザー情報編集画面は「マイページ編集」、ユーザー新規登録画面は「アカウント登録」というテキストリンクにしてます。

app/views/shared/_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と組み合わせることにより、ユーザーのログイン状態に基づいて、表示するリンクを動的に切り替えることができます。

ビューファイル | user_signed_in?を使用した条件分岐
1
2
3
4
5
<% if user_signed_in? %>
  <%# ユーザーがログインしている時に表示されるコンテンツ %>
<% else %>
  <%# ユーザーがログインしていない時に表示されるコンテンツ %>
<% end %>
ログイン状態に応じたリンク表示を設定しましょう

ユーザーがログインしている時は「新規作成」、「マイページ編集」、「ログアウト」のリンクを表示し、ログインしていない時は「アカウント登録」、「ログイン」のリンクを表示するようにします。

_header.html.erbファイルを以下のように編集して、ユーザーのログイン状態に応じて表示するリンクを変更しましょう。

app/views/shared/_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コマンドを実行し、これらのファイルをプロジェクト内にコピーします。

rails g devise:viewsコマンドを実行しましょう
ターミナル(~/environment/memo_app) | deviseのビューファイルをコピーする
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
form_withへの書き換えとスタイリングを行いましょう

deviseのビューファイルはデフォルトでform_forを使用していますが、Rails 5.1以降ではform_withが推奨されています。より現代的なフォームヘルパーform_withに書き換え、さらに以前に作成したCSSクラスを適用して見た目を整えましょう。

各ビューファイルを以下のコードに書き換えましょう。

app/views/devise/sessions/new.html.erb| ログインの変更後
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 %>
app/views/devise/registrations/new.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
<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 %>
app/views/devise/registrations/edit.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
<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サーバーの再起動を行いましょう。

config/locales/ja.yml
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には、以下のように記述しましょう。

/app/views/shared/_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: インスタンス名 %>
例:resourceインスタンスを渡す場合
1
<%= render 'shared/validation_error', instance: resource %>
エラーメッセージの部分テンプレートを適用しましょう

メモ機能のフォーム、ユーザー新規登録画面、およびユーザー情報編集画面にて、エラーメッセージを部分テンプレートから呼び出して表示するように変更しましょう。

メモ機能ではmemoインスタンス、ユーザー新規登録画面とユーザー情報編集画面ではresourceインスタンスをそれぞれ渡します。

app/views/memos/_form.html.erb | 変更後
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 %>
app/views/devise/registrations/edit.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
<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 %>
app/views/devise/registrations/new.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
<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コントローラーでの明示的な許可設定が必要です。

以下の手順でユーザーネームを追加していきます。

  1. マイグレーションファイルの作成: deviseに新しい属性を追加する際は、最初にマイグレーションファイルを生成し、データベースに新しいカラムを追加します。

  2. マイグレーションの実行: マイグレーションファイルを作成した後、データベースに変更を適用します。

  3. ビューファイルの編集: ユーザーが新規登録やアカウント情報を編集する際に、新しく追加した属性をフォームに含める必要があります。

  4. 翻訳ファイルの編集: 新しい属性の表示と、それに関連するエラーメッセージが適切に表示されるように必要な項目を追加します。

  5. モデルファイルのバリデーション追加: Userモデルに新しい属性に対するバリデーションを追加します。

  6. Applicationコントローラーでの設定: 新しい属性を安全に扱うために、Applicationコントローラーでストロングパラメータを設定します。

それでは実際に手を動かしながら、Userモデルに新しい属性を追加してみましょう。

ぴかわかさん

ユーザーネーム属性を追加しよう

データベースをリセットしましょう

まずは、以下のコマンドを実行して、データベースをリセットしておきましょう。

ターミナル(~/environment/memo_app) | データベースを作り直す
1
rails db:migrate:reset
マイグレーションファイルを生成して、データベースに適用しましょう

新しい属性をdeviseのユーザーモデルに追加するには、最初にマイグレーションファイルを作成して、データベースに新しいカラムを追加する必要があります。ここでは、usersテーブルにusernameという新しいカラムを追加します。

ターミナルで以下のコマンドを実行し、マイグレーションファイルを生成し、データベースに変更を適用しましょう。

ターミナル(~/environment/memo_app)
1
2
rails generate migration AddUserNameToUsers username:string
rails db:migrate

新しく追加した属性をフォームに含めましょう

ユーザーが新規登録やアカウント情報を編集する際に、新しく追加した属性をフォームに含める必要があります。新規登録画面とアカウント編集画面のビューファイルにユーザーネームの入力フィールドを追加しましょう。

app/views/devise/registrations/new.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
<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 %>
app/views/devise/registrations/edit.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
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サーバーの再起動を行いましょう。

config/locales/ja.yml
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モデルにバリデーションを設定しましょう

Userモデルに新たに追加されたユーザーネーム属性に対してバリデーションを設定します。

app/models/user.rbを開き、ユーザーネームの存在を確認するバリデーションを追加します。これにより、ユーザーネームが必ず入力されるように設定します。以下のようにコードを追加してください。

/memo_app/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
ApplicationControllerで新しいユーザー属性を許可する設定の方法を確認しましょう

deviseでは、メールアドレスやパスワードのようなデフォルトの属性は追加の設定なしで扱えますが、新しく追加した属性はApplicationController内で明示的に許可する設定を行う必要があります。configure_permitted_parametersメソッドを使用します。

以下の:アクション名は、deviseによって提供されているアクションを指定し、keys:では、許可するカラム名をシンボルの配列で指定します。

configure_permitted_parametersメソッドの基本書式
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に設定していきます。

ぴかわかさん
ApplicationControllerで新しいユーザー属性を許可しましょう

app/controllers/application_controller.rbファイルに変更を加え、ユーザーが新規登録(sign_up)とアカウント情報を更新(account_update)する際に、ユーザーネームがデータベースに保存されるようにしましょう。

app/controllers/application_controller.rb
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カラムを追加します。これにより、メモがどのユーザーに属しているかをデータベースレベルで関連付けられます。

memosテーブルにuser_idの外部キーを追加しましょう

新しいマイグレーションを作成し、外部キー制約が設定されるuser_idカラムをmemosテーブルに追加します。

以下のコマンドを順番に実行しましょう。

ターミナル(~/environment/memo_app)
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)という関係を設定します。

Memoモデルにbelongs_to :userを追加しましょう

app/models/memo.rbファイルを開いて、belongs_to :userを追加しましょう。

app/models/memo.rb
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を返します。

current_userメソッドの使い方
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メソッドを利用してログインユーザー情報を取得しよう

current_userメソッドを使用して現在ログイン中のユーザー情報を取得する方法を確認します。pryコンソールを開いて、current_userメソッドの挙動を確認しましょう。

下記の情報でログイン中のユーザー、「ももこ」さんの情報がcurrent_userメソッドで取得可能です。

  • ユーザーネーム:ももこ
  • メールアドレス:hoge@example.com

current_userメソッドは、ビューやコントローラーで現在ログイン中のユーザー情報にアクセスできる便利なメソッドです。今回は、index.html.erbbinding.pryを挿入し、実際にその動作を確かめてみましょう。

app/views/memos/index.html.erb
1
2
3
4
5
<h2>学習メモ一覧</h2>

<%= render @memos %>

<%= binding.pry %>

上記を追加後、メモ一覧画面にアクセスしてください。ターミナルにてpryコンソールが起動し、そこでcurrent_userを実行します。

以下のように、現在ログイン中のユーザー「ももこ」さんの情報が表示されるはずです。current_user.idcurrent_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を含む新たなハッシュを作成します。

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.id1を返します。

memo_paramsメソッドで.merge(user_id: current_user.id)を実行すると、上記のハッシュに"user_id" => 1を追加します。これにより、以下のようなハッシュが返されます。

1
2
3
4
5
{
  "title" => "新しいメモ",
  "content" => "メモの内容です。",
  "user_id" => 1
}

このようにcurrent_user.idmergeメソッドを活用することで、メモのタイトル(title)と内容(content)に加えて、user_idカラムに現在ログインしているユーザーのIDを保存できます。

メモ登録時にログインユーザーのIDを保存できるようにしましょう

memos_controller.rbファイルを開いて、以下のように変更しましょう。

app/controllers/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でuser_idの登録を確認しよう

phpMyAdminを使用して、memosテーブルにおいて、新しく追加したメモのuser_idカラムが正しく設定されているかを確認します。ももこさんがメモを作成した場合、そのメモのuser_idカラムにはももこさんのユーザーID「1」が格納されているはずです。

さらに、user_idの「1」を選択すると、該当するユーザー(この場合はももこさん)の詳細情報に関連づけられていることが確認できます。

ユーザーネームの動的表示

メモ一覧画面で各メモに対応するユーザーネームを表示させます。既に設定したメモとユーザーのアソシエーションを活用し、ユーザーネームを動的に表示させましょう。

メモ一覧にユーザーネームを表示しましょう

アソシエーションを設定しており、memo.user.usernameのようにMemoモデル経由でユーザーネームを取得できます。

_memo.html.erbファイルを開いて、以下のように変更しましょう。

app/views/memos/_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として設定することで、指定したアクションをログイン済みのユーザーに限定することができます。

コントローラにauthenticate_user!メソッドを設定する例
1
2
3
4
5
class SomeController < ApplicationController
before_action :authenticate_user!, only: [:action1, :action2]
# ... その他の処理... end

この設定により、action1action2を実行しようとするユーザーがログインしていない場合、自動的にログインページにリダイレクトされます。

メモ関連アクションで未ログインユーザーのアクセスを制限しよう

authenticate_user!メソッドを使用して、メモ関連アクション(new, create, edit, update, destroy)で未ログインユーザーのアクセスを制限します。

ログアウト状態で学習メモの新規作成画面にアクセスしよう

ログアウトした状態で、ブラウザのアドレスバーに /memos/new を入力して、未ログイン時に学習メモの新規作成ページにアクセスできるかを確認してみましょう。

ユーザーがログインしていなくても学習メモの登録画面にアクセスできる状態

newアクションで未ログインユーザーをログインページにリダイレクトさせよう

memosコントローラのnewアクションでユーザーがログインしていない場合に、ログインページにリダイレクトさせる設定を追加します。

memos_controller.rbファイルを開いて、以下の1行を追加してください。

app/controllers/memos_controller.rb
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アクションに加えて、createeditupdatedestroyアクションでも未ログインユーザーのアクセスを制限しましょう。

app/controllers/memos_controller.rb
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で学習メモの編集画面にアクセスし、自動的にログインページへリダイレクトされるかを確認してみましょう。

ログインページにリダイレクトされることを確認できれば、設定が成功しています。

ポイント
  1. 未ログイン時のリンクをビューで非表示にしても、直接リンクを使用するとアクセスできてしまう。
  2. 上記の対策として、コントローラ内にauthenticate_user!メソッドをbefore_actionとして設定する。
  3. authenticate_user!は、ユーザーが未ログインの場合に自動的にログインページへリダイレクトさせる。

メモ作成者以外のアクセス制限

現在、ログイン状態に関わらず他のユーザーの学習メモを「編集」や「削除」ができる状態です。たとえば、たけしさんがログインしていても、ももこさんの編集・削除リンクが表示されるケースがあります。

たけしさんがログインしている状態でも、ももこさんの編集・削除リンクが表示される

これを防ぐために、現在ログインしているユーザーとメモの作成者が一致する場合のみ、編集や削除を表示するようにビューを調整します。

たけしさんのユーザー情報でログインしましょう

以下の情報を使用してログインしておきましょう。

  • メールアドレス:fuga@example.com
  • パスワード:000000
編集と削除リンクを表示する条件を設定しましょう

現在ログインしているユーザーがメモの作成者である場合のみ、編集と削除リンクを表示するように設定します。これを実現するためには、if current_user == memo.user条件を利用して、リンクの表示を制御します。

_memo.html.erbファイルで、編集と削除リンクの表示周りを以下のように変更しましょう。

/memo_app/app/views/memos/_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を構成する基本的な要素」の編集ページへ直接URL /memos/1/edit を使用してアクセスしましょう。現状では、たけしさんがももこさんの編集画面にアクセスし、編集が可能な状態になっていることを確認できます。

先ほどmemosコントローラのbefore_actionで設定したauthenticate_user!メソッドは、ユーザーがログインしているかどうかのみを判断し、ログインしていない状態で学習メモの編集画面にアクセスしようとすると、自動的にログインページへリダイレクトします。

しかし、現在のケースではユーザーがログインしているため、authenticate_user!メソッドはこの状況ではアクセス制限を適用しません。

そのため、メモの作成者と現在のログインしているユーザーが一致しない場合には、ルートページへリダイレクトさせる必要があります。

ぴかわかさん
ログインユーザーが自分のメモのみを編集・削除できるように制限しましょう

redirect_unless_creator!メソッドを定義して、メモ作成者とログインユーザーが一致しない場合はルートページにリダイレクトさせる処理を実装します。このメソッドを編集、更新、削除アクション実行前に適用するようにbefore_actionを設定します。

memos_controller.rbファイルを以下のように編集してください。

app/controllers/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を利用したログイン認証機能の実装は完了です!長い道のりでしたが、よく頑張りましたね!

ぴかわかさん