RJ Acuña
RJ Acuña

Reputation: 530

Rails 4.1 tests return undefined method `authenticate' for nil:NilClass

I'm working through the treehouse tutorials using Ruby 2.1.2 and Rails 4.1.5 and I got into an issue with my tests in the auth tutorial.

The tutorial says that this code should pass:

def create
    user = User.find(params[:email])
    if user && user.authenticate(params[:password])
        session[:user_id] = user.id
        redirect_to todo_lists_path
    else
        flash.now[:error] = "There was an error logging in. Please check your email and password"
        render action: 'new'
    end
end

Here are my tests:

describe "POST 'create'" do

        context "with correct credentials" do
            let!(:user){
                User.create(
                first_name: "Jason", 
                last_name: "Seifer", 
                email: "[email protected]", 
                password: "teamtreehouse1234",
                password_confirmation:"teamtreehouse1234")}

          it "redirects to the todo list path" do
            post :create, email: "[email protected]", password: "treehouse1234"
            expect(response).to be_redirect
            expect(response).to redirect_to(todo_lists_path)
          end

          it "finds the user" do
            expect(User).to receive(:find_by).with({email: "[email protected]"}).and_return(user)
            post :create, email: "[email protected]", password: "treehouse1234"
          end

          it "authenticates the user" do
            User.stub(:find_by).and_return(user)
            expect(user).to receive(:authenticate)
            post :create, email: "[email protected]", password: "treehouse1234"
         end

          it "sets the user id in the session" do
            post :create, email: "[email protected]", password: "treehouse1234"
            expect(session[:user_id]).to eq(user.id)
          end

          it "sets the flash success messagge" do
            post :create, email: "[email protected]", password: "treehouse1234"
            expect(flash[:success]).to eq("Thanks for logging in!")
          end
        end

        context "with blank credentials" do
          it "renders the new template" do
            post :create
            expect(response).to render_template('new')
          end
          it "sets the flash error messagge" do
            post :create, email: "[email protected]", password: "treehouse1234"
            expect(flash[:error]).to eq("There was an error logging in. Please check your email and password")
          end
        end

        context "with incorrect credentials" do
          let!(:user){
            User.create(
            first_name: "Jason", 
            last_name: "Seifer", 
            email: "[email protected]", 
            password: "teamtreehouse1234",
            password_confirmation:"teamtreehouse1234")}

          it "renders the new template" do
            post :create, email: user.email, password: "fuckingpassword"
            expect(response).to render_template('new')
          end
          it "sets the flash error messagge" do
            post :create, email: user.email, password: "fuckingpassword"
            expect(flash[:error]).to eq("There was an error logging in. Please check your email and password")
          end
       end
end

That gives me the error undefined method `authenticate' for nil:NilClassthe only way I've made the tests pass is with the following:

class UserSessionsController < ApplicationController
    def new
    end

    def create
        user = User.find_by(email: params[:email])

        if user.nil?
            flash[:error] = "There was an error logging in. Please check your email and password"
            render action: 'new'
        else
            user.authenticate(params[:password])
            session[:user_id] = user.id
            flash[:success] = "Thanks for logging in!"
            redirect_to todo_lists_path
        end 
    end
end

I know it's ugly but it worked, right to the point that I needed to test whether it denied authentication with the right user but the wrong password. The tests returned two errors:

1) UserSessionsController POST 'create' with incorrect credentials renders the new template
Failure/Error: expect(response).to render_template('new')
   expecting <"new"> but rendering with <[]>

2) UserSessionsController POST 'create' with incorrect credentials sets the flash error messagge
Failure/Error: expect(flash[:error]).to eq("There was an error logging in. Please check your email and password")
expected: "There was an error logging in. Please check your email and password"
        got: nil

The first example failed because for what ever reason(maybe rspec) the if clause doesn't handle user.authenticate(params[:password]) because it returns a hash.

let it be noted that the following code works in bin/rails console :

> user = User.create(first_name:"John", last_name: "Doe", email: "[email protected]", password: "password", password_confirmation: "password")
> user.save
> if user && user.authenticate("password")
> puts "wawa"
> end
wawa
=> nil

I've tried refactoring the create method so that it authenticates on assignment to no avail:

  def create
    user = User.find_by(email: params[:email]).authenticate(params[:password])

    if user.nil? || user == false
        flash[:error] = "There was an error logging in. Please check your email and password"
        render action: 'new'
    else
        session[:user_id] = user.id
        flash[:success] = "Thanks for logging in!"
        redirect_to todo_lists_path
    end 
  end

I get undefined method `authenticate' for nil:NilClass again.

Upvotes: 0

Views: 2074

Answers (2)

RJ Acu&#241;a
RJ Acu&#241;a

Reputation: 530

Found the bug it wasn't in the logic it was in the tests, I was testing the password with incorrect credentials treehouse123 instead of the one that was being created in my user teamtreehouse123.

Inspiration came in this debugging code:

    def create
      user = User.find_by(email: params[:email])
      if (user.nil? == false) 
        if user.authenticate(params[:password]) 
          puts user.authenticate(params[:password])
          puts "NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Batman!"
          session[:user_id] = user.id
          flash[:success] = "Thanks for logging in!"
          redirect_to todo_lists_path
        else 
          puts user.authenticate(params[:password])
          puts "waka waka waka waka waka waka waka waka waka waka waka waka waka waka waka waka"
          flash[:error] = "There was an error logging in. Please check your email and password"
          render action: "new"
        end
      else
        flash[:error] = "There was an error logging in. Please check your email and password"
        render action: "new"
      end
    end

user.authenticate(params[:password]) always returned false, regardless of what I did.

The Treehouse code is correct, but I'm changing all occurrences of teamtreehouse123 to password.

Upvotes: 1

pierallard
pierallard

Reputation: 3371

The undefined method 'authenticate' for nil:NilClass is a classic Rails error.

The error is never in the authenticate method. It's always in the part before it.

Here, you have an error on User.find_by(email: params[:email]). This part of code returns nil in this case. So Rails is trying to execute nil.authenticate(params[:password]).

Please check your params[:email] value or your list of users.

Upvotes: 0

Related Questions