Rails 6 でTodoApp作る part5

プロフィールの編集を制限する

自分以外のユーザーの編集をできなくする

現状、ログインしていれば全てのユーザーの編集ページに入ることができます。


ユーザー"panda"でログイン中

[ 自分のページ ] f:id:yukitoku_sw:20200120201930p:plain

[ rakudaさんのページ ] f:id:yukitoku_sw:20200120202003p:plain

これを対処します

  • 自分のページのみ編集ページへのリンクを表示する

  • URL直打ちでのアクセスを防ぐ


自分のページのみ編集ページへのリンクを表示する

[ views/users/show.html.slim ]

h1 アカウントの詳細

.nav.justify-content-end
  = link_to '一覧', users_path, class: 'nav-link'

table.table.table-hover
  tbody
    tr
      th 名前
      td = @user.name
    tr
      th メールアドレス
      td = @user.email
    tr
      th 登録日
      td = @user.created_at
    tr
      th 更新日
      td = @user.updated_at

- if current_user.id == @user.id     # 追加
  = link_to '編集', edit_user_path, class: 'btn btn-primary mr-3'

- if current_user.id == @user.idによりログイン中のユーザーのidと詳細ページのユーザーのidが同じ時のみ編集ページへのリンクが表示されるようになります


ユーザー"panda"でログイン中

[ 自分のページ ] f:id:yukitoku_sw:20200120203359p:plain

[ rakudaさんのページ ] f:id:yukitoku_sw:20200120203420p:plain

自分のshowページでのみ、editページへのリンクが表示されるようになりました!


URL直打ちでのアクセスを防ぐ

[ users_controller.rb ]

class UsersController < ApplicationController
  before_action :correct_user, only: [:edit, :update, :destroy]

  def index
    @users = User.all
  end

  (省略)

  private

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

    # before_action
    def correct_user
      user = User.find(params[:id])
      redirect_to root_url if current_user != user
    end
end

correct_userをprivateメソッドとして定義し、before_actionで呼び出します。 users_controller内で[:edit, :update, :destroy]のアクションを実行する前に、実行しようとするユーザー(current_user)と編集されようとしているユーザーが一致していなければroot_urlに飛ばします。

これで自分のユーザーアカウントを他のユーザーに編集されないようになります。


フィルタを使い重複を避ける

taskコントローラーを作る際に作ったフィルタをUserバージョンでもう一度やります。

[ users_controller.rb(現在) ]

class UsersController < ApplicationController
  before_action :correct_user, only: [:edit, :update, :destroy]

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to users_url, notice: "ユーザー「#{@user.name}」を登録しました。"
    else
      render :new
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      redirect_to users_url, notice: "ユーザー「#{@user.nsame}」を更新しました。"
    else
      render :edit
    end
  end

  def destroy
    @user = User.find(prams[:id])
    @user.destroy
    redirect_to root, notice: "ユーザー「#{@user.name}」を削除しました。"
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

    # before_action
    def correct_user
      user = User.find(params[:id])
      redirect_to root_url if current_user != user
    end
end

上記は現在のusers_controllerです。 @user = User.find(prams[:id])が連発されているのでフィルタを使いすっきりさせます。


before_action :set_user, only: [:show, :edit, :update, :destroy]

def set_user
  @user = User.find(prams[:id])
end

set_userを定義し、before_actionで呼び出します。
指定されたアクションが実行される前に@user = User.find(prams[:id])を行います。 そのため、各アクションから@user = User.find(prams[:id])の記述を消すことができるので、各アクションの中身がシンプルになります。
トータルの行数は増えてしまいますが、、、

[ users_controller.rb(変更後) ]

class UsersController < ApplicationController
  before_action :correct_user, only: [:edit, :update, :destroy]
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  def index
    @users = User.all
  end

  def show
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to users_url, notice: "ユーザー「#{@user.name}」を登録しました。"
    else
      render :new
    end
  end

  def edit
  end

  def update
    if @user.update(user_params)
      redirect_to users_url, notice: "ユーザー「#{@user.nsame}」を更新しました。"
    else
      render :edit
    end
  end

  def destroy
    @user.destroy
    redirect_to root, notice: "ユーザー「#{@user.name}」を削除しました。"
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

    # before_action
    def correct_user
      user = User.find(params[:id])
      redirect_to root_url if current_user != user
    end

    def set_user
      @user = User.find(prams[:id])
    end
end


i18nで日本語対応にする

所々、英語が表示されちゃってる部分を日本語にしていくう


まず、

[ Gemfile ]

gem 'rails-i18n'
bundle

以前設定したlocale.rbをいちよう確認。

[ config/initializers/locale.rb ]

Rails.application.config.i18n.default_locale = :ja


これだけで日本語になります。(完全な英語の状態を撮り忘れました。)

f:id:yukitoku_sw:20200120220505p:plain


しかし、まだちょっと残っている英語も日本語にします。

config/locales/ja.ymlと言うファイルを作成します。

そして、内容修正

[ config/locales/ja.yml ]

ja:
  activerecord:
    models:
      task: タスク
    attributes:
      task:
        title: タイトル
        description: 詳しい説明
  • モデル名はactiverecoredのmodelsの中に
  • 属性名はactiverecoredのattributesの中に定義します。

サーバーを再起動すると・・

f:id:yukitoku_sw:20200120221050p:plain

ちょっとあれですね・・・

f:id:yukitoku_sw:20200120232443p:plain

こんな感じにします。

[ tasks_controller.rb ]

- if task.errors.any?
  div#error_explanation
    .alert.alert-danger
      = "#{task.errors.count}種類のエラーがあります"
      ul
        - task.errors.full_messages.each do |message|
          li = message
      
= form_with model: task, local: true do |f|
  (省略)

application.html.slimも変更しなくては・・

[ layouts/application.html.slim ]

doctype html
html
  head
    title
      | TodoApp
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
  body
    = render 'layouts/header'
    .container
      - flash.each do |message_type, message|
        = content_tag(:div, message, class: "alert alert-#{message_type}")
      = yield

noticeからflashに変更したのでコントローラーも変更しなくては・・・

[ tasks_controller.rb ]

(省略)
  def create
    @task = current_user.tasks.new(task_params)
    if @task.save
      flash[:success] = "タスク「#{@task.title}」を登録しました。"
      redirect_to tasks_url
    else
      render :new
    end
  end

(省略)

  def update
    if @task.update(task_params)
      flash[:success] = "タスク「#{@task.title}」を更新しました!"
      redirect_to tasks_url
    else
      render :edit
    end
  end

  def destroy
    @task.destroy
    flash[:warning] = "タスク「#{@task.title}」を削除しました。"
    redirect_to tasks_url
  end

(省略)


こっちも・・・

[ users_controller.rb ]

(省略)
  
  def create
    @task = current_user.tasks.new(task_params)
    if @task.save
      flash[:success] = "タスク「#{@task.title}」を登録しました。"
      redirect_to tasks_url
    else
      render :new
    end
  end

(省略)

  def update
    if @task.update(task_params)
      flash[:success] = "タスク「#{@task.title}」を更新しました!"
      redirect_to tasks_url
    else
      render :edit
    end
  end

  def destroy
    @task.destroy
    flash[:warning] = "タスク「#{@task.title}」を削除しました。"
    redirect_to tasks_url
  end

(省略)


あと、sessions_controllerにも・・
flash.now[:danger] = 'メールアドレスとパスワードの組み合わせが存在しません'も追加してます!

[ sessions_controller.rb ]

(省略)

  def create
    user = User.find_by(email: session_params[:email])

    if user&.authenticate(session_params[:password])
      session[:user_id] = user.id
      redirect_to root_url, notice: 'ログインしました'
    else
      flash.now[:danger] = 'メールアドレスとパスワードの組み合わせが存在しません'
      render :new
    end
  end

  def destroy
    reset_session
    flash[:success] = 'ログアウトしました'
    redirect_to login_url
  end

(省略)


usersformも直しておきます。

[ users/_form.html.slim ]

- if user.errors.any?
  div#error_explanation
    .alert.alert-danger
      = "#{task.errors.count}種類のエラーがあります"
      ul
        - user.errors.full_messages.each do |message|
          li = message
      
= form_with model: user, local: true do |f|
(省略)


今日はここまで、、


次回、rspec導入