I am working through the Hartl rails tutorial, within Chapter 9 I am trying to replicate the tests for restricting the edit action to the current user:
require 'spec_helper'
describe "Authentication" do
subject { page }
....
....
....
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title('Edit user')) }
specify { expect(response).to redirect_to(root_url) }
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
end
end
I have then implemented the code as described within the book, but the tests still fail:
Failures:
1) Authentication as wrong user submitting a PATCH request to the Users#update action
Failure/Error: specify { expect(response).to redirect_to(root_url) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/signin>.
Expected "http://www.example.com/" to be === "http://www.example.com/signin".
# ./spec/requests/authentication_pages_spec.rb:100:in `block (4 levels) in <top (required)>'
I have investigated by adding some logging to the sign_in method called within the test:
def sign_in(user, options={})
Rails.logger.debug "sign_in "+"*"*15
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
Rails.logger.debug "Created cookie rt: "+ cookies[:remember_token].to_s
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
Within the log the create cookie remember_token is reported as blank. If I change the sign in to sign_in user (without the no capybara option) I can see that the user is signed in and the cookie populated, but when I visit the edit action the cookie is no longer populated.
How can I investigate further as to why the cookie is being destroyed?
Thanks...
For information I've included the application code below:
content of users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def new
#user = User.new
end
def show
#user = User.find(params[:id])
logger.debug "User show: "+ #user.to_yaml
logger.debug "cookie on user show: "+ cookies[:remember_token].to_yaml
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
def edit
logger.debug "Edit user method"
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
def signed_in_user
si = signed_in?
logger.debug "*"*200
logger.debug "signed in user: signed in?: "+si.to_s
redirect_to signin_url, notice: "Please sign in." unless si
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
Content of sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email].downcase)
if user && user.authenticate(params[:password])
sign_in user
redirect_to user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
end
Content of sessions_helper.rb
module SessionsHelper
def sign_in(user)
logger.debug "Sessions Helper sign in Method"
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?
s_in = !current_user.nil?
logger.debug "signed_in?: "+s_in.to_s
s_in
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user=(user)
#current_user = user
end
def current_user
logger.debug "getting current user"
logger.debug "cookie: "+ cookies[:remember_token].to_s
remember_token = User.encrypt(cookies[:remember_token])
logger.debug "rt: "+ remember_token.to_yaml
logger.debug "current_user before: "+ #current_user.to_yaml
#current_user ||= User.find_by(remember_token: remember_token)
logger.debug "current_user after: "+ #current_user.to_yaml
#current_user
end
def current_user?
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
end
There is nothing wrong with your authentication test
Its the code in users_controller.rb file
please remove the indicated(<---) line from your update method from users_controller.rb file.
It is clear from the error itself that it is expected to be at root_ulr but landing on sign_in_url.
This is happening because you have added one line "sign_in #user" to your users_controller.rb file in update method.so as per you code it is redirecting to "sign_in_url" which is unnecessary as the user have already logged in and updated his profile.I hope this will be helpful and resolve your issue.
update method in users_controller.rb file
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in #user `<----------- Remove this line
redirect_to #user
else
render 'edit'
end
end
Related
Well, im new to coding with Rails and Michael Hartl tutorial is awesome, and till now i've benn able to find my coding errors... right now im in chapter 10 "Friendly Forwarding" and whilst performing the test for succesfull edit i stumbled on an error i cant see through.
test_successful_edit_with_friendly_forwarding#UserEditTest (1.07s)
Expected response to be a <3XX: redirect>, but was a <200: OK>
test/integration/user_edit_test.rb:32:in `block in '
my user_edit_test is:
require 'test_helper'
class UserEditTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "unsuccessful edit" do
log_in_as(#user)
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), params: { user: { name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar" } }
assert_template 'users/edit'
end
test "successful edit with friendly forwarding" do
get edit_user_path(#user)
log_in_as(#user)
assert_redirected_to edit_user_url(#user)
name = "Foo Bar"
email = "foo#bar.com"
patch user_path(#user), params: { user: { name: name,
email: email,
password: "",
password_confirmation: "" } }
assert_not flash.empty?
assert_redirected_to #user <--- this is line 32 from the test
#user.reload
assert_equal name, #user.name
assert_equal email, #user.email
end
end
My users controller is:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params) # Not the final implementation!
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
# Handle a successful update.
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
#Before Filters
#Confirms a logged-in user
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
My sessions.Helper is:
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
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 current user is the given user
def current_user?(user)
user == current_user
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out current user
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
# Redirects to stored location (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.original_url if request.get?
end
end
And my Sessions Controller is:
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
From what i've seen in other questions i understand the problem but can't seem to find a way to fix it, i feel is right there in my face but dont see it.
FWIW, your friendly forwarding is working correctly and is tested in the first 3 lines of your test case. The assert_redirected_to #user test corresponds to your UsersController#update, more specifically the redirect_to #user statement in the if branch. What happens if you log into the site and update a user's profile? Check the logs at that point too.
I am on chapter 9 and it seems that the tests are failing once again. Here is the relevant code:
sessions_helper.rb:
module SessionsHelper
#logs in the given user
def log_in(user)
session[:user_id] = user.id
end
#Remember 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
def current_user?
user == current_user
end
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
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
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
Here is users_edit_test.rb:
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
def setup
#user = users(:michael)
end
test "successful edit" do
log_in_as(#user)
get edit_user_path(#user)
assert_template 'users/edit'
name = "Foo Bar"
email = "foo#bar.com"
patch user_path(#user), user: { name: name,
email: email,
password: "",
password_confirmation: ""}
assert_not flash.empty?
assert_redirected_to #user
#user.reload
assert_equal name, #user.name
assert_equal email, #user.email
end
test "unsuccessful edit" do
log_in_as(#user)
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), user: { name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar"}
assert_template 'users/edit'
end
end
Here is users_controller.rb:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
# debugger
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#Handle a successful save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
#Handle a successful update.
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
#Before filters
# Confirms a logged-in user
def logged_in_user
flash[:danger] = "Please login to access this page."
redirect_to login_url
end
def correct_user
#user - User.find(params[:id])
redirect_to(root_url) unless #user == current_user
end
end
And lastly here are the errors:
1) Failure:
UsersEditTest#test_unsuccessful_edit [/home/robert/sample_app/test/integration/users_edit_test.rb:32]:
expecting <"users/edit"> but rendering with <[]>
2) Failure:
UsersEditTest#test_successful_edit [/home/robert/sample_app/test/integration/users_edit_test.rb:15]:
expecting <"users/edit"> but rendering with <[]>
3) Failure:
UsersControllerTest#test_should_redirect_update_when_logged_in_as_wrong_user [/home/robert/sample_app/test/controllers/users_controller_test.rb:37]:
Failed assertion, no message given.
4) Failure:
UsersControllerTest#test_should_redirect_edit_when_logged_in_as_wrong_user [/home/robert/sample_app/test/controllers/users_controller_test.rb:30]:
Failed assertion, no message given.
This is annoying cause I don't see anything different from the book and the code that I have (in most parts copied and pasted from the book). In the book it apparently works, but when I run the test I get these four failures.
I see, you have a typo in your code.
In your users_controller.rb's correct_user method, you have: #user - User.find(params[:id]), which should be: #user = User.find(params[:id]).
Change that to:
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless #user == current_user
end
See if that fixes your issue.
If not, try adding these two methods to your SessionsHelper module.
If that also does not fix your issue. I would suggest you to clone the original working repo from github and try to figure out what you have missed in your version. I have cloned this github repo and all the tests are passing.
I am new to programming and getting the following errors while trying to get through the 9th chapter of Rails Tutorial. I checked the code several times but still didn’t understand why my local variable or method isn’t being defined. Every time I rewrite the code I get similar errors: undefined local variable or method ‘current_user’.
Error:
ERROR["test_layout_links", SiteLayoutTest, 0.888518]
test_layout_links#SiteLayoutTest (0.89s)
ActionView::Template::Error: ActionView::Template::Error: undefined local variable or method `current_user' for #<#<Class:0x007fcd97c44cf0>:0x007fcd97c4c4a0>
app/helpers/sessions_helper.rb:22:in `logged_in?'
app/views/layouts/_header.html.erb:8:in `_app_views_layouts__header_html_erb__1982327839123609485_70260496954760'
app/views/layouts/application.html.erb:12:in `_app_views_layouts_application_html_erb___2753884707929057206_70260450931560'
test/integration/site_layout_test.rb:6:in `block in <class:SiteLayoutTest>'
app/helpers/sessions_helper.rb:22:in `logged_in?'
app/views/layouts/_header.html.erb:8:in `_app_views_layouts__header_html_erb__1982327839123609485_70260496954760'
app/views/layouts/application.html.erb:12:in `_app_views_layouts_application_html_erb___2753884707929057206_70260450931560'
test/integration/site_layout_test.rb:6:in `block in <class:SiteLayoutTest>'
sessions_controller
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' #Not quite right!
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
users_controller
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
sessions_helper
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 the user corresponding to the remember token cookie.
def current_user?(user)
user == current_user
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 location (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
sessions_helper_test
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
#user = users(:michael)
remember(#user)
end
test "current_user returns right user when session is nil" do
assert_equal #user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
#user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
I think you may be missing the current_user method. It's defined here.
Though the name is similar, current_user is a completely different method to current_user?. The question mark is a convention that typically means the method will return either true or false.
You're missing a current_user method in your sessions_helper.rb. Check back in Chapter 8.57
I've built an app that allows users to sign up, sign in, and sign out (my own devise from the ground up, if you will).
The app allows users to sign up, but when trying to sign in I get the following error:
// (needless to say I'm at a stand still, who ever can figure this out is a genius in they're own right)
NoMethodError in SessionsController#create
undefined method `-' for nil:NilClass
Rails.root: /Users/user/Sites/rails_projects/sample_app1
app/controllers/sessions_controller.rb:11:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"/SEaqnrMf5X0pd4FVWbu8uWVAjTNw4LPKiXg+8Hl0PQ=",
"session"=>{"email"=>"Anthonypane#example.com",
"password"=>"[FILTERED]"},
"commit"=>"Sign in"}
Here are my Session_Controller Contents:
class SessionsController < ApplicationController
def new
#title = "Sign in"
end
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title - "Sign In"
render 'new'
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
redirect_to root_path
end
end
Users_Controller contents
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#user = User.new
#title = "Sign up"
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
redirect_to #user, :flash => {:success => "Welcome to the Fun House!"}
else
#title = "Sign up"
render 'new'
end
end
end
Sessions_helper contents
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
def sign_out
cookies.delete(:remember_token)
self.current_user = nil
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token)
end
def remember_token
cookies.signed[:remember_token] || [nil, nil]
end
end
And Lastly Users_helper contents:
module UsersHelper
def gravatar_for(user, options = { :size => 50})
gravatar_image_tag(user.email.downcase, :alt => user.name,
:class => 'gravatar',
:gravatar => options)
end
end
I think this line:
#title - "Sign In"
Should be this:
#title = "Sign In"
I'm trying to test my controllers using Capybara. I have tried numerous ways, but for some reason I can not get it to see the current_user session. I know the login works when I run my spec for it. I'm also visiting the login page before my controller test. But it always seems to redirect me back when the :logged_in function is hit.
So not sure what I'm missing?
Here's what I have..
session_controller
def create
user = User.find_by_username(params[:username])
if( user && user.authenticate(params[:password]))
user.update_attribute(:token, User.token_digest)
flash[:notice] = "Success"
session[:user_id] = user.id
session[:token] = user.token
redirect_to dashboard_index_path
else
flash[:notice] = "Failed"
flash.now.alert = "Invalid user name or password"
render "new"
end
end
application_controller
protect_from_forgery
before_filter :logged_in
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def logged_in
if !current_user
redirect_to root_url
return
end
if session[:token] != current_user.token
redirect_to root_url
end
end
products_controller_spec
it 'Should display new product form' do
user_login
visit new_product_path
response.body.should have_content("<h1>Create New Product</h1>")
end
spec_helper.rb
def user_login
visit root_path #this is new_session
fill_in "username", :with => "admin"
fill_in "password", :with => "111111"
click_button "Login"
end
Well I got it working,Not sure its politically correct way but.. instead of visiting the page, I'm just hard setting the session. In spec_helper.rb..
def user_login
user = FactoryGirl.create(:user)
session[:user_id] = user.id
session[:token] = User.token_digest
end