为什么before_actions声明的顺序很重要?

时间:2014-07-31 01:06:12

标签: ruby-on-rails rspec railstutorial.org

我正在研究Hartl的Ruby on Rails教程。在执行第11章时,我更新了UserController中的before_actions,并且认为订单不重要。所以我看起来像这样:

class UsersController < ApplicationController
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  before_action :signed_in_user,
            only: [:index, :edit, :update, :destroy, :following, :followers]

他看起来像这样:

class UsersController < ApplicationController
  before_action :signed_in_user,
            only: [:index, :edit, :update, :destroy, :following, :followers]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy

唯一的区别是陈述的顺序。

当我在我的运行rspec时,我得到四个错误。但是当我改变订单时,没有。我没有看到任何依赖声明的顺序。我想知道为什么订单很重要。

这是我的问题......以下是有关我正在运行的代码的更多信息。

因为我在本书的最后,有很多代码,所以我会尝试包含我认为可能相关的部分。

声明中列出的两个动作是在同一个类的末尾定义的:

class UsersController < ApplicationController

  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  before_action :signed_in_user,
            only: [:index, :edit, :update, :destroy, :following, :followers]

.
.
.
  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

第三个是在SessionsHelper中定义的,它包含在ApplicationController中,是UsersController的父类:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

module SessionsHelper
.
.
.
  def current_user=(user)
    @current_user = user
  end

  def current_user
    remember_token = User.digest(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end

  def current_user?(user)
    user == current_user
  end

  def signed_in_user
    unless signed_in?
      store_location
      redirect_to signin_url, notice: "Please sign in."
    end
  end

  def signed_in?
    !current_user.nil?
  end
.
.
.

如果运行rspec并按照我的方式运行代码,结果如下:

>rspec spec
.............................................................................F.
....FF.............................................F...........................
.............

Failures:

  1) Authentication authorization for non-signed-in users when attempting to visit a protected page after signing in should render the desired protected page
     Failure/Error: expect(page).to have_title('Edit user')
       expected #has_title?("Edit user") to return true, got false
     # ./spec/requests/authentication_pages_spec.rb:68:in `block (6 levels) in <top (required)>'

  2) Authentication authorization for non-signed-in users in the Users controller visiting the edit page 
     Failure/Error: it { should have_title('Sign in') }
       expected #has_title?("Sign in") to return true, got false
     # ./spec/requests/authentication_pages_spec.rb:117:in `block (6 levels) in <top (required)>'

  3) Authentication authorization for non-signed-in users in the Users controller submitting to the update action 
     Failure/Error: specify { expect(response).to redirect_to(signin_path) }
       Expected response to be a redirect to <http://www.example.com/signin> but was a redirect to <http://www.example.com/>.
       Expected "http://www.example.com/signin" to be === "http://www.example.com/".
     # ./spec/requests/authentication_pages_spec.rb:122:in `block (6 levels) in <top (required)>'

  4) User pages as admin user should not be able to delete it's own account 
     Failure/Error: expect { delete user_path(admin) }.not_to change(User, :count)
     NoMethodError:
       undefined method `admin?' for nil:NilClass
     # ./app/controllers/users_controller.rb:95:in `admin_user'
     # ./spec/requests/user_pages_spec.rb:79:in `block (5 levels) in <top (required)>'
     # ./spec/requests/user_pages_spec.rb:79:in `block (4 levels) in <top (required)>'

Finished in 8.84 seconds
171 examples, 4 failures

Failed examples:

rspec ./spec/requests/authentication_pages_spec.rb:67 # Authentication authorization for non-signed-in users when attempting to visit a protected page after signing in should render the desired protected page
rspec ./spec/requests/authentication_pages_spec.rb:117 # Authentication authorization for non-signed-in users in the Users controller visiting the edit page 
rspec ./spec/requests/authentication_pages_spec.rb:122 # Authentication authorization for non-signed-in users in the Users controller submitting to the update action 
rspec ./spec/requests/user_pages_spec.rb:78 # User pages as admin user should not be able to delete it's own account 

以下是authentication_pages_spec.rb中的失败测试:

describe "Authentication" do
  subject { page }
.
.
.
  describe "authorization" do

    describe "for non-signed-in users" do
      let(:user) { FactoryGirl.create(:user) }

      describe "when attempting to visit a protected page" do
        before do
          visit edit_user_path(user)
          sign_in(user)
        end

        describe "after signing in" do

          it "should render the desired protected page" do
            expect(page).to have_title('Edit user')
          end

.
.
.
      end
.
.
.
      describe "in the Users controller" do

        describe "visiting the edit page" do
          before { visit edit_user_path(user) }
          it { should have_title('Sign in') }
        end

        describe "submitting to the update action" do
          before { patch user_path(user) }
          specify { expect(response).to redirect_to(signin_path) }
        end
        describe "visiting the user index" do
          before { visit users_path }
          it { should have_title('Sign in') }
        end

        describe "visiting the following page" do
          before { visit following_user_path(user) }
          it { should have_title('Sign in') }
        end

        describe "visiting the followers page" do
          before { visit followers_user_path(user) }
          it { should have_title('Sign in') }
        end

      end
    end

为了完成,让我补充一点,如果我将before_action声明的顺序更改为:

  before_action :signed_in_user,
                only: [:index, :edit, :update, :destroy, :following, :followers]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy

所有测试均通过:

>rspec spec
..............................................................
..............................................................
...............................................

Finished in 8.82 seconds
171 examples, 0 failures

代码似乎在浏览器中完美运行。声明的顺序仅影响测试的结果。我花了一段时间才找到这个问题,回过头来看看Hartl的代码并确保我的确反映了他的问题。我在一段时间后发现了这个订单差异并进行了更改,突然我的测试全部通过了。我已经多次来回切换订单,以确保它是订单,只有订单正在改变,这才有所作为。我想了解所以当我编写自己的代码而不是复制别人的代码时,我没有遇到类似的问题。

2 个答案:

答案 0 :(得分:1)

显然,before_action的顺序非常重要。

如果您在第一次使用signed_in_user,则其他操作(例如correct_user或admin_user方法)可能没有current_user变量可供使用。

首次登录时,您的代码可能会正常运行。但是,如果您在登录前调用操作,则会收到current_user is nil的错误。

答案 1 :(得分:1)

看起来在这个控制器中,signed_in_user充当警卫以确保在继续之前有一个有效的current_user(如果没有,则重定向到登录页面,将创建一个)。

您的第四次测试将此消除 - admin?未定义,因为在admin_user之前调用了signed_in_user。首先调用signed_in_user时,除非实际上有admin_user个有效current_user,否则不会调用admin?。所以这里实际上依赖于过滤器的顺序 - 首先要调用signed_in_user ,因为它确保存在有效的current_user,而其他过滤器则认为这已经发生了。