I'm following along with Michael Hartl's rails tutorial and making small adjustments. Users can make microposts (status updates) on their profiles, at which point the page will appear to reload and their new status will be displayed. I can confirm by hand that this works exactly as intended. The problem is that tests for it aren't passing for some reason, even though the end result outside of tests looks perfect.
Screenshots of microposts_controller.rb, microposts_interface_test.rb (the failing test), related error messages, and users_controller.rb, in that order: http://imgur.com/a/IS1HI
microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Status updated!"
redirect_to #user
else
#feed_items = []
flash[:warning] = "Status was blank!"
redirect_to #user
end
end
def destroy
#micropost.destroy
flash[:success] = "Status deleted."
redirect_to #user
end
microposts_interface_test.rb:
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
#user = users(:mrtestit)
end
test "micropost interface" do
log_in_as(#user)
assert is_logged_in?
# Invalid submission
assert_no_difference 'Micropost.count' do
post microposts_path, micropost: { content: "" }
end
# Valid submission
content = "This status really ties the room together"
assert_difference 'Micropost.count', 1 do
post microposts_path, micropost: { content: content }
end
follow_redirect!
assert_match content, response.body
# Delete a post.
assert_select 'a', text: 'delete'
first_micropost = #user.microposts.paginate(page: 1).first
assert_difference 'Micropost.count', -1 do
delete micropost_path(first_micropost)
end
# Visit a different user.
get user_path(users(:archer))
assert_select 'a', text: 'delete', count: 0
end
end
users_controller.rb:
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 index
#users = User.where(activated: true).paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
if logged_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
#microposts = #user.microposts.paginate(page: params[:page])
redirect_to root_url and return unless #user.activated?
end
I understand that, for some reason, this is saying that #user is nil. However, I've confirmed many times over that redirect_to #user works everywhere else.
The fact that every other test passes is already proof of that, but just to go the extra mile, I've confirmed that the failing redirect_to #user line of code works in other files, such as when a user updates their profile information under their settings page and is then redirected to their profile page. I've spent over 10 hours working on this (mostly yesterday) and am on the verge of simply deleting this test and moving on with my life. Stackoverflow is my last resort.
I think that you must change
redirect_to #user
to
redirect_to current_user
#user is class instance variable and must be created manually, eg:
#user = User.find_by_id post.owner_id
While current_user is global variable and can be used anywhere.
But don't forget check it to nil because of user can be not signed in!
You can check it manually by unless current_user.nil? in each method or better add helper method and use it in your controllers, tutorial already describes this way:
module SessionHelper
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
# ...
class MicropostsController < ApplicationController
before_action :signed_in_user
# ...
Update:
For Aleks's advice: if you want redirect to post's owner that might be not current user, use
user = micropost.owner
redirect_to user
There are two possible solutions for this.
First one is this:
Change this:
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Status updated!"
redirect_to #user
else
#feed_items = []
flash[:warning] = "Status was blank!"
redirect_to #user
end
end
to this:
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Status updated!"
redirect_to current_user
else
#feed_items = []
flash[:warning] = "Status was blank!"
redirect_to current_user
end
end
OR (if first doesn't work) the second approach:
Update this line:
before_action :correct_user, only: :destroy
to:
before_action :correct_user, only: [:create, :destroy]
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 :)
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
So I've adapted the Rails tutorial a little, and am trying to use the Micropost feature as a listing of posts only.
The problem is that when the micropost is deleted, the focus goes back to the root URL and I am looking to not redirect. My trial and error didn't work.
Essentially, what am I looking for. After the deletion of the post, I'd like the focus to remain on the user/1 page, which is the display listing of microposts. I tried simply removing the line of code for the redirect, but that was not the answer.
Here's the micropost controller:
class MicropostsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
before_filter :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(params[:micropost] )
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = [ ]
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_url
end
def correct_user
#micropost = current_user.microposts.find(params[:id])
redirect_to root_url if #micropost.nil?
end
end
As I mentioned in the comment, I found the answer by trial and error. I changed the redirect_to. It then reads:
redirect_to #current_user
which did the trick.
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.