Ruby on Rails Tutorial Test Not Passing - ruby-on-rails

I'm working through the railstutorial.org book, chapter 9. When running the test suite, I can't get rid of the following failure. All the code has been attempted by first typing it in and then by copying and pasting from the book.
...............F............................................
Failures:
1) SessionsController DELETE 'destroy' should sign a user out
Failure/Error: controller.should_not be_signed_in
expected signed_in? to return false, got true
# ./spec/controllers/sessions_controller_spec.rb:69:in `block (3 levels) in <top (required)>'
Finished in 8.28 seconds
60 examples, 1 failure
Here's the code for the test in sessions_controller_spec.rb:
describe "DELETE 'destroy'" do
it "should sign a user out" do
test_sign_in(Factory(:user))
delete :destroy
controller.should_not be_signed_in
response.should redirect_to(root_path)
end
end
The (I think) relevant portions of sessions_controller.rb:
def destroy
sign_out
redirect_to root_path
end
The sign_out method is found in sessions_helper.rb
def current_user=(user)
#current_user ||= user_from_remember_token
end
def current_user
#current_user
end
def signed_in?
!current_user.nil?
end
def sign_out
cookies.delete(:remember_token)
self.current_user = nil
end
So, if I understand this correctly, the test, after signing in a factory user, is calling the SessionsController's destroy method, which calls sign_out (from SessionsHelper), which is explicitly setting self.current_user to nil. Then the test checks signed_in? with the line be_signed_in. Since the code sets self.current_user to nil, current_user.nil? should return true, !current_user.nil? should return false, which is what the test wants (controller.should_not be_signed_in).
Any help would be appreciated. I'm learning Ruby, Rails and TDD all at the same time, so I'm not sure where my problem lies.

The problem is how you are setting current user - you are only ever setting it to login from the remember token, you aren't ever allowing it to be set to nil. You might want to try the following:
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end

Related

Michael Hartl chapter 8.3 logging out sessions

I'm going through Michael Hartl's The Ruby on Rails Tutorial, Chapter 8.3 Logging Out Sessions and I don't understand how removing the session[:user_id] can remove the #current_user as well:
here is the SessionController:
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)
redirect_to user
else
#flash.now will only flash once - if a new request or view is rendered,the flash will go away now
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_path
end
end
and here is the SessionsHelper for the login and logout helpers:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
#find the user if #user is not defined yet, or else, just keep the current user
#that way we dont have to do a database search every time current_user is called
#current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
end
end
The way I understand, once #current_user is defined once logged in, shouldn't the variable still last even though the session[:user_id] has been removed since it is being set to itself?
#current_user ||= User.find_by(id: session[:user_id])
There was no action that I am aware of that removed the #current_user variable. But when I tested it during the debugger, I can see that once someone logs out, #current_user becomes nil.
Can someone explain the mechanics to me?
The session persists between requests. But the instance variable #current_user only persists for the length of one request. When the destroy action redirects to the root_path that is the start of a new request which will load the root page.
You may want to try this out so see that clearing the user_id out of the session doesn't clear out the instance variable:
def destroy
# Test code to initialize #current_user
current_user
Rails.logger.debug("#current_user before: #{#current_user.inspect}")
log_out
# Test code to check #current_user after updating session
Rails.logger.debug("#current_user after: #{#current_user.inspect}")
redirect_to root_path
end
And then check what ends up in log/development.log. #current_user will still be present after log_out but it will go away at the end of the request.

undefined method `update_attribute' for nil:NilClass

I am able to create users and sign in, however when I sign out I receive the following (although the session does appear to end):
NoMethodError at /signout
undefined method `update_attribute' for nil:NilClass
This is on the sign_out method in the SessionsHelper, where current_user.update_attribute(...)
Does this mean that current_user is nil? What can I do to fix, this. I'm very new to RoR, thanks.
Here's my SessionsHelper
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def sign_out
current_user.update_attribute(:remember_token, User.encrypt(User.new_remember_token))
#current_user.update_attribute(:remember_token, User.new_remember_token)
cookies.delete(:remember_token)
self.current_user = nil
end
end
Here's my SessionsController
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_to user
else
flash.now[:error] = 'Invalid email/password combination' #not quite right
render 'new'
end
end
def destroy
sign_out
redirect_to 'signin'
end
end
it would be nice if you told us what the exact circumstances are that you got this error.
it's quite possible you're going to the sign-out page when you're not actually currently signed-in.
In which case - why don't you add "if signed_in?" to your action eg:
def sign_out
return unless signed_in? # you are already signed out
current_user.update_attribute(:remember_token, User.encrypt(User.new_remember_token))
cookies.delete(:remember_token)
self.current_user = nil
end
def destroy
sign_out if signed_in?
redirect_to 'signin'
end
Alternatively - do you have skip_before_action authenticate_user or similar for sign_out?
Again - to sign out, you have to be signed-in... so you can't skip the authentication action for sign-out.

RailsTutorial.org Chapter9 session.delete issues

I'm following the tutorial on http://ruby.railstutorial.org
Specifically, chapter 9 (9.2.3)
http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#top
I've managed to get the part when a user will get prompted to login when accessing a restricted page then be redirected back to the restricted page after successfully logging in.
I'm trying to get it so that after one redirects to the protected page, the next login attempt will direct back to the main user profile page, however, session.delete(:return_to) doesn't appear to be working and the user is repeatedly directed back to the originally saved protected page. Here's my code:
My session Controller:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or user
# Sign the user in and redirect to the user's show page.
else
# Create an error message and re-render the signin form.
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
...
end
My session helper:
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def current_user?(user)
user == current_user
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url
end
end
Any help you can give would be brilliant! It seems like session.delete() simply isn't working.
The following block solved it. Nothing else needs to change.
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in." #unless signed_in?
end
end
When I did the tutorial, my code had the first lines of the SessionsController#create method as just:
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
But, I can see that the corresponding code in the book has changed to:
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
I attempted to use that new code in my sample_app, but most of my tests ended up failing. So, for you, I guess test adding the downcase method to your params[:session][:email] call first, and if that doesn't work, try substituting the lines out for the session-less code above and see if it works.
Update
After looking at your code, as far as I can tell, these are your problems:
You're calling session.delete(:return_to) in SessionsController#create for some reason. This line can be removed:
app/controllers/sessions_controller.rb
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
# session.delete(:return_to)
sign_in user
# ...
#...
end
Both lines of code in your UsersController#signed_in_user method need to be put in the unless block, not just the call to redirect_to:
app/controllers/users_controller.rb
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in." #unless signed_in?
end
end
If you make these changes and run your tests, you'll still have a Nokogiri::XML::XPath::SyntaxError: on your call to
spec/requests/authentication_pages_spec.rb
it { should have_exact_title('title', text: full_title('')) }`
but I'm assuming this is a custom matcher you're planning to work on. If not and it's a mistake, remove it and all your tests will pass.

basic ruby on rails authentication trouble

I am working on a basic authentication system for a rails app. The authentication is verifying account information from Active Directory using a net-ldap class (this part is working fine).
Something seems to be wrong with my session_helper however. Even though ActiveDirectoryUser.authenticate is successful, the signed_in helper always returns false. After signing in, the script redirects to root_path (default_controller's home) and then immediately redirects back to signin_path again-- as a result of the signed_in helper returning false.
See the code below. What am I missing?
Thanks
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
end
default_controller.rb
class DefaultController < ApplicationController
before_filter :signed_in_user
def home
end
private
def signed_in_user
redirect_to signin_path, notice: "Please sign in." unless signed_in?
end
end
sessions_helper.rb
module SessionsHelper
def sign_in(user)
#current_user = user
end
def current_user
#current_user ||= nil
end
def signed_in?
!#current_user.nil?
end
def sign_out
#current_user = nil
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = ActiveDirectoryUser.authenticate(params[:session][:username],params[:session][:password])
if user.nil?
# authentication failed
flash.now[:error] = 'Invalid email/password combination'
render 'new'
else
# authentication succeeded
sign_in #user
flash[:error] = 'Great success'
redirect_to root_path
end
end
def destroy
sign_out
redirect_to root_path
end
end
You should use session for to persist that kind of data (will be assessable for every request), it's user data. But I highly recommend you to use something like the devise gem that do all that authentication things and more for you. Why reinvent the weel right?
I believe this would work for you.
module SessionsHelper
def sign_in(user)
session[:user_id] = user.id
end
def current_user
ActiveDirectoryUser.find(session[:user_id]) ||= nil
end
def signed_in?
!session[:user_id].nil?
end
def sign_out
session[:user_id] = nil
end
end

Boolean model attributes as automatic methods

I'm working through http://railstutorial.org (currently working on section 10.4.2 - destroying users).
My User model has a boolean admin attribute, and in the Users controller there is some simple code making use of this attribute:
def admin_user
redirect_to(root_path) unless current_user.admin?
end
The RSpec test:
describe "as a non-signed-in user" do
it "should deny access" do
delete :destroy, :id => #user
response.should redirect_to(signin_path)
end
end
And the error this test produces:
NoMethodError in 'UsersController DELETE 'destroy' as a non-signed-in user should deny access'
undefined method `admin?' for nil:NilClass
What is nil:NilClass? Why am I getting this?
edit: j., this might help:
def sign_in(user)
user.remember_me!
cookies[:remember_token] = { :value => user.remember_token,
:expires => 20.years.from_now.utc }
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
Good news! Your rspec test found a bug!
Your controller method
def admin_user
redirect_to(root_path) unless current_user.admin?
end
is being called directly or indirectly by your controller's delete action.
And admin_user is being called when current_user is nil. So the admin_user method is failing with the error
undefined method `admin?' for nil:NilClass
fix: depends on how admin_user is being called and used. Is it in a filter?
Could be changed to either of
def admin_user
redirect_to(root_path) unless current_user && current_user.admin?
end
# or
def admin_user
redirect_to(root_path) if current_user && !current_user.admin?
end
depending on the situation....
It looks like your current_user object is nil and it should be nil.
As you are not signed in then surely your current _user is nil and then you are trying to get
nil.admin? so that's why it give No method error.
Probabily you should rewrite your method something like
def admin_user
redirect_to(root_path) unless current_user || current_user.admin?
end
Did you complete section 9.3.4 Current User of the tutorial? This creates a current_user variable within the SessionsController via the inclusion of the SessionsHelper module.
I had this exact problem, and I found that the underlying problem was that I'd failed to add :destroy to the before_filter :authenticate in app/controllers/users_controller.rb, as below (from listing 10.41:
before_filter :authenticate, :only => [:index, :edit, :update, :destroy]

Resources