Ruby教程Ch9练习#9 - 不允许管理员删除自己

时间:2012-03-31 14:01:27

标签: ruby-on-rails-3 tdd railstutorial.org

我是Ruby和Rails的新手,这就是为什么我要通过Michael Hartl的Rails教程。我被困在第9章,练习#9。我已将用户控制器中的def destroy代码更新为:

def destroy
  user = User.find(params[:id])
  if (current_user == user) && (current_user.admin?)
    flash[:error] = "Can not delete own admin account!"
  else
    user.destroy
    flash[:success] = "User destroyed."
  end
redirect_to users_path
end

当我在浏览器中测试时,通过在管理员登录时向current_user添加“删除”链接,这似乎有效。但是练习说先写一个测试 - 我做了但似乎没有用。以下是我的测试内容:

describe "as admin user" do
  let(:user_admin) { FactoryGirl.create(:admin) }

  before { sign_in user_admin }

  describe "submitting a DELETE request to destroy own admin account" do
    before { delete user_path(user_admin) }
    it { should have_selector('div.alert.alert-error', text: 'delete own admin') }
  end
end

也许我测试的不应该被测试。如何测试用户控制器中def def代码的修改?

7 个答案:

答案 0 :(得分:3)

我试图让原始帖子中的代码生效,但没有运气。相反,我让它像这样工作(让它失败,然后通过)。这将测试正确的重定向以及正确的Flash消息。

测试:authentication_pages_spec.rb

  describe "as admin user" do
    let(:admin) { FactoryGirl.create(:admin) }
    before { sign_in admin }

    describe "can't delete self by submitting DELETE request to Users#destroy" do
      before { delete user_path(admin) }
      specify { response.should redirect_to(users_path), 
                  flash[:error].should =~ /Can not delete own admin account!/i }
    end
  end

实施:用户#troy

def destroy
    user = User.find(params[:id])
    if (current_user == user) && (current_user.admin?)
      flash[:error] = "Can not delete own admin account!"
    else
      user.destroy
      flash[:success] = "User destroyed."
    end
  redirect_to users_path
  end

原始测试不起作用的原因可能是因为我们发出请求的方式? 我尝试将以下各项分别添加到describe块中,但都失败了:

it { should have_selector('div.alert.alert-error', text: 'delete own admin') }

it { should have_selector('title', text: 'All users') }
it { should have_selector('h1', text: 'All users') }

所以,似乎Capybara实际上并没有重定向到页面来检查这些选择器。我尝试'标题'和'h1',认为选择器'div.alert.alert-error'可能存在一些问题...但是'title'和'h1'失败了同样的“预期的CSS返回一些东西” ...

任何人都更了解specify { response.should ... }样式测试的工作原理?如果他们在点击控制器操作时没有遵循重定向?

答案 1 :(得分:1)

我也是Rails Tutorial(和Rails一般)的新手,也有同样的问题,你的问题帮助我找到答案。

我仍然不确定为什么你的代码确实失败了,但是以下步骤肯定有效。

首先,稍微修改测试代码以使用以下结构(这里我省略了这个描述块的位置 - 你已经有了正确的位置):

describe "deleting herself" do
  it "should not be possible" do
    expect { delete user_path(admin) }.to_not change(User, :count).by(-1)
  end
end

请注意,我正在使用expect {}块来跟踪User对象的数量。这肯定会导致测试变为红色(这点很好),而检查Flash将使测试变为红色,但检查错误闪存在这里似乎不起作用。我真的不知道为什么!也许与发生的双重定向有关?

接下来,编写保护代码以使测试再次变为绿色。你的代码是有效的(我认为),但我认为我的代码更加惯用,因为它使用了第9章前面定义的会话助手。

def destroy
  user = User.find(params[:id])
  if (current_user? user) && (current_user.admin?)
    flash[:error] = "You are not allowed to delete yourself as an admin."
  else
    user.destroy
    flash[:success] = "User destroyed. ID: #{user.id}"
  end
  redirect_to users_path
end

这一改变使我的测试再次变为“绿色”,成功完成了练习10。

答案 2 :(得分:1)

还值得指出的是,在列表9.43下的tutorial下,app / views / users / _user.html.erb中的部分视图会检查以防止显示当前签名的“删除”链接 - 在用户索引页面上的admin用户中。

因此,即使用户无法通过Web UI删除自己的帐户,我想练习9.9还要进一步确保控制器级别存在逻辑,以防有人制作并向当前用户发送http删除请求。 / p>

在整个rails应用程序中添加这些安全防护装置可能是一个很好的做法,以防止出现任何奇怪的错误。

由于部分视图上的过滤器使得您无论如何都不会看到错误闪存,您还可以通过删除“其他”来简化用户控制器的销毁操作。

def destroy
  user = User.find(params[:id])
  unless current_user?(user)
    user.destroy
    flash[:success] = "User deleted."
  end
  redirect_to users_url
end

答案 3 :(得分:0)

我在Rails中也是新手,只是第一次做教程,你的帖子对我有很多帮助但只是为了贡献,你真的不需要检查用户是否是破坏中的管理员作为破坏将在添加行

时仅供管理员用户使用
before_action :admin_user,     only: :destroy

在用户控制器中。

所以只要询问是否与当前用户不一样就足够了

def destroy
  usertodestroy = User.find(params[:id])
  if (current_user == usertodestroy)
    flash[:error] = 'Can´t delete own user'
  else
    usertodestroy.destroy
    flash[:success] = "User destroyed. ID: #{usertodestroy.name}"
    redirect_to users_url
  end
end

此外,测试应该只是要求在尝试删除后计数没有改变

describe "as admin user" do
  let(:admin) { FactoryGirl.create(:admin) }
  before { sign_in(admin) }

  it "should not be able to delete itself" do
    expect { delete user_path(admin)  }.not_to change(User, :count) 
  end
end

两者都不会改变结果,但只是保持简单。

为什么我真的不知道为什么下一个代码在任何情况下都会在测试时删除用户:

describe "as admin user" do
  let(:admin) { FactoryGirl.create(:admin) }
  before { sign_in(admin) }

  it "should not be able to delete itself" do
    expect { admin.destroy  }.not_to change(User, :count) 
  end
end

在我看来,这是直接调用UsersController所以不应该删除它。

答案 4 :(得分:0)

尽管遵循了上述建议,我的测试仍然没有通过,但错误:

undefined method `admin?' for nil:NilClass

我认为这意味着登录时存在一些问题,因为这只是作为before_filter'admin_user'检查的一部分调用的。

我能够通过使用登录方法

的非水豚版来解决这个问题
before { signin admin, no_capybara: true }

谢谢!

答案 5 :(得分:0)

我很好地应用了eblume建议,但我有一些评论和疑问:

首先,测试可以简化,因为我们不需要检查计数是否会改变一个单位,但是如果它改变了任何数字:

expect { delete user_path(admin) }.not_to change(User, :count)

关于控制器中的代码,它也可以简化。正如我们在破坏行动之前编码了以下内容:

def admin_user
    redirect_to(root_url) unless current_user.admin?
end

如果用户是admin,则无需在'destroy'方法内检查,它必须是admin。

所以if子句变为:

if (current_user? user)

我现在的问题是:我不明白这段代码,我不知道这项检查是做什么的。

我的第一次尝试是使用以下代码:

if (current_user.id == params[:id])

但这不起作用,我不明白为什么。

答案 6 :(得分:0)

控制器操作

def destroy
  usertodestroy = User.find(params[:id])
  if (current_user == usertodestroy)
    flash[:error] = 'Can´t delete own user'
    redirect_to root_url
  else
    usertodestroy.destroy
    flash[:success] = "User destroyed. ID: #{usertodestroy.name}"
    redirect_to users_url
  end
end

并测试

describe "as admin user" do
  let(:admin) { FactoryGirl.create(:admin) }
  before { sign_in admin, no_capybara: true }

  it "attempting to delete self" do
    expect{ delete user_path(admin) }.not_to change(User, :count)
  end
end

为我工作。