user1486510
user1486510

Reputation: 139

NoMethodError in SessionsController#destroy undefined method `forget' for nil:NilClass

Working through Rails Tutorial by M. Hartl Ch 8 Log in, Log out

When attempting to log out of my application, I receive the error message given in the title of my question.

I'm very new to this, but I think this means that the forget instance method defined in models/user.rb and called in SessionsHelper is being called on user which for some reason is nil. Here's the snippet I'm talking about in SessionsHelper:

#Found in SessionHelper.  Forgets persistent session
def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_tolken)
end

And here's the definition of the forget instance method:

#Found in class models/user.rb
def forget
  update_attribute(:remember_digest, nil)
end

I don't know why user would be nil when called in SessionsHelper. Here are the full files, including SessionsController, SessionsHelper, and User.

I hope I was clear and used the right terminology. Let me know if I need to clarify.

class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
        log_in user # Log the user in and redirect to the user's showpage
        params[:session][:remember_me] == '1' ? remember(user) : forget(user)
        redirect_to user # same as user_url(user)
    else
        # Show error message
        flash.now[:danger] = 'Wrong email or password' 
        render 'new'
    end
    end

    def destroy
    log_out # if logged_in?    # not logging out
    redirect_to root_url
  end
end

XXXXXXXXXX

module SessionsHelper

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

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

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


# Returns the current logged-in user (if any)
def current_user
    if (user_id = session[:user_id]) # CAREFUL! (not ==). Read if session of user_id exists
        @current_user ||= User.find_by(id: user_id)
    else (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

# Returns true if the user is logged-in, else false
def logged_in?
    !current_user.nil?
end

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

XXXXXXXXXX

class User < ActiveRecord::Base
attr_accessor :remember_token

  before_save {email.downcase!}
  validates :name, presence: true, length: {maximum: 50}
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[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, length: {minimum: 6}

  # Returns the hash digest of the given string. Used to make hashed password of user in fixture model for login integration testing                                                        
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns random token for remembering users via cookies
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # Remembers a user in the database for use in persisten 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

Thank you

Upvotes: 2

Views: 1647

Answers (2)

BigRon
BigRon

Reputation: 3282

Your log_out method is deleting the session before forgetting the user. This causes an error because forget takes current_user as a parameter and current_user uses the session to determine who the current user is:

    if (user_id = session[:user_id]) # This will attempt to assign user_id based on the value of session[:user_id], but will fail since the session was deleted

You need to delete the session after forgetting the user. Change your log out method from this:

def log_out
    session.delete(:user_id)
    forget(current_user)
    @current_user = nil
end

To this:

def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
end

Upvotes: 1

Prakash Murthy
Prakash Murthy

Reputation: 13077

The current_user method is returning nil, and that is throwing the error further down the line.

There is a typo in your current_user method. Change the else to elsif and it should most likely fix the issue.

If that does not fix it, make sure the method is exactly the same as in Listing 8.57 of the rails tutorial ; the same is included below:

# Returns the user corresponding to the remember token cookie.
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

Upvotes: 1

Related Questions