しおブログ言うてますけど

技術ブログ言うてますけど技術以外のことも発信すると思われ。RubyとかRuby on Railsとか生き方とかジャンルにこだわらず自分の好きなように。初心者なのでお手柔らかに。

ログイン機能の実装(モデルのアソシエーションまで)③

ログイン機能の実装②まではログイン情報を取得するまで進めた。このままではログインしたままになってしまうので、ここからはログアウト機能の実装をしていきたいと思う。

ログアウト機能の実装

ユーザーがログイン状態ではsession[:user_id]にユーザーのIDが入っていることだった。ログアウトの状態にするにはsession[:user_id]にnilが入っている状態に変えればいいということになる。

session.delete(:user_id)

セッションからuser_idの情報だけピンポイントで消すには上記のようにできる。
セッション内のすべてのデータを削除したい場合には下記のようにする。

reset_session

アクションの設定

ログアウト機能を実装するためにsessionsコントローラーのdestroyアクションを編集していく。

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

これでdestroyアクションはできた。
続いてログアウトのリンクを追加する。

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'

ログインしているときはタスク一覧、ユーザー一覧、ログアウトの表示が見え、ログインしていないときはログインできるようにログインの表示がされるようになっています。

ログインしていない場合の制限

制限をかけていない今のままではログインしていなくても他の機能が使えたりしてあまり意味がないのでログインしていなければタスク管理を利用できなくするという制限をかけて行きたいと思う。
そのためには「フィルター」という機能を使う。
フィルターとは一般的な処理の流れに従ってメインの処理をアクションと考えると、フィルターとはすべてのアクションに対して、前処理・後処理を設定する役割を提供するものと言える。
つまり、前処理・後処理を必要とする場合だけ、フィルターを設定する。 before_actionでアクションの前処理として他の機能へのリダイレクトを実装するとリダイレクトが行われ、アクションには到達しない。

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

  ....ruby

  def login_required
    redirect_to login_url unless current_user
  end

before_action :login_requiredでアクションを実行する前にlogin_requiredが呼び出される。login_requiredではログインしていなければログイン画面にリダイレクトするという機能を実装しているので、もしログインしていなければアクションは実行されない。
1つ問題があり、アプリケーションのどのURLをリクエストしてもリダイレクトが行われてしまう。
これを防ぐために特定のコントローラーにおいて親クラスなどですでに定義済みのフィルタを通らないようにするにはskip_before_actionを利用する。 sessions_controller.rbを編集し、skip_before_actionを利用する。

class SessionsController < ApplicationController
  skip_before_action :login_required
   ...

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

ログインしているユーザーのデータだけを扱えるようにする

ログインしているユーザーのデータだけを扱えるようにするには特定のユーザーに紐付いたTaskデータだけを扱うようにプログラムを変更しなければいけいない。
そのためには

  • UserとTaskを紐付ける。具体的にはtasksテーブルにuser_idというカラムを追加してタスクを所有しているユーザーのidが格納されるようにする。
  • UserとTaskの紐付けを簡単に扱えるよう、Railsの「関連」を定義する
  • ログインしているユーザーに紐付いたTaskデータを登録できるようにする。
  • 一覧、詳細、変更など既存のレコードを扱う機能ではログインしているユーザーに紐付くデータだけを扱うようにする。

とくことが必要である。

UserとTaskを紐付ける

1つのユーザーに対して複数のTaskが存在する1対多の関係になるので「多」にあたるTaskにuser_idを持たせる。

$ rails g migration AddUserIdToTasks

上記のコマンドでマイグレーションファイルを作成する。

class AddUserIdToTasks < ActiveRecord::Migration[5.2]
  def change

    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
end

execute 'DELETE FROM tasks;'SQL文で今まで作られたタスクをすべて消すというもの。

既存のタスクがある状態でタスクとユーザーの関係を表すカラム(user_id)を追加すると、既存のタスクに紐付くユーザーを決められず、NOT NULL制約に引っかかってしまいます。そのため既存のタスクをすべて削除してから、カラム追加を行うようにしている。

ということなので、これをマイグレーション実行する。
これでTaskとUserが紐付く。

Railsで「関連」という仕組みを利用するには、モデルクラス同時の紐付けを定義する。
UserとTaskは1対多の「関係」にあたるので、Userクラスにはhas_many :tasks、Taskクラスにはbelongs_to :userを定義する。
ここは一番理解しにくかったが、1対多の関係において、1のモデルクラスにはhas_many、多のモデルクラスにはbelongs_toを定義する。
日本語にしたらわかりやすいが、has_manyはいくつかのモデルを持っている(なのでモデルクラスの複数形)belongs_toは1つのモデルに所属している(なのでモデルクラスの単数形)というふうに定義する。
このような定義をすることでUserクラスのインスタンスはuser.tasksといったメソッドで紐付いたTaskオブジェクトの一覧を得られるようになる。また、Taskクラスのインスタンスはtask.userといったメソッドで紐付いたUserオブジェクトを得られるようになる。

ログインしているユーザーのTaskデータの登録

現在の登録アクション(tasks_controller.rb) のcreateアクションでは@task = Task.new(task_params)となっている。
これを@task = current_user.tasks.new(task_params)とすることでログインしているユーザーのuser_idを入れた状態でTaskデータを登録することができる。

まとめ

  • session[:user_id]からログアウトの状態にするにはnilが入っている状態に変えればいいのでピンポイントでuser_idの情報を消すにはsession.delete(:user_id)とする。またセッション内のすべてのデータを削除するのはreset_sessionとする。
  • フィルターとはすべてのアクションに対して、前処理・後処理を設定する役割を提供するもので、 他の機能へのリダイレクトを実装するとリダイレクトが行われ、アクションには到達しない。
  • 親クラスなどですでに定義済みのフィルタを通らないようにするにはskip_before_actionを利用する。
  • 1対多の関係において、1のモデルクラスにはhas_many、多のモデルクラスにはbelongs_toを定義する。

モデル同士の「関連(アソシエーション)」についてはもっと理解を深めていかないといけないと感じた。