I'm working on chapter 9, exercise 1 of the Rails Tutorial. I'm unsure of what to do in order to test that, after a normal login, the user will be redirected to his/her profile. The exercise's hint implies that I should modify the "successful edit with friendly forwarding" test in users_edit_test.rb in order to compare the value of session[:forwarding_url] with that of the user profile path. To do that, I added
assert_equal session[:forwarding_url], user_path
to the end of the function. However, when I do rake test, I get this error:
Expected: nil
Actual: "/users/762146111"
Can anyone assist me with this problem?
Update: Here are the controller and helper that are being tested:
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
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
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
# Returns true if the given user is the current user.
def current_user?(user)
user == current_user
end
# Returns the current logged-in user (if any).
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
# Returns true if the user is logged in, false otherwise.
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
# Redirects to stored located (or to the default).
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
# Stores the URL trying to be accessed.
def store_location
session[:forwarding_url] = request.url if request.get?
end
end
I'm way late in answering this question, so it might be a moot point now, but I'm working on this exercise in the tutorial right now myself and had to struggle to come up with the correct answer, but I finally did. So I thought I'd share what I did to get the tests to pass in case you're still seeking an answer. First I'll point out where the flaw in your current logic is (because I made the same mistake initially and had to process the logic flow of the code to figure out what went wrong).
In your test, you're testing for equality between with
assert_equal session[:forwarding_url], user_path(@user)
. The problem is, the user'suser_path
is never set as the forwarding url. In thesessions_helper.rb
file, theredirect_back_or()
method has this line:redirect_to(session[:forwarding_url] || default)
(thedefault
argument passed to this method isuser
when it's called in the create action of the sessions controller). So this line in theredirect_back_or()
method can be read in plain English asIn order to test for equality as you're trying to do (and as I initially tried), it requires that at some point
user_path(@user)
oruser
would have to be set as the session's forwarding url.store_session
takes no parameters to do this, so the only way user_path(@user) could be set as the forwarding url is if an unauthorized user were attempting to access that page and were first forced to log in then be redirected there (as best I can tell, this text does not require a person to be logged in to view a single user's page). So,user_path(@user)
would never conceivably be set as the forwarding url in thestore_session
method, and thusly,assert_equal session[:forwarding_url], user_path(@user)
would never evaluate to true.At this point, the error message you cited in your original question is extremely helpful in solving your problem.
If you recall from the early chapters of Hartl's text (or from the Rails API) assert_equal tests take two required parameters: the expected value and the actual value and then compare the two. This is reflected in your error message:
session[:forwarding_url]
evaluates tonil
whileuser_path(@user)
evaluates to a specific user's url. Since, like I stated earlier, the user's profile page should never conceivably be set as the forwarding url, we can eliminateuser_path(@user)
entirely from our test.Fortunately for us, the exercise in the textbook doesn't require that we user
assert_equal
to get the correct result. We just need to test thatsession[:forwarding_url]
has the right value. That should be pretty easy. Let me walk through it step by step:logged_in_user
method.unless logged_in?
evaluates to false, so the code in this method is run.logged_in_user
and the unauthorized user'sget
request is stored as the forwarding url. Simultaneously, the user is redirected to the log in page.create
method, to create a new session. This action logs the user in and callsredirect_back_or()
.redirect_back_or()
checks to see ifsession[:forwarding_url]
has a value associated with it -- we know it was becausestore_location
assigned a value to this key in step #3. So the first half ofredirect_back_or()
's || statement is executed and the now-authenticated user gets redirected to the page they were initially trying to access before they logged in.redirect_back_or()
then deletes the value associated with the forwarding url, since the user has been redirected there already.redirect_back_or()
gets called,session[:forwarding_url]
evaluates to nil, so the second half of the || statement gets executed, namelyredirect_to user
(sinceuser
is the parameter passed to the method as its default).To summarize, the correct value of
session[:forwarding_url]
for subsequent login attempts should be nil. You can write this test one of two ways:assert_equal
to test for equality:I prefer test #2, but I tested them both and it worked just fine both ways.
Though not explicitly required in the original exercise, I also added a test after this one to ensure all subsequent redirects are sent to the
user_path(@user)
:TL;DR:
session[:forwarding_url]
will never equaluser_path(@user)
. If it hasn't been stored withstore_location
(which is the case with all subsequent login attempts), it will evaluate tonil
, so a passing test for this exercise must be written one of two ways: