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

Bootstrap

はじめに

前回に続き、以前作成したタスク管理アプリにログイン機能を実装していきます。
今回はその第2弾です。

前回はログイン機能を追加する準備としてユーザー管理機能の基礎部分を実装しました。

しかし今のままでは、管理者以外のユーザーも管理機能を使えてしまいます。ログインしている管理者のみ使えるように制限をかけるために、先にログイン機能を実装します。

ログイン機能の実装

ログイン機能を実装します。ユーザーがログインするためのフォーム画面を表示し、送信されて来た譲歩をもとにユーザーを認証します。ログアウトも提供します。

コントローラの作成

Railsでログイン機能を実装する場合、よくSessionsControllerという名前でコントローラが作られます。SessionsControllerに追加するアクションは以下の通りです。

以下のコマンドを実行してSessionsControllerとnewアクションを作成します。

$ bin/rails g controller Sessions new

route.rbにget ‘sessions/new’が定義されていますので、以下の通りに編集します。

config/route.rb

Rails.application.routes.draw do
  get '/login', to: 'sessions#new'
  ・・・
end

ログインフォームを表示するアクションのURLを/loginに変更できました。

ログインフォームの表示

次に、ログインフォームの画面を作ります。
app/views/sessions/new.html.slimを修正します。

app/views/sessions/new.html.slim

h1 ログイン

= form_with scope: :session, local: true do |f|
  .form-group
    = f.label :email, 'メールアドレス'
    = f.text_field :email, class: 'form-control', id: 'session_email'
  .form-group
    = f.label :password, 'パスワード'
    = f.password_field :password, class: 'form-control', id: 'session_password'
  = f.submit 'ログインする', class: 'btn btn-primary'

/loginにアクセスすると以下の画面が表示されます。

ログインフォーム画面を作成できました。

ログインの実行

続いて、ログイン画面から送られる認証情報をもとにログインを実行する部分を作ります。

以下のルーティング設定を追加します。

config/route.rb

Rails.application.routes.draw do
  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  ・・・
end

次にSessionsController(app/controllers/sessions_controller.rb)にcreateアクションを追加します。送信されたパラメータを安全化するsession_paramsメソッドも追加します。

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  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
      render :new
    end
  end

  
  private

  def session_params
    params.require(:session).permit(:email, :password)
  end
end

createアクションでは、メールアドレスでユーザーを検索します。ユーザーが見つかった場合は、送られて来たパスワードによる認証をauthenticateメソッドを使って行います。

authenticateメソッドはUserクラスにhas_secure_passwordを記述した際に自動追加されました。受け取ったパスワードをハッシュ化した結果がUserオブジェクト内部に保存されているdigestと一致するか調べます。
一致している場合認証成功となりUserオブジェクト自身を、一致しない場合認証失敗となりfalseを返します。

認証成功時には、セッションにuser_idを格納します。

これにより以下の2種類を区別できます。

  • ・誰もログインしていない状態(session[:user_id]がnil)
  • ・誰かがログインしている状態(session[:user_id]にログイン中のユーザーのIDが入っている)

ユーザーがログインしている場合、セッションにユーザーIDが格納された状態になるため、ログイン後はセッションが生きている限り、以下のコードで簡単にユーザーを取得できます。

User.find_by(id: session[:user_id])

このログイン中のユーザーを取得する処理は頻繁に使うことになるので、ApplicationControllerにcurrent_userメソッドを定義して全てのコンローラから呼び出せるようにします。さらにhelper_method指定することで全てのビューからも使えるようにします。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  helper_method :current_user

  private

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

これでcurrent_userメソッドで簡単にUserオブジェクトを取得できるようになりました。

ログアウトの実装

現状ではログインしなくてもどの機能でも使えます。各種の機能をログインしている時だけ使えるように制限したいと思います。

その前段階としてログアウト機能を実装します。

ログアウト機能はsession[:user_id]にnilが入っている状態にすることで実現できます。

セッション内のuser_idの情報だけを消すには以下のようにします。

session.delete(:user_id)

しかし、今回はユーザーに紐づく他の情報もセッションに入れているため、ログアウト時にセッション内の全ての情報を削除できるように以下のコードを使用します。

reset_session

ログアウト処理を実行するdestroyアクションをSessionsController(app/controllers/sessions_controller.rb)に追加します。

まずはログアウトのためのルーティングを定義します。

config/route.rb

Rails.application.routes.draw do
  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'
  ・・・
end

次にアクションを追加します。

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  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
      render :new
    end
  end

  def destroy
    reset_session
    redirect_to root_url, notice: 'ログアウトしました。'
  end


  private

  ・・・
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'
          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

先ほどヘルパーに定義したcurrent_userメソッドを利用して、ログインしているか否かでリンクの表示を切り替えています。

今回はここまでです。

終わりに

今回はログイン/ログアウト機能を実装しました。しかしこのままではログインしなくてもどの機能でも使えてしまいます。

そこで、次回は認証機能を活かして「ログインしていない場合の機能制限」や「ログインユーザーが閲覧できるデータの制限」などの制限を追加します。

コメント

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