はじめに
今回は前回に引き続きタスクアプリにログイン周りのを追加していきます。
前回、ログイン/ログアウト機能を実装できました。今回は「ログインしていない場合の機能制限」や「ログインユーザーが閲覧できるデータの制限」など認証機能を活かした機能制限を実装していきます。
タスク管理の機能制限
まずはユーザーがログインしていない場合、タスク管理機能を利用できないよう制限します。
コントローラの「フィルタ(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
これでユーザー管理機能は管理権限を持つユーザーのみが使えるようになりました。
終わりに
今回は認証機能を活用して機能の利用制限を実装しました。
これでログイン周りの機能は一通り実装できました。
次回はデータの絞り込みやソート機能を追加します。
コメント