Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby User Authentication with Rails Password Hashing and Sign In Creating the Sessions Controller

Ricardo Acuna
Ricardo Acuna
9,014 Points

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: "jason@teamtreehouse.com", 
            password: "teamtreehouse1234",
            password_confirmation:"teamtreehouse1234")}

          it "redirects to the todo list path" do
            post :create, email: "jason@teamtreehouse.com", 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: "jason@teamtreehouse.com"}).and_return(user)
            post :create, email: "jason@teamtreehouse.com", password: "treehouse1234"
          end

          it "authenticates the user" do
            User.stub(:find_by).and_return(user)
            expect(user).to receive(:authenticate)
            post :create, email: "jason@teamtreehouse.com", password: "treehouse1234"
         end

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

          it "sets the flash success messagge" do
            post :create, email: "jason@teamtreehouse.com", 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: "jason@teamtreehouse.com", 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: "jason@teamtreehouse.com", 
            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: "jon.doe@me.com", 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.

4 Answers

Ricardo Acuna
Ricardo Acuna
9,014 Points

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, as Jason said once spelling always gets us. Thanks Aurelien and Kang Kyu!

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

Good! I am glad you solved your problem :-). Funny thing, Jason made the same mistake (on purpose?) in his first of the two videos and gets the same error.

Kevin Mulhern
Kevin Mulhern
20,374 Points

Thank you so much, I had a similar problem, It was the password too. I was about to pull my hair out with this lol

Kang-Kyu Lee
Kang-Kyu Lee
52,045 Points

Hello Ricardo, I didn't figure this out, but from the test code I suspect let declaration didn't do its job. Well sorry couldn't be much help

Kang-Kyu Lee
Kang-Kyu Lee
52,045 Points

However undefined method 'authenticate' for nil:NilClass and some of errors above can happen when there's no registered user. (testing database shall be populated each time test runs.) Somehow let! declaration didn't work or by another reason, it seems the data was not there. And I think rake db:migrate RAILS_ENV=test or rake db:test:prepare wouldn't be an issue because rails version you used was over 4.1

Ricardo Acuna
Ricardo Acuna
9,014 Points

let! is working fine because my tests find the user, I did rake db:test:prepare rake db:migrate RAILS_ENV=test and the error is still there. Thanks anyway I guess this happens to me for trying to live on the bleeding edge.

1) Try replacing render 'new' with render "new" (double quotes), and see if that fixes your first problem.

You can also do render :new. Symbols are strings but can behave as representation of a name that Ruby saves during the execution of the program. Symbols are really handy in many ways when working with rails.

(see here different examples of rendering views/templates: http://guides.rubyonrails.org/layouts_and_rendering.html)

2) In your User model did you add the Public Instance Method has_secure_password?

class User < ActiveRecord::Base
  has_secure_password
end

has_secure_password will provide a series of methods including the method authenticate

user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
user.save                                                       # => false, password required
user.password = 'mUc3m00RsqyRe'
user.save                                                       # => false, confirmation doesn't match
user.password_confirmation = 'mUc3m00RsqyRe'
user.save                                                       # => true
user.authenticate('notright')                                   # => false
user.authenticate('mUc3m00RsqyRe')                              # => user
User.find_by(name: 'david').try(:authenticate, 'notright')      # => false
User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
``

thanks for the -1 :D

Ricardo Acuna
Ricardo Acuna
9,014 Points

I know that you're trying to help and I thank you, but your answers are just wrong.

1) 'new' and "new" are both strings, I know the advantage of symbols, but that's not where my code breaks.

2) I have has_secure_password my test for expect(user).to receive(:authenticate) in my "if user.nil?" version of the code. I said in the description that that version makes my tests pass, just not the ones that test for correct user with incorrect credentials

3) I had already read the documentation, and tried the variants of the authenticate method, they all work in the rails console.

Yep just trying to help you out, and that is to help out, but also learn on my part.

1) Yes 'new' and "new" are both strings. But they are not the same in Ruby. Look up String Interpolation. In any event I am not sure if it affects the render action, but in the Ruby on Rails guides they always use "Double Quotes" or a "Symbol". I was suggesting trying it out.

2) How about posting your test code when asking a question and make your post readable.

Ricardo Acuna
Ricardo Acuna
9,014 Points

Done, I had posted it on stack overflow and just copied and pasted out of fatigue, that's why it rendered so badly sorry. I added the tests too, this snippet has been driving me nuts for hours.

The render "new" solution, wouldn't work, but I did try it because I tend to have magical thinking. There is no difference in how 'new' and "new" are stored in memory, the only thing double quotes do is to tell the ruby interpreter to interpolate the variable into the string so that:

> new = 'puppy'
> puts  "#{new}"
puppy
 => nil

instead of the literal interpretation:

> puts '#{new}'
"\#{new}"
 => nil

Edit: added to the new answer.

Also could you run your test, but change the find_by method and see if the tests are in the green?

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

with

user = User.find_by_email(params[:email])

Both do the same thing and I am asking this because in my rails 3 app I used find_by_email and wonder if the rspec test is specific with find_by() or can also assume other syntaxes.

Below is the code block from one my online app to confirm that Jason's code is very similar and works.

def create
    user = User.find_by_email(params[:email])
    if user && user.authenticate(params[:password])
      if params[:remember_me]
        cookies.permanent[:auth_token] = user.auth_token
      else
        cookies[:auth_token] = user.auth_token
      end
      redirect_to session.delete(:return_to) || missions_url, flash: { :success => t(".sessions.new.logged_notice", :user => user.email) }
    else
      flash.now[:error] = t(".sessions.new.login_error")
      render "new"
    end
  end
Ricardo Acuna
Ricardo Acuna
9,014 Points

I you're right the error is in the find_by method it must be returning nil and thus ruby is trying to do nil.authenticate(params[:password]).

But I changed it so that:

if user.nil? == false
user.authenticate(params[:password])

and everything passes except the authenticate on incorrect credentials test, but when I change it to:

if user.nil? == false 
        if user.authenticate(params[:password])
          session[:user_id] = user.id
          flash[:success] = "Thanks for logging in!"
          redirect_to todo_lists_path
       end

It never redirects to new, the authenticate method is never evaluated, and I get this output with rspec --format=documentation

POST 'create'
    with correct credentials
      redirects to the todo list path (FAILED - 1)
      finds the user (FAILED - 2)
      sets the user id in the session (FAILED - 3)
      authenticates the user (FAILED - 4)
     sets the flash success messagge (FAILED - 5)
    with blank credentials
      renders the new template
      sets the flash error messagge
    with incorrect credentials
      renders the new template (FAILED - 6)
      sets the flash error messagge (FAILED - 7)

I think I have spent enough time on this, and I can only prove to you that the problem is not Treehouse code, but your refactored code if I replicate the entire app with the same gemset.

I asked you to test the refactored code in my other answer. Did you try it? What did you get?

Your comments are talking about a different problem:

You are asking if there is user or not, does it exist or not?. Even if there is user, the user can still be wrong. That's what you are not asking in your refactored code. The Authenticate method does that, and that is why you need to add an ELSE statement if the authenticate method returns false.... Your user.nil? doesn't check if the user credentials are wrong or not. Your test code is returning an error because you didn't add any fallbacks if the credentials are wrong. Did you try that? What did you get?

Best of luck,

Aurelien

Ricardo Acuna
Ricardo Acuna
9,014 Points

My nested if isn't the problem, the only reason I nested it was to isolate the point of failure.

If you still feel like proving that there's nothing wrong with the Treehouse code on my environment then you should actually get my environment and my code.

$rvm get stable 

or

$ \curl -sSL https://get.rvm.io | bash -s stable if 

$ rvm install ruby #installs the latest ruby
$ rvm gemset update

$ mkdir odot; cd odot

$ rvm use ruby-2.1.2@odot --ruby-version --create
$ gem install rails
$ git init
$ git pull https://github.com/drakezhard/odot_authenticate_bcrypt_treehouse.git

That should give you access to my development environment.

I will also add this answer. Sorry about clogging up your question.

Treehouse made a mistake in their video page with their code snippet.

They wrote

user = User.find(params[:email])

When you write ModelName.find() it expects to look for an id number. An exception to that Rails rule would be to create a custom to_params method in the model where you can have the model search by another column instead of ID.

However Ruby provides other methods when you want to find a record by using another column instead of the ID column such as

ModelName.find_by(column_name: [params: column_name])

# And you can also remove the parentheses because Ruby is cool

ModelName.find_by column_name: [params: column_name]

# OR you can use the where method

ModelName.where(column_name: [params: column_name]) 

#and this gets handy if you chain it with .first_or_create when creating Tag words for example

I hope this helps clears up the confusion about the find_by() error.

Jason actually uses find_by(email: params[:email]) in the video not like in the code snippet on the presentation page.

The second problem with your refactored code is actually really simple:

    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

In the code above you first check if the user with email exists, if it doesnt it throws an error. If it does exist it will try to authenticate the user with the given password. If the password is good it will work, however you did not add a conditional if the authentication fails with the wrong password. Thats why your test gives you the error undefined method `authenticate' for nil:NilClass (Jason actually explains that part in the first of the two videos) . When you refactored your own code the second time, the problem remained

Now in the snippet code on the presentation and in Jason's video, he uses conditional block with && operator. This will check if the user exists in the database with the given email AND if the user has authenticated . You can correct your code by trying out

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

But that is the same as

def create
    user = User.find_by(email: params[:email])
    if user && user.authenticate(params[:password])
        session[:user_id] = user.id
          flash[:success] = "Thanks for logging in!"
          redirect_to todo_lists_path

   else
      flash[:error] = "There was an error logging in. Please check your email and password"
       render action: 'new'
   end
end
Ricardo Acuna
Ricardo Acuna
9,014 Points

On

if user.authenticate(params[:password])
    %CODE%
end

%CODE% never executes, that's the problem. Also on Rails 4.1:

if user
    %CODEA%
else
    %CODEB%
end

%CODEB% doesn't execute because I suspect Ruby 2.1.2 doesn't like if nil and breaks upon execution never reaching else. Thats why I added the pessimist case if user.nil? to ensure execution of flash[:error].

Doesn't break for me. So I don't understand your problem with if nil.

$ ruby -v
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
$ irb
2.1.2 :001 > def user_exist(user)
2.1.2 :002?>   if user
2.1.2 :003?>     puts "user exists"
2.1.2 :004?>     else
2.1.2 :005 >       puts "user doesn't exist"
2.1.2 :006?>     end
2.1.2 :007?>   return user
2.1.2 :008?>   end
 => :user_exist 
2.1.2 :009 > user = nil
 => nil 
2.1.2 :010 > user_exist(user)
user doesn't exist
 => nil 
2.1.2 :011 > user = "Aurelien"
 => "Aurelien" 
2.1.2 :012 > user_exist(user)
user exists
 => "Aurelien" 
2.1.2 :013 > 
Ricardo Acuna
Ricardo Acuna
9,014 Points

It doesn't break on irb or bin/rails console. It only breaks when you run the tests. It doesn't create the user session, because the if user && user.authenticate(params[:email]) never returns user. And if I make it so that user.authenticate(params[:email]) goes after the if user I get test failure on the with incorrect credentials because I'm never authenticating the user before setting the user session. Furthermore if I nest an if inside if user that says if user.authenticate(params[:email]) and prevents setting a session for unautenticated users all the with correct credentials context fails.

So as I've said before the point of failure is in if user.authenticate(params[email]) because if I remove it from any if bloc my user receives(:authenticate). I've been wondering if I can access that feature of Rspec and implement it in my code, I'll read the documentation for receives to see how it checks that the user received authentication and then put that on the if block. But that's just speculation I don't know what's going on really, why would if fail to execute upon authenticate, that's crazy because the authenticate method returns self .