Search code examples
ruby-on-railsrubyrailstutorial.org

chapter 8 rubytutorial - NoMethodError: undefined method `forget' for nil:NilClass


I am learning from rubytutorial and am having this error. I checked this answer and this but still does not work. I attached session_helper.rb and user.rb here. could someone show me where I miss?

1) Error:
UsersLoginTest#test_login_with_valid_information_followed_by_logout:
NoMethodError: undefined method `forget' for nil:NilClass
    app/helpers/sessions_helper.rb:25:in `forget'
    app/helpers/sessions_helper.rb:32:in `log_out'
    app/controllers/sessions_controller.rb:19:in `destroy'
    test/integration/users_login_test.rb:33:in `block in <class:UsersLoginTest>'

22 runs, 50 assertions, 0 failures, 1 errors, 0 skips

session_helper.rb

module SessionsHelper
    # Logs in the given user.
    def log_in(user)
        session[:user_id] = user.id
    end

    def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
    if user && user.authenticated?(cookies[:remember_token])
      log_in user
      @current_user = user
      end
    end
    end

    def logged_in?
        !current_user.nil?
    end

    # Forgets a persistent session.
    def forget(user)
      user.forget
      cookies.delete(:user_id)
      cookies.delete(:remember_token)
    end

    # Logs out the current user.
    def log_out
      forget(current_user)
      session.delete(:user_id)
      @current_user = nil
    end  

    # Remembers a user in a persistent session.
    def remember(user)
      user.remember
      cookies.permanent.signed[:user_id] = user.id
      cookies.permanent[:remember_token] = user.remember_token
    end
  end

user.rb

class User < ActiveRecord::Base
  attr_accessor :remember_token
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # Returns the hash digest of the given string.
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns a random token.
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # Remembers a user in the database for use in persistent sessions.
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

  # Returns true if the given token matches the digest.
  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

  # Forgets a user.
  def forget
    update_attribute(:remember_digest, nil)
  end
end

Solution

  • Your error message indicates that you have an:

    undefined method forget' for nil:NilClass

    This means that you attempted to call a method (forget) on an object that did not have that method (nil). Looking at the part of your code where the error is coming from...

    def forget(user)
          user.forget
    

    ...you can see that you are calling forget on the variable user. This is being called from the log_out method:

    def log_out
      forget(current_user)
    

    So, you are passing current_user to the forget method, and then calling forget on it. Only, it is not an instance of the User class (like you expected), which does have a forget method, but rather is nil. nil does not have a forget method.

    This is probably nil because you are not authenticated and there is no current_user. One way to remedy this would be to check if the current user actually exists before forgetting it, like this:

    def log_out
      current_user && forget(current_user)
    

    A more sophisticated way to handle this issue would be with the Null Object Pattern, where current_user would return an instance of User with minimal authorizations, instead of nil.