#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