1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rails初学者によるRailsチュートリアル学習記録⑫ 第10章

Last updated at Posted at 2021-05-26

#目次

#1. はじめに

  • この記事は、Rails初学者の工業大学三年生がRailsチュートリアルの学習記録を
    つけるための記事です。
  • 筆者自体がRailsやWebについて知識が少ないので、内容の解釈などに
    間違いがある可能性があります。(その時はコメントで指摘してくださると助かります!)
  • Railsチュートリアル内ではRailsの内容以外にも、gitでのバージョン管理やHerokuを使ったデプロイも
    学習しますが、gitに関しては既に私が学習済みのため学習記録には記述しません。
  • 演習の記録も省略します。

#2. 第10章の概要
この章では、一度登録したユーザー情報を更新したり、ユーザーを削除する機能を追加します。
これらの機能を実装するときには、自分のプロフィールのみを編集できるようにし、
削除は管理者権限を持ったユーザーしか実行できないようにします。
また、ユーザー一覧ページを作成するindexアクションを実装し、そのページの表示でページネーションの使い方を学びます。

  1. ユーザーの更新機能の実装
    1. ユーザーを更新する
    2. 更新を実行できるユーザーを制限する
    3. フレンドリーフォワーディング
  2. ユーザーを一覧する
    1. indexアクション
    2. ページネーション
  3. ユーザーを削除する
    1. 管理ユーザーのクラスを作成する
    2. destroyアクション

#3. 学習内容
###1. ユーザーの更新機能の実装

####1-1. ユーザーを更新する
ユーザーを更新するまでの流れは、ユーザー登録の流れと似ています。
更新にはeditアクションとupdateアクションを使用しますが、
editアクションはnewアクション、updateアクションはcreateアクションと役割がほぼ一致しています。
そのため、editアクションでフォームを表示させるビューを作成して、updateアクションでフォームに入力された値を使用して
ユーザー情報を更新できるようにする。
以上の流れで更新機能を実装していきます。

まず、編集フォームを作成していきますが、ここで使用するフォームはユーザー登録フォームとほぼ同じです。
書かれているコードは全く同じで、違いはフォームが送信するリクエストの種類にあります。

フォームを作成する際に、<%= form_with(model: @user, local: true) do |f| %>というコードで始めます。
このコードはユーザー登録フォームと同じです。

しかし、ユーザー登録時にはPOSTリクエストがこのコードから発行されますが、
更新用フォームではPATCHリクエストがこのコードから発行されます。
どのようにしてリクエストが使い分けられるかというと、
引数に渡されている@userが新規作成されたオブジェクトか、既存のオブジェクトなのかをRailsが判断することで
使用するリクエストを使い分けています。

フォームの違いはそれくらいなので、updateアクションの内容に移ります。
updateアクションではフォームに入力された値でユーザー情報を更新します。
実装した以下のコードで説明をします。

updateアクション
def update
  @user = User.find(params[:id])
  if @user.update(user_params)
    flash[:success] = "Profile updated"
    redirect_to @user
  else
    render 'edit'
  end
end

ユーザー情報を更新するには、どのユーザーを更新するかを伝えてあげる必要があります。
それを行っているのがアクション内の一行目で、findメソッドを使用してIDからユーザーを探しています。
そしてupdateメソッドの引数にuser_paramsを渡して更新を実行しています。

なぜここでuser_paramsという変数が使われているかというと、Strong Parametersを使ってフォームから受け取る属性を
制限する必要があるためです。
現段階ではあまり意味はないですが、この後実装する管理者属性がユーザーによって自由に設定されてしまうと、
誰でも管理者権限を得ることができてしまうため、このコードは重要です。

updateメソッド以降はcreateアクションと同じで、成功した場合はフラッシュメッセージを表示した
プロフィールページへリダイレクト。
失敗した場合はrenderメソッドで再度フォームを表示させています。

また、このフォームからパスワードも変更できますが、パスワードを変更しない場合に
現在のパスワードの入力を求めないで入力欄を空のままでも更新できるようにするには、
ユーザーモデルのバリデーションに、パスワードが空だった時の例外処理を加える必要があります。
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
このコードを加えることで、パスワードが空でも更新はできるようになります。

####1-2. 更新できるユーザーを制限する
これまででユーザーの更新機能は実装できましたが、このままでは誰でもurlを直接入力すれば、
全てのアクションを実行できてしまいます。
先ほど実装したeditアクションを例に挙げると、誰でも他のユーザーの情報を更新できてしまうということです。

このようなセキュリティ上の問題を解消するために、特定のアクションの実行にはログインを要求したり、
許可されていないユーザーからのアクセスを防ぐためにページを保護していきます。

まずは、先ほど実装したeditアクションとupdateアクションをログインしていないユーザーが実行しようとしたときに、
ログインを要求したいと思います。

このような処理を実装したいときはbeforeフィルターを使用します。
beforeフィルターは、before_actionというメソッドを使って、
処理が実行される前に指定したメソッドが実行されるようにする仕組みです。
ここではeditアクションか、updateアクションが実行される前に、実行しようとしているユーザーが
ログイン済みかどうかを確認して、ログインしていなかったらログインページにリダイレクトする処理を実行します。

上記のログイン済みのユーザーかどうかを確認するメソッドをlogged_in_userメソッドとして定義します。
そして、before_actionメソッドでlogged_in_userメソッドが事前に実行されるようにします。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update] #editアクション, updateアクションのみに適用


  def logged_in_user
    unless logged_in?
      flash[:danger] = "Please log in."
      redirect_to login_url
    end
  end

end

上記のコードを実装しても、ログインをすれば他のユーザーの情報も更新できるので、
次に正しいユーザーを要求するメソッドを実装します。
これによりユーザーが自分の情報のみを更新できるようになります。

ここでは、current_user?という渡されたユーザーがログイン中のユーザーかどうかを論理値で返すメソッドと、
そのメソッドを使ってurl上のIDのユーザーと比較を行うcorrect_user
を定義します。
2つのメソッドでユーザー更新ページにアクセスしたときに、アクセス先のurlユーザーIDとログイン中のユーザーのユーザーIDを比較します。
そして、correct_userメソッドも先ほどのlogged_in_userメソッドと同様に事前に実行されるようにします。

app/helpers/sessions_helper.rb
def current_user?(user)
  user && user == current_user
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user, only: [:edit, :update]

  def current_user?(user)
    user && user == current_user
  end
end

####1-3. フレンドリーフォワーディング
ここまででセキュリティ上の問題を解消でき、欲しかった機能が実装できたわけですが、
ユーザーの体験を考えると改善すべき小さな問題が1つ残っています。

それは、ログインしていない時に自分の更新ページにアクセスしようとしたときに発生する問題です。
ログインしていないため、ログインページにリダイレクトされるまでは問題ないのですが、
ログインした後には自分のプロフィールページにリダイレクトされてしまいます。

アプリケーションとして親切な振る舞いは、ログインした後には初めにアクセスしようとしていた更新ページにリダイレクトすることです。
このような処理をフレンドリーフォワーディングと呼びます。
この処理を実装するには初めのリクエストのページを保存しておく必要があります。

実装するメソッドは2つです。
1つ目はアクセスしようとしたURLを保存するstore_locationメソッド。
2つ目は記憶したURLにリダイレクトするredirect_back_orメソッドです。
どちらもSessionsヘルパーに定義します。

app/helpers/sessions_helper.rb
# アクセスしようとした URL を覚えておく
def store_location
  session[:forwarding_url] = request.original_url if request.get?
end

# 記憶した URL(もしくはデフォルト値)にリダイレクト
def redirect_back_or(default)
  redirect_to(session[:forwarding_url] || default)
  session.delete(:forwarding_url)
end

store_locationメソッドはgetリクエストのみをsession変数の:forwarding_urlキーに保存しています。
redirect_back_orメソッドでは先ほど保存したURLか引数に渡されたURLにリダイレクトします。
その後に保存してあるURLを削除しているのは、ブラウザを閉じずにログアウトして再度ログインしたときに
保存していたURLにリダイレクトしてしまうためです。

この2つのメソッドでフレンドリーフォワーディングが実装できるので、
store_locationメソッドをログイン済みユーザーか確認するlogged_in_userメソッドの中で実行して
URLを保存し、ログインを行うcreateアクションの中でredirect_back_orメソッドを実行します。

###2. ユーザーを一覧する
####2-1. indexアクション
indexアクションは、すべてのユーザーを一覧表示するためのアクションです。
アクションの内容やビューは簡単で、indexアクションですべてのユーザーを取り出し、
ビューでそれをeachメソッドを使って1人ずつ表示させるだけです。
このアクションもbeforeフィルターをかけて、ログインしているユーザーしか実行できないようにしています。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]

  def index
    @users = User.all
  end
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

####2-2. ページネーション
このアプリケーションではユーザーが自分で登録しない限り増えませんが、
実際のアプリケーションでは様々な人が利用します。
その場合、先ほど実装した一覧ページではすべてのユーザーを1つのページに一度に表示させているので、
動作が重かったり見づらかったりという問題があります。

この問題を解消するためにページネーションを実装していきます。
Railsにはページネーションを実装するメソッドが様々ありますが、
ここではwill_paginateメソッドを使います。
このメソッドを使用するためにはwill_paginate gembootstrap-will_paginate gemが必要です

ページネーションを実装するにはビューとアクションの両方に変更を行います。
ビューへの変更はユーザー1人ずつ表示している部分を、
<%= will_paginate %>で囲みます。

これはページネーションのリンク(下図)を表示するためのコードです。
スクリーンショット 2021-05-26 121048.png
二カ所にこのコードを書くことページの上と下にページネーションを配置することができます。

アクションへの変更の内容はallメソッドでユーザーを取得していた状態から、paginateメソッドでユーザーを取得するように変更します。
変更前:@users = User.all → 変更後:@users = User.paginate(page: params[:page])
will_paginateメソッドを使用するにはこのpaginateメソッドの実行結果が必要で、
このメソッドは引数にハッシュを取ります。
ハッシュのキーは:pageで値がページ番号となります。params[:page]という変数はwill_paginateにより自動生成されたもので、
この値に基づいてデータベースからデータをまとめて取り出します。
デフォルトでは30個のデータを取り出して1ページに表示させます。

出来上がった一覧ページが以下の画像です。
スクリーンショット 2021-05-26 122530.png

Railsチュートリアルではページネーションを実装する前にFaker gemというgemを使用して、
実在しそうなユーザー名を生成し、サンプルユーザーを埋め込みRubyで99人作成することで
ページネーションの動きを体験できるようにしていました。

###3. ユーザーを削除する
####3-1. 管理ユーザーのクラスを作成する
ここからユーザーを削除する機能の実装に入っていきますが、その前にユーザーの削除を実行できる
管理者権限を実装するためにUserモデルに変更を加えます。

adminという論理値を取る属性をUserモデルに追加するために以下のコマンドを実行します。
rails generate migration add_admin_to_users admin:boolean
これにより、admin?メソッドが利用できるようになり、
このメソッドを使用して管理者ユーザーのみがdestroyアクションを実行できるようにします。

####3-2. destroyアクション
destroyアクションによってユーザーの削除を実行できるようにするために、
ビューにdestroyアクションへのリンクを追加します。
実は、先ほどのユーザー一覧ページの画像を撮ったのが10章を全部終えた後なので、
先ほどの画像には既にdeleteリンクが実装されています。
スクリーンショット 2021-05-26 193230.png

このリンクを表示させるためのビューが以下のコードです

ユーザー削除用リンクの実装(管理者にのみ表示)
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %>
  <% end %>
</li>

destroyアクションを実行するためのDELETEリクエストを発行するために:method: :deleteが使われています。
そして、confirmでリンクがクリックされた時に、「You sure?」という警告を表示しています。

destroyアクションの処理の内容が以下のコードです。

destroyアクション
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user, only: [:edit, :update]
  before_action :admin_user, only: :destroy

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end


  def admin_user
    redirect_to(root_url) unless current_user.admin?
  end
end

destroyメソッドでユーザーを削除し、フラッシュメッセージを表示させたユーザー一覧ページにリダイレクトさせる処理と、
そしてログイン中のユーザーが管理者ユーザーかどうかを確認するadmin_userメソッドを追加しています。

destroyアクションはbeforeフィルターで、ログイン中のユーザーかつadmin_userがtrueとなるユーザー、つまり管理者ユーザーのみが
実行できるようになりました。

#4. 終わりに
この章でユーザーの更新や削除の機能、ユーザー一覧を表示するページが実装できました。
ユーザーが実行できる機能を制限したり、ユーザーがアクセスしようとしたページにログイン後リダイレクトされるというのは
普段アプリケーションを使う中で体験していることですが、いざ自分で開発をする際には
あまり気が付かなそうなことだと思うので、Railsチュートリアルを通してUXについてまで学べているような気がします。

この次の11, 12章では様々なユーザーがアプリケーションを利用する上で必要となる、
アカウントの有効化の機能とパスワードの再設定機能を実装していきます。
就活の準備も始まっていますが、残り4章、頑張って進めます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?