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 trialRicardo Acuna
9,014 PointsRails 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
9,014 PointsFound 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
.
Kang-Kyu Lee
52,045 PointsHello 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
52,045 PointsHowever 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
9,014 Pointslet!
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.
Aurelien Schlumberger
6,127 Points1) 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
``
Aurelien Schlumberger
6,127 Pointsthanks for the -1 :D
Ricardo Acuna
9,014 PointsI 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.
Aurelien Schlumberger
6,127 PointsYep 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
9,014 PointsDone, 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
Aurelien Schlumberger
6,127 PointsEdit: added to the new answer.
Aurelien Schlumberger
6,127 PointsAlso 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
9,014 PointsI 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)
Aurelien Schlumberger
6,127 PointsI 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
9,014 PointsMy 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.
Aurelien Schlumberger
6,127 PointsI 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
9,014 PointsOn
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]
.
Aurelien Schlumberger
6,127 PointsDoesn'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
9,014 PointsIt 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
.
Aurelien Schlumberger
6,127 PointsAurelien Schlumberger
6,127 PointsGood! 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
20,374 PointsKevin Mulhern
20,374 PointsThank you so much, I had a similar problem, It was the password too. I was about to pull my hair out with this lol