Rails 総復習1ヶ月チャレンジ 4日目(Railsチュートリアル編)
チャレンジ4日目です!今日はまたRailsチュートリアルの続きからやっていきます!
4日目(Ruby on Rails チュートリアル 10章ユーザーの更新・表示・削除)
allow_nil
ユーザー情報編集の際に名前やemail情報だけを変更しようとしてもUserモデルにパスワードの長さに対するバリデーションを設定している為、このバリデーションに引っかかってしまいます。そういう場合はallow_nil :trueを設定しパスワードのバリデーションが空だった場合の例外処理を加えます。
#app/models/user.rb class User < ApplicationRecord . . . has_secure_password validates :password, presence: true, length: { minimum: 6 }, allow_nil: true . . . end
新規ユーザー登録時に空のパスワードが有効になってしまうのではないか?という心配が出てきますが、これはhas_secure_passwordが解決しています。今回追加したバリデーションとは別にhas_secure_passwordがオブジェクト生成時に存在性を検証するようになっている為、空のパスワード(nil)が新規ユーザー登録時に有効になることはありません。
認可(ログインしているユーザーかつユーザー本人のみ編集を許可する)
認可のシステムを実装します。 まずはログインしているユーザーのみ編集できる状況を作るため、ユーザーにログインを要求する仕組みを実装します。
#app/controllers/users_controller.rb class UsersController < ApplicationController before_action :logged_in_user, only: %i[edit update] . . . private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end end end
before_actionメソッド使い、privateの中で定義したlogged_in_userメソッドを呼び出します。使いたい場面は編集時なので、only: %i[edit update]でedit,updateアクション呼び出し前に実行するようにします。
続いてユーザー本人のみが情報を編集できるようにします。まずはSessionsヘルパーでユーザーがcurrent_userであればtrueを返すcurrent_user?メソッドを定義します。
#app/helpers/sessions_helper.rb module SessionsHelper . . . # 渡されたユーザーがカレントユーザーであればtrueを返す def current_user?(user) user && user == current_user end . . . end . . . end
なお下記のコードはuser == current_userでもほぼ問題なく動くと思いますが、userがnilになってしまったレアケースもキャッチするためにuser &&を記載しています。
user && user == current_user
そしてUsersコントローラーにて正しいユーザーかどうか確認するcorrect_userをprivate内に定義し、before_actionでedit,updateのアクション前に呼び出すようにします。
#app/controllers/users_controller.rb class UsersController < ApplicationController before_action :logged_in_user, only: %i[edit update] before_action :correct_user, only: %i[edit update] . . . def edit end def update if @user.update(user_params) flash[:success] = "Profile updated" redirect_to @user else render 'edit' end end . . . private . . . # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end end # 正しいユーザーかどうか確認 def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless current_user?(@user) end end
フレンドリーフォワーディング
認可システムによって保護されたページにアクセスしようとするとこちらで指定されたページに移動させるようになっていますが、ユーザーが元々行きたかったページに移動させるのが望ましいです。今回はその実装をやっていきます。
流れとしてはざっくりこのような形です。
- (アクセス権限のない)ユーザーがアクセスしようとしたURLを記憶 ↓
- ユーザーがアクセス権限を取得した後、記憶したURLにリダイレクトさせる
では実装を行なっていきます!
まずはSessionsヘルパーに必要なメソッドを定義します。
#app/helpers/sessions_helper.rb module SessionsHelper . . . # 記憶したURL(もしくはデフォルト値)にリダイレクト def redirect_back_or(default) redirect_to(session[:forwarding_url] || default) session.delete(:forwarding_url) end # アクセスしようとしたURLを覚えておく def store_location session[:forwarding_url] = request.original_url if request.get? end end
まず転送先のURLを保存する仕組みはユーザーログインさせた時と同様、session変数を使います。
- stroe_locationメソッド
リクエストが送られたURL(request.original_urlでリクエスト先が取得できます)をsession変数の:forwarding_urlキーに格納しています。if request.get?部分でGETリクエストが送られた時だけ格納するようにしています。
- redirect_back_orメソッド
リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトします。
session[:forwarding_url] || default
上記コードで値がnilでなければsession[:forwarding_url]を評価し、nilの場合はdefault URLにリダイレクトさせるようにしています。その後の行session.delete(:forwarding_url)で転送用のURLを削除しています。これをしないと次回ログイン時に保護されたページに転送されてしまいます。
まずstore_locationメソッドをUsersコントローラーに追加します。
#app/controllers/users_controller.rb class UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] before_action :correct_user, only: [:edit, :update] . . . private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end . . . end
続いてredirect_back_orメソッドをSessionsコントローラーに追加します。
#app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end . . . end
これで実装完了です!
パーシャルのrender
ユーザー一覧ページでパーシャルのrenderを実装します。
まず現状のコードはこのようになっています。
#app/views/users/index.html.erb <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul>
一旦下記にリファクタリングします。
<ul class="users"> <% @users.each do |user| %> <%= render user %> <% end %> </ul>
これはrenderでパーシャルに対してではなく、Userクラスのuser変数に対して実行しています。この場合はRailsは自動的に_user.html.erbという名前のパーシャルを探しにいくようにしています。(パーシャルは作成しておく必要があります)
ただこれはさらにリファクタリングできます。
<ul class="users"> <%= render @users %> </ul>
renderを@users(この場合indexアクション内でUser.allを格納している変数)に対して直接実行しています。Railsではモデルインスタンスの集合体をrenderの引数に渡すとパーシャル(今回は_user.html.erb)をレンダリングします。
boolean
管理ユーザーカラムを設定する際の属性の型として使用する。
#ターミナル $ rails generate migration add_admin_to_users admin:boolean
#db/migrate/[timestamp]_add_admin_to_users.rb class AddAdminToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :admin, :boolean, default: false end end
booleanを設定することにより、カラム?(admin?)、toggle(デフォルトでfalseをtrueにする)メソッドが使えるようになる。
今回は以上です!