I am currently in the process of learning Ruby on Rails through Michael Hartl's Rails Tutorial (Chapter 12). I am suddenly getting the below error.
UsersControllerTest#test_should_redirect_destroy_when_not_logged_in:
NoMethodError: undefined method admin?' for nil:NilClass
app/controllers/users_controller.rb:92:inadmin_user'
test/controllers/users_controller_test.rb:48:in block (2 levels) in <class:UsersControllerTest>'
test/controllers/users_controller_test.rb:47:inblock in '
Here is my test code:
test "should redirect destroy when not logged in" do
assert_no_difference 'User.count' do
delete :destroy, id: #user
end
assert_redirected_to login_url
end
And the rest of my code:
class UsersController < ApplicationController
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
:following, :followers]
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
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)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
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
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
Can someone please have a look and tell me where I am going wrong? The code has basically been copied from the tutorial so I'm at a loss.
Thanks.
I had this same error in the Hartl tutorial. The problem was a missed :destroy action in listing 9.53 in users_controller.rb. Tests were back to green once I found that missing bit.
That said, it would be great if someone could explain precisely why this is the case.
The correct code:
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
Change admin_user method like this:-
def admin_user
redirect_to(root_url) unless current_user.present? && current_user.admin?
end
This will check first that current_user is present or not then check second condition(current_user.admin?). If current_user.present? will false then it will not check second condition.
You need to update your admin_user method to check if user is even logged in.
# Confirms an admin user.
def admin_user
redirect_to(root_url) if current_user.nil? || !current_user.admin?
end
Related
I have a user and article model where a user can have many models, and have restricted the delete function to user or admin. yet when i attempt to destroy the article, i get the following error:
undefined method `user' for nil:NilClass
and its pointing to this private function in my articles controller:
def require_same_user
if current_user != #article.user and !current_user.admin?
flash[:danger] = "You can only edit or delete your own articles"
redirect_to root_path
end
end
this is my whole controller file:
class ArticlesController < ApplicationController
before_action :set_article, only: [:edit, :update, :show]
before_action :require_user, except: [:index, :show]
before_action :require_same_user, only: [:edit, :update, :destroy]
def index
#articles = Article.paginate(page: params[:page], per_page: 5)
end
def show
end
def new
#article = Article.new
end
def edit
end
def update
#article.update(article_params)
if #article.save
flash[:success] = "Article successfully updated!"
redirect_to article_path(#article)
else
flash[:danger] = "Sorry, try again..."
render :edit
end
end
def create
#article = Article.new(article_params)
#article.user = current_user
if #article.save
flash[:success] = "New article created"
redirect_to articles_path
else
flash[:danger] = "Sorry, invalid values"
render :new
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
flash[:success] = "Article deleted"
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:name, :title, :description)
end
def set_article
#article = Article.find(params[:id])
end
def require_same_user
if current_user != #article.user and !current_user.admin?
flash[:danger] = "You can only edit or delete your own articles"
redirect_to root_path
end
end
end
the articles and users exist in db so what could this be? thanks in advance
You're setting the article using the set_article function, but in the case of the require_same_user function, it doesn't know what's the value of #article; so in that case, the value is nil, as it's not in the scope, nor figure as an instance variable created before.
def require_same_user
# Here there's no #article, so it's evaluated as nil,
# and nil doesn't have a method user.
if current_user != #article.user ...
...
One approach could be to set the set_article also to be executed before the require_same_user does it.
before_action :set_article, only: %i[edit update show require_same_user]
You could also divide your code in smaller pieces to be used back again whenever you need it:
def require_same_user
redirect_to_root_path unless article_owner? && admin?
end
def redirect_to_root_path
flash[:danger] = 'You can only edit or delete your own articles'
redirect_to root_path
end
def admin?
current_user.admin?
end
def article_owner?
current_user == #article.user
end
Make sure set_article runs before destroy action.
In your controller:
before_action :set_article, only: [:edit, :update, :show, :destroy]
I've been following Rails Tutorial from Michael Hartl, https://www.railstutorial.org/book/updating_and_deleting_users.
I am having an error to pass one of the action level tests for admin access control test.
The test failed is as follows:
def setup
#user = users(:jessie) #admin
#other_user = users(:brenda) #non_admin
end
test "should redirect destroy when logged in as a non-admin" do
log_in_as(#other_user)
assert_no_difference 'User.count' do
delete user_path(#user)
end
assert_redirected_to root_url
end
The error that the terminal gives me is :
FAIL["test_should_redirect_destroy_when_logged_in_as_a_non-admin", UsersControllerTest, 1.6799899999750778]
test_should_redirect_destroy_when_logged_in_as_a_non-admin#UsersControllerTest (1.68s)
Expected response to be a redirect to root_url but was a redirect to login_url.
Expected root_url to be === login_url.
test/controllers/users_controller_test.rb:65:in `block in '
My users_controller.rb file is:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
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
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def index
#users = User.paginate(page: params[:page])
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
#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 current user.
def correct_user
#user = User.find(params[:id])
recirect_to(root_url) unless current_user?(#user)
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Confirms an admin user
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
As I've been following the tutorial strictly and I am still very new to ruby on rails, it's very hard for me to identify where went wrong. I've been googling for solutions for a few hours, but in vain. Any help would be greatly appreciated. Thank you :)
This is my first foray into app development, and I have been following Michael Hartl's tutorial pretty much verbatim. All went fine until towards the end of chapter 12, when my tests started failing. The strange thing is that the tests which fail seem to be unrelated to the changes I made in chapter 12.
There are three failures and one error, and they are also duplicated by the local server when manually checked in the browser. Basically the logged_in_user before action doesn't seem to work, but only with some of the model actions, not all. I've tried to trace it with the debugger and tried to revert back to pre-chapter 12 commits (which all definitely worked) but I'm having trouble with database migrations and stuff and only introducing more and more errors. Below is the error log, my users controller, application controller and sessions helper. I feel like the error must be in one of those.
Many many thanks in advance for your help!
FAIL["test_should_redirect_update_when_not_logged_in", UsersControllerTest, 2016-03-06 05:58:45 -0800]
test_should_redirect_update_when_not_logged_in#UsersControllerTest
(1457272725.92s)
Expected true to be nil or false
test/controllers/users_controller_test.rb:27:in `block in '
ERROR["test_should_redirect_destroy_when_not_logged_in", UsersControllerTest, 2016-03-06 05:58:45 -0800]
test_should_redirect_destroy_when_not_logged_in#UsersControllerTest
(1457272725.96s) NoMethodError: NoMethodError: undefined
method admin?' for nil:NilClass
app/controllers/users_controller.rb:77:inadmin_user'
test/controllers/users_controller_test.rb:47:in block (2 levels) in <class:UsersControllerTest>'
test/controllers/users_controller_test.rb:46:inblock in '
app/controllers/users_controller.rb:77:in admin_user'
test/controllers/users_controller_test.rb:47:inblock (2 levels) in '
test/controllers/users_controller_test.rb:46:in `block in '
FAIL["test_should_redirect_edit_when_not_logged_in", UsersControllerTest, 2016-03-06 05:58:46 -0800]
test_should_redirect_edit_when_not_logged_in#UsersControllerTest
(1457272726.03s)
Expected true to be nil or false
test/controllers/users_controller_test.rb:21:in `block in '
FAIL["test_successful_edit_with_friendly_forwarding", UsersEditTest, 2016-03-06 05:58:46 -0800]
test_successful_edit_with_friendly_forwarding#UsersEditTest
(1457272726.07s)
Expected response to be a redirect to <[removed link]/users/633107804/edit> but was a redirect to <[removed
link]/users/633107804>.
Expected "[removed link]users/633107804/edit" to be === "[removed link]/users/633107804".
test/integration/users_edit_test.rb:22:in `block in '
class UsersController < ApplicationController
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to_users_url
end
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
end
def new
#user = User.new
end
def index
#users = User.paginate(page: params[:page])
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
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)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
And the application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
private
#confirms a logged in user. Now placed in the application controller so both teh user controller and microposts controller can use it
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
And the sessions helper:
module SessionsHelper
#logs in the given user
def log_in(user)
session[:user_id] = user.id
end
# returns the current logged-in user (if any)
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: session[:user_id])
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(:remember, cookies[:remember_token])
log_in user
#current_user = user
end
end
end
# returns true if the user is logged in, and false otherwise
def logged_in?
!current_user.nil?
end
#Saves a user id and remember token digest to cookies
def remember(user)
user.remember
#Code to save cookies with 20 year expiry ("permanent") and "signed" so it can't be accessed by third party
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
# runs the forget method and removes remember tokens from cookies
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# logs out the given user
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
def current_user?(user)
user == current_user
end
# Redirects to stored location after trying to edit while logged out and then logging in
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
Userscontrollertest:
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
#user = users(:Joe)
#other_user = users(:Jane)
end
test "should redirect to index when not logged in" do
get :index
assert_redirected_to login_url
end
test "should get new" do
get :new
assert_response :success
end
test "should redirect edit when not logged in" do
get :edit, id: #user
assert_not flash.empty?
assert_redirected_to login_url
end
test "should redirect update when not logged in" do
patch :update, id: #user, user: { name: #user.name, email: #user.email }
assert_not flash.empty?
assert_redirected_to login_url
end
test "should redirect edit when logged in as wrong user" do
log_in_as(#other_user)
get :edit, id: #user
assert flash.empty?
assert_redirected_to root_url
end
test "should redirect update when logged in as the wrong user" do
log_in_as(#other_user)
patch :update, id: #user, user: { name: #user.name, email: #user.email }
assert flash.empty?
assert_redirected_to root_url
end
test "should redirect destroy when not logged in" do
assert_no_difference 'User.count' do
delete :destroy, id: #user
end
assert_redirected_to login_url
end
test "should redirect destroy when logged in as a non-admin" do
log_in_as(#other_user)
assert_no_difference 'User.count' do
delete :destroy, id: #user
end
assert_redirected_to root_url
end
end
After comparing your code to what I have in my tutorial code, try changing the order of the before_action statements in your UsersController and see if that works:
Before:
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
After:
before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
All the before_action statements are run in the order they are defined in the controller, so for the update action, you're currently running the correct_user filter first, which calls current_user?(#user), which calls current_user:
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: session[:user_id])
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(:remember, cookies[:remember_token])
log_in user
#current_user = user
end
end
end
At that point in your tests, since you haven't logged in a user yet, you don't have a session[:user_id], so that if statement fails, and you also don't have a cookies.signed[:user_id], so the elsif statement fails, leaving you with a nil return value and #current_user not being set.
At some point in your request in the test, you seem to be finding its way over to the admin_user method, which is calling current_user.admin?, and since at this point current_user is nil, you're getting the NoMethodError: undefined method admin?' for nil:NilClass that you're seeing.
Hello guys so what i want to do is simple.
I want a before filter to check if the current user is admin, to automatically redirect him to the admin panel even if the admin accesses the index page(e.g localhost:3000 which is the root url for normal users)
for the admin users the path is localhost:3000/admin/
I have written these blocks of code so far but cause a redirect loop
in my application_controller.rb i have this
include SessionsHelper
before_filter :admin_users?
private
def admin_users?
if current_user.admin?
redirect_to admin_path
end
end
and the current_user method is the following which is in sessionshelper.rb
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?(:remember, cookies[:remember_token])
log_in user
#current_user = user
end
end
end
here is users controller
class UsersController < ApplicationController
before_filter :admin_users? # application controller method
before_action :logged_in_user, only: [:index,:edit,:update] # is in Application Controller
before_action :correct_user, only: [:edit,:update]
before_action :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def edit
#user = User.find(params[:id])
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:name,:email,:password,:password_confirmation)
end
# before filters
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
any clues?
The issue is next. You have put your redirect inside application_controller.rb, that all other controller inherit from, even Admin controller.
You have to do next:
remove before_filter :admin_user from your ApplicationController, and put it only where you want to check if user is admin, all controllers except admin controller.
You have redirect loop because admin controller will redirect to admin_path, when you come to admin_path.
I'm working through the Rails Tutorial, by Michael Hartl, and a question popped up, as I was creating an admin user.
I followed the instructions, and created an admin_user, who has access to the :destroy method. It also isn't attr_accessible, so people can't simply put a put request via the browser and change themeselves to admin.
But, I have a two-part question--
1) How would I make a user admin?
I though I would need to write something like this in the console
rails console
user = User.find(params[:101])
user.toggle!(:admin)
When I try that, I get
Undefined Local Variable or Method 'Params' for main:Object
2) Assuming that it is possible to make myself an admin, what's stopping other people from making themselves admin using a command line as well?
Here's a copy of the users_controller, I think Michael addressed this in the tutorial, and I followed his instructions, but I don't get how the below code prevents someone from going to the command line and making themselves admin
class UsersController < ApplicationController
before_filter :signed_in_user,
only: [:edit, :update, :index, :destroy]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: :destroy
def destroy
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_url
end
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
end
def new
unless signed_in?
#user = User.new
else
redirect_to #current_user
end
end
def create
unless signed_in?
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
else
redirect_to #current_user
end
end
def edit
end
def update
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
I would really appreciate your help clearing things up!
User.find(params[:101]) is appropriate only for http browser requests. If you visit http://www.example.com?101=test, then you can use params[:101] with value "test". But in console you can't use params unless you declare it. In your case the wright way will be User.find(101), if 101 is user id.
Other people can't make them admin because you didn't add attr_accessible for admin field. How can they do it via command shell? They have no access to command line. If they are it's a serious security breach.