Reputation: 4119
I'm going through the latest rails tutorial on railstutorial.org and I am stuck on a certain exercise (#8 on http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#sec:updating_deleting_exercises). You have to write a rspec/capybara test to make sure the admin can't delete themself. I have the implementation working, but can't get the test to work right. Here is my code. I found similar questions here: Ruby on Rails syntax and https://getsatisfaction.com/railstutorial/topics/how_to_prevent_admin_user_from_deleting_themselves . But I think it is an older tutorial and not the same question.
Here is the relevant code in spec/requests/user_pages_spec.rb:
describe "User pages" do
subject { page }
describe "delete links" do
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) }
before do
sign_in admin
visit users_path
end
it "should not be able to delete themself" do
expect { admin.delete }.should_not change(User, :count)
end
end
end
end
end
The error message says that the user count is getting reduced by 1.
For completeness, here is my (working) implementation:
class UsersController < ApplicationController
before_filter :current_admin, only: :destroy
def current_admin
@user = User.find(params[:id])
redirect_to users_path, notice: "Cannot delete current admin" if current_user?(@user)
end
end
Where am I going wrong, thanks? (I left out some methods, but hopefully there is enough to figure out what I'm trying to do)
Edit: Using Ruby v1.9.3, Rails v3.2.3. By default, there is no delete link for admins.
Edit2: Here is what I got working:
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
describe "admins" do
let(:admin) { FactoryGirl.create(:admin) }
it "should not be able to delete themself" do
sign_in admin
expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
end
end
end
users_controller.rb
def destroy
@user = User.find(params[:id])
if current_user?(@user)
flash[:error] = "Cannot delete current admin"
else
@user.destroy
flash[:success] = "User destroyed."
end
redirect_to users_path
end
Upvotes: 3
Views: 7293
Reputation: 81
This can be tested in the user_pages_spec which is pretty much what the railstutorial.org book seems to want you to do. (Ruby on Rails Tutorial Chapter 9, Exercise 9). Whether that is a good idea I leave up to greater minds.
The test code in user_pages_spec.rb would look like this:
describe "I should not be able to delete admins" do
before { delete user_path(admin.id) }
it { should_not have_selector('div.alert.alert-error', text: 'Admins cannot delete themselves') }
end
the delete user_path(admin.id) will mimic clicking 'delete' and works in rspec-capybara. This will pass with your controller code above, assuming you change your error message to match mine or vice-versa.
Also, the before_filter syntax in the UsersController seems to work either with or without the [] if there is only item.
Upvotes: 8
Reputation: 995
The syntax of your before_filter is incorrect. The call should look like this
before_filter :current_admin, :only => [:destroy]
You would also probably be better off keeping this logic in the destroy action. Since it only applies to that action, I don't see any reason to move it into a separate method/filter. The other questions you pointed to are in fact from older versions of the tutorial, but the logic is nonetheless the same:
class UsersController < ApplicationController
def destroy
@user = User.find(params[:id])
if current_user?(@user)
flash[:error] = "Cannot delete current admin"
else
user.destroy
flash[:notice] = "User was successfully deleted"
end
redirect_to users_path
end
end
As for your test, it's failing because you are calling the delete method instead of the destroy action in your controller. From ActiveRecord::Relation
Active Record objects are not instantiated, so the object’s callbacks are not executed, including any :dependent association options or Observer methods.
Since they're asking you to use rspec/capybara, you can use the click_link method to trigger the destroy action. Since you're on an index page with multiple listings, you should look into Capybara::Node::Finders in order to select the correct button reliably.
Edit: Since you're looking to test the controller, not the view, you can test with:
describe "admins" do
let(:admin) { FactoryGirl.create(:admin) }
it "should not be able to delete themself" do
sign_in admin
expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
end
end
Upvotes: 7