如何处理 Grape 中特定操作的过滤器之前?

How to handle before filter for specific action in Grape?

我正在我的 Rails 项目中安装 Grape 以构建 RESTful API。

现在一些端点的操作需要身份验证,而另一些则不需要身份验证。

例如,我有 users 端点,看起来像:

module Backend
  module V1
    class Users < Grape::API
      include Backend::V1::Defaults

      before { authenticate! }

      resource :users do

        desc "Return a user"
        params do
          requires :id, type: Integer, desc: 'User id'
        end
        get ':id' do
          UsersService::Fetch.new(current_user,params).call
        end

        desc "Update a user"
        params do
          requires :id, type: Integer, desc: 'User id'
          requires :display_name, type: String, desc: 'Display name'
          requires :email, type: String, desc: 'Email'
        end
        post ':id' do
          UsersService::Save.new(current_user,params).call
        end

        desc "Reset user password"
        params do
          requires :old_password, type: String, desc: 'old password'
          requires :password, type: String, desc: 'new password'
        end
        post 'password/reset' do
          PasswordService::Reset.new(current_user,params).call
        end

        desc "Forget password"
        params do
          requires :email, type: String
        end
        post 'password/forget' do
          PasswordService::Forget.new(current_user,params).call
        end            

      end
    end
  end
end

现在如您所见,除 password/forget 之外的所有操作都需要用户为 logged-in/authenticated。创建一个新的端点也没有意义,比如 passwords 并且只是删除 password/forget 从逻辑上讲,这个端点应该与用户资源相关。

问题在于 Grape before 过滤器没有像 except, only 这样的选项,我可以在其中说对某些操作应用过滤器。

一般情况下,您是如何处理得干干净净的?

一种肮脏的帮助方式是使用 namespace,例如:

module Backend
  module V1
    class Users < Grape::API
      include Backend::V1::Defaults

      namespace :users do
        desc "Forget password"
        params do
          requires :email, type: String
        end
        post 'password/forget' do
          PasswordService::Forget.new(current_user,params).call
        end

        namespace do
          before { authenticate! }

          desc "Return a user"
          params do
            requires :id, type: Integer, desc: 'User id'
          end
          get ':id' do
            UsersService::Fetch.new(current_user,params).call
          end

          desc "Update a user"
          params do
            requires :id, type: Integer, desc: 'User id'
            requires :display_name, type: String, desc: 'Display name'
            requires :email, type: String, desc: 'Email'
          end
          post ':id' do
            UsersService::Save.new(current_user,params).call
          end

          desc "Reset user password"
          params do
            requires :old_password, type: String, desc: 'old password'
            requires :password, type: String, desc: 'new password'
          end
          post 'password/reset' do
            PasswordService::Reset.new(current_user,params).call
          end            

        end
      end
    end
  end
end

这样我们不会 运行 在过滤 users/password/forget 之前,但对于其余的我们 运行 before { authenticate! }

我能想到的一种方法是使用 route_setting 为您想要绕过身份验证的路由添加自定义属性。在调用 authenticate! 之前检查前过滤器中的这些属性。像下面这样的东西应该可以工作:

module Backend
  module V1
    class Users < Grape::API
      include Backend::V1::Defaults

      before { authenticate! unless route.settings[:auth] && route.settings[:auth][:disabled] }

      resource :users do

        desc "Return a user"
        params do
          requires :id, type: Integer, desc: 'User id'
        end
        get ':id' do
          UsersService::Fetch.new(current_user,params).call
        end

        desc "Update a user"
        params do
          requires :id, type: Integer, desc: 'User id'
          requires :display_name, type: String, desc: 'Display name'
          requires :email, type: String, desc: 'Email'
        end
        post ':id' do
          UsersService::Save.new(current_user,params).call
        end

        desc "Reset user password"
        params do
          requires :old_password, type: String, desc: 'old password'
          requires :password, type: String, desc: 'new password'
        end
        post 'password/reset' do
          PasswordService::Reset.new(current_user,params).call
        end

        desc "Forget password"
        route_setting :auth, disabled: true
        params do
          requires :email, type: String
        end
        post 'password/forget' do
          PasswordService::Forget.new(current_user,params).call
        end            

      end
    end
  end
end

更清楚一点但可能不是很明显的方法是将您的名称空间拆分为单独的子类:

module API
  module Users
    class Root < Grape::API
      namespace :users do
        mount Create # <= `before` callback is not executed

        before do
          authenticate!
        end

        mount Update # <= `before` callback is executed
      end
    end
  end
end

相对目录结构如下所示:

 api/users/root.rb
 api/users/create.rb
 api/users/update.rb