第5回 Ruby on Rails〜タスク管理アプリケーション 機能追加③ (ログイン機能 part3)〜

Ruby

はじめに

今回は前回に引き続きタスクアプリにログイン周りのを追加していきます。

前回、ログイン/ログアウト機能を実装できました。今回は「ログインしていない場合の機能制限」や「ログインユーザーが閲覧できるデータの制限」など認証機能を活かした機能制限を実装していきます。

タスク管理の機能制限

まずはユーザーがログインしていない場合、タスク管理機能を利用できないよう制限します。

コントローラの「フィルタ(Filter)」という機能を使用します。
フィルタを使うことでアクションの処理の前後に任意の処理を挟むことができます。

今回は、タスク管理機能の各アクションの前に、ユーザーがログインしているか調べ、ログインしていない場合はログイン画面にリダイレクトさせるフィルタを実装します。

before_actionメソッドを使いlogin_requiredというメソッドをフィルタとして登録します。
before_actionはonlyなどで適用するアクションを指定できますが、今回は指定しません。

今後タスク管理機能以外にも、ログインしているかどうかで制限を設け流可能性があるため、login_requiredフィルタはApplicationController(app/controllers/application_controller.rb)に追加します。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  helper_method :current_user
  before_action :login_required

  private

  def current_user
    @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
  end

  def login_required
    redirect_to login_url unless current_user
  end
end

これで全てのアクションの処理前にユーザーがログイン済みかどうかチェックが入り、ログインしていない場合ログイン画面が表示されます。

しかしこのままではログイン画面を表示するアクションの前にもlogin_requiredフィルタが実行されてしまうため無限にredirectが行われます。

skip_before_actionを利用し、SessionsControllerではlogin_requiredフィルタを通らないようにし、ログインしていなくても使えるようにします。

app/controllers/application_controller.rb

class SessionsController < ApplicationController
  skip_before_action :login_required
  ・・・
end

これでログインしていないユーザーに利用制限をかける実装ができました。

管理できるデータの制限

次にログイン中のユーザーのタスクデータのみ管理できるように制限します。

そこで特定のユーザーに紐づいたタスクデータだけを扱うようプログラムを以下の手順で変更します。

  • ・tasksテーブルにuser_idカラムを追加してUserとTaskを紐づける
  • ・UserとTaskの紐付けを行うためにRailsの「関連」を定義する
  • ・ログイン中のユーザーに紐づいたTaskデータを登録できるようにする
  • ・一覧、詳細、変更など既存レコードを扱う機能では、ログイン中のユーザーに紐づくデータのみを扱うようにする

UserとTaskの紐付け

データベース上でUserとTaskを紐づけます。UserとTaskは1対多の関係になるためTaskにuser_idを持たせます。

ジェネレータでマイグレーションを作成します。

$ bin/rails g migration AddUserIdToTasks
Running via Spring preloader in process 93366
      invoke  active_record
      create    db/migrate/20210404171225_add_user_id_to_tasks.rb

作成したファイルを変更します。

class AddUserIdToTasks < ActiveRecord::Migration[6.1]
  def up
    execute 'DELETE FROM tasks;'
    add_reference :tasks, :user, null: false, index: true
  end

  def down
    remove_reference :tasks, :user, index: true
  end
end

既存のタスクは紐づくユーザーが決められないため、NOT NULL製薬に引っかかります。そのため既存のタスクを全て削除してからカラム追加を行います。

これでデータベース上でUserとTaskが紐づきました。

関連の定義

Railsにはデータベース上の紐付けを前提として、モデル上の紐付けを定義する「関連(Association)」という仕組みがあります。

これにより、関連するデータにアクセスできます。

app/models/user.rb

class User < ApplicationRecord
  has_secure_password

  validates :name, presence: true
  validates :email, presence: true, uniqueness: true

  has_many :tasks
end

app/models/task.rb

class Task < ApplicationRecord
  before_validation :set_nameless_name
  validates :name, presence: true
  validates :name, length: { maximum: 30 }
  validate :validate_name_not_including_comma

  belongs_to :user


  private
  ・・・
end

has_manyはそのクラス(User)のidを外部キーとして抱える他のクラス(Task)があり、そのレコードが複数登録可能であることを表します。

belongs_toはそのクラス(Task)が、別のクラス(User)に従属しており、従属先クラスのidを外部キーとして抱えることを表します。

こうすることでuser.tasksといったメソッドで紐づくTaskオブジェクトの一覧を取得できます。Taskクラスのインスタンスはtask.userで紐づくUserオブジェクトを取得できます。

ログインユーザーのTaskデータの登録

ユーザーに紐づくTaskデータを扱う準備ができました。

登録アクションを変更します。

class TasksController < ApplicationController
  ・・・
  def new
    @task = Task.new
  end

  def create
    @task = current_user.tasks.new(task_params)

    if @task.save
      redirect_to tasks_url, notice: "タスク 「#{@task.name}」を登録しました。"
    else
      render :new
    end
  end
 ・・・
end

ログインしているuser_idを代入した状態でTaskオブジェクトを登録することができます。

ログインユーザーのタスクデータの読み出し

登録以外の変更、詳細、一覧、削除といった機能はデータベースからTaskデータを取り出す処理を含んでいます。

データベースからデータを取り出す処理に絞り込み条件を加えて、ログイン中のユーザーに紐づくデータのみを扱うようにします。

タスク一覧を変更します。TaskControllerのindexアクションを変更します。

class TasksController < ApplicationController
  def index
    @tasks = current_user.tasks
  end
  ・・・
end

次にshow,edit,update,destroyアクションを変更します。

class TasksController < ApplicationController
  def index
    @tasks = current_user.tasks
  end

  def show
    @task = current_user.tasks.find(params[:id])
  end

  def new
    @task = Task.new
  end

  def create
    @task = current_user.tasks.new(task_params)

    if @task.save
      redirect_to tasks_url, notice: "タスク 「#{@task.name}」を登録しました。"
    else
      render :new
    end
  end

  def edit
    @task = current_user.tasks.find(params[:id])
  end

  def update
    task = current_user.tasks.find(params[:id])
    task.update!(task_params)
    redirect_to tasks_url, notice: "タスク「#{task.name}」を更新しました。"
  end

  def destroy
    task = current_user.tasks.find(params[:id])
    task.destroy
    redirect_to tasks_url, notice: "タスク「#{task.name}」を削除しました。"
  end


  private

  def task_params
    params.require(:task).permit(:name, :description)
  end
end

これで認証機能を活かしてアプリケーション内のデータをログイン中のユーザーの分だけ扱えるように制限できました。

管理機能の利用制限

管理権限を持つユーザーだけがユーザー管理機能を利用できるよう修正します。

ユーザー管理機能の修正をする前にログイン中のユーザーが管理権限を持っている場合のみ、ユーザー一覧画面へのリンクをメニューに表示するようにします。

app/views/layouts/application.html.slim

doctype html
html
  head
    title
      | Taskapp
    meta[name="viewport" content="width=device-width,initial-scale=1"]
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
  body
    .app-title.navbar.navbar-expand-md.navbar-light.bg-light
      .navbar-brand Taskapp

      ul.navbar-nav.ml-auto
        - if current_user
          li.nav-item= link_to 'タスク一覧', tasks_path, class: 'nav-link'
          - if current_user.admin?
            li.nav-item= link_to 'ユーザー一覧', admin_users_path, class: 'nav-link'
          li.nav-item= link_to 'ログアウト', logout_path, method: :delete, class: 'nav-link'
        - else
          li.nav-item= link_to 'ログイン', login_path, class: 'nav-link'
    .container
      - if flash.notice.present?
        .alert.alert-success= flash.notice
      = yield

これでログインしているユーザーが管理権限を持っている場合のみメニューにユーザー一覧へのリンクが表示されます。

次に管理権限を持っているユーザーだけがユーザー管理機能を使えるようにします。ユーザー管理機能(Admin::UsersController)に、管理者以外の利用を禁止するフィルタとしてrequire_adminメソッドを追加します。

app/controllers/admin/users_controller.rb

class Admin::UsersController < ApplicationController
  ・・・

  private

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

  def require_admin
    redirect_to root_url unless current_user.admin?
  end
end

これでユーザー管理機能は管理権限を持つユーザーのみが使えるようになりました。

終わりに

今回は認証機能を活用して機能の利用制限を実装しました。
これでログイン周りの機能は一通り実装できました。

次回はデータの絞り込みやソート機能を追加します。

コメント

タイトルとURLをコピーしました