🔥

【Rails】undefined method `メソッド名(authenticate)’ for nil:NilClass

2024/04/28に公開

はじめに

前提

筆者はRailsチュートリアル第7版の動画(テキストも付属)を購入済みですが、8章以降が未リリースであるとの認識の下、ここでは第6版の教材を引用します。第7版のコードや説明と異なる部分があるかもしれませんが、ご了承ください

引用

動画「前編: Sessionsコントローラ、ログインフォーム」

テキスト「8.1.2 ログインフォーム」

https://railstutorial.jp/chapters/basic_login?version=6.0#sec-login_form

エラー発生コード

class SessionsController < ApplicationController
  def new
  end

  # POST /login
  def create
    user = User.find_by(params[:session][:email].downcase)
    if user.authenticate(params[:session][:password])
      # Success => redirect to the user page
    else
      # Failure => Display error message
      render 'new'
    end
  end
end

エラー内容

このエラー「NoMethodError: undefined method authenticate for nil:NilClass」は、user変数がnilである状態でauthenticateメソッドを呼び出そうとしているために発生

rails console で確認。

irb(main):006> user = User.find_by(email: '')
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", ""], ["LIMIT", 1]]
=> nil

nilオブジェクトに対してauthenticateメソッドを呼び出してみる。

irb(main):009> nil.authenticate
(irb):9:in `<main>': undefined method `authenticate' for nil:NilClass (NoMethodError)

nil.authenticate
   ^^^^^^^^^^^^^

RubyではnilはNilClassクラスのインスタンスだが、NilClassにはauthenticateというメソッドが定義されていないためエラーが発生。

※ タイポなどでデータベースに存在しないemailを取り出そうとしてもnilが返ってくる。

https://qiita.com/tsuchinoko_run/items/f3926caaec461cfa1ca3

エラー解決コード

class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # Success => redirect to the user page
    else
      # Failure => Display error message
      render 'new'
    end
  end

  def destroy
  end
end

具体的な修正の解説

User.find_byメソッドが一致するユーザーを見つけられない場合、結果としてnilが返される。そのため、Userオブジェクトを操作する前に、その変数がnilではないかをチェックする必要がある。具体的には、Userオブジェクトがnilの場合、User認証は失敗する一方、適切な値を受け取った場合は、ログイン処理が成功するようにコードを修正する。

以下のコードでは、userがnilではなく、かつuser.authenticate(params[:session][:password])がtrueを返す場合にログイン成功の処理を行う。

  # POST /login
  def create
    user = User.find_by(params[:session][:email])
    if !user.nil? && user.authenticate(params[:session][:password]) # ここ
      # Success => redirect to the user page
    else
      # Failure => Display error message
      render 'new'
    end
  end

以下は、上記の条件式をより簡略化したコードである。Rubyではnilとfalseは条件式でfalseと評価され、それ以外の値はtrueと評価される。そのため、userオブジェクトがnilの場合は自動的にfalseとして扱われ、user.authenticate(...)以降は評価されない。

if user && user.authenticate(params[:session][:password])

この簡略化された条件式では、繰り返しにはなるがuserがnilの場合には最初の条件userがfalseを返し、認証メソッドが呼び出されることはない。userが存在する場合のみ、user.authenticate(...)が評価される

補足

(このケースで)Userオブジェクトをインスタンス変数(@user)にしない理由

1. Viewでの使用が不要なため
もしUserオブジェクトをViewテンプレートで使用しない場合、インスタンス変数にする必要はない。例えば、Userオブジェクトを作成や更新後にリダイレクトするだけのアクションではローカル変数で十分。

2. スコープ限定をするため
Userオブジェクトがそのコントローラの特定のアクション内でのみ必要である場合、ローカル変数を使用することでスコープを限定し、不必要に他のアクションやビューに変数が露出する(アクセス可能になってしまう)ことを防げる。

参考

https://railstutorial.jp/chapters/basic_login?version=6.0#sec-login_form

https://teratail.com/questions/265788

https://stackoverflow.com/questions/15331973/rails-tutorial-3-2-chapter-8-error-nomethoderror-in-sessionscontrollercreate

最後に

ここまで読んでいただきありがとうございました!
今回の記事が良かったと思ったらぜひ「いいね」を押していただけると嬉しいです(大変励みになります💪)
noteでも記事を執筆していますので、ぜひチェックしてみてください。

https://note.com/take_lifelog/n/n58df7ce7af6f

👇他にもこのようなことについて記載しているのでぜひチェックしてください!
https://zenn.dev/take_tech

今回もご精読いただきありがとうございました!!!

Discussion