Как обрабатывать перед фильтром для определенного действия в Grape?

#ruby-on-rails #ruby #grape #grape-api

#ruby-on-rails #ruby #ruby-grape #grape-api

Вопрос:

Я монтирую Grape в свой проект Rails для создания 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 необходимости входа пользователя в систему / аутентификации. Также не имеет смысла создавать новую конечную точку, скажем passwords , и просто удалять password/forget ее, поскольку, логически говоря, эта конечная точка должна быть связана с пользовательским ресурсом.

Проблема в том, что у Grape before filter нет таких опций, except, only в которых я могу сказать применить фильтр для определенных действий.

Как вы обычно справляетесь с таким делом чисто?

Ответ №1:

Один из способов, о котором я мог подумать, — это использовать route_setting для добавления пользовательских атрибутов для маршрутов, для которых вы хотели бы обойти аутентификацию. Перед вызовом проверьте наличие этих атрибутов в фильтре before authenticate! . Должно работать что-то вроде приведенного ниже:

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

      before { authenticate! unless route.settings[:auth] amp;amp; 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
  

Комментарии:

1. Мне это нравится больше, чем принятый ответ. Подход с использованием пространства имен означает, что действие before должно находиться внутри вложенного пространства имен. Это позволило бы зарегистрировать его в базе api и исключить его только для нескольких конечных точек, потенциально в разных файлах.

2. before { authenticate! unless route.settings.dig(:auth, :disabled) } таким образом, это немного короче 🙂

Ответ №2:

Грязный способ помочь — использовать 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
  

Таким образом, мы не будем запускать before filter for users/password/forget , но для остальных мы запустим before { authenticate! }

Комментарии:

1. поддержано.. Интересно, есть ли теперь более элегантный способ сделать это

2. Работает только непосредственно в классе endpoint. Это не работает, если вы хотите сделать что-то вроде before callback для конкретного mount API . Например, я хочу запустить его mount Admin::Base , но я не хочу, чтобы он запускался Public::Base

Ответ №3:

Немного более понятным, но, возможно, не столь очевидным способом будет разделение вашего пространства имен на отдельные подклассы:

 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