Элегантный способ абстрагироваться от before_actions между контроллерами?

У меня есть серия контроллеров в моем Rails API, которые все очень похожи - они имеют только базовые действия CRUD и отличаются только формой базовых данных, которые они хранят.

Как я реализую авторизацию, в каждом контроллере у меня есть несколько вызовов before_action, которые проверяют разрешения на соответствующем уровне для заданных действий CRUD - эти проверки разрешений буквально дублируются, за исключением того, что каждый из них принимает переменную экземпляра с другим именем - - например можно сказать

before_action -> { is_app_admin?(@app_name) } #where @app_name is the actual name of the app.

Теперь, если бы каким-то образом сам контроллер мог принимать параметр, я мог бы поместить их перед проверками в ApiController и не повторять их. Или я мог бы изменить имя переменной во всех контроллерах на что-то общее, например @app_name, но в самих контроллерах, что приведет к менее читаемому коду.

Есть ли стандартный способ абстрагироваться от повторяющегося кода в этом типе сценария?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
161
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Is there a standard way of abstracting the duplicate code in this type of scenario?

да. Это, ну, абстракция. Скройте это изменяющееся имя в методе со значимым именем. Если, например, у вас есть:

class Controller1
  before_action -> { is_app_admin?(@app_name) }
end

class Controller2
  before_action -> { is_app_admin?(@my_other_app_name) }
end

Тогда вот что вы могли бы сделать:

class Controller1
  before_action -> { is_app_admin?(app_name_for_authorization) }

  private 

  def app_name_for_authorization
    @app_name
  end
end

class Controller2
  before_action -> { is_app_admin?(app_name_for_authorization) }

  private 

  def app_name_for_authorization
    @my_other_app_name
  end
end

Действия before теперь идентичны, и вы можете перетащить их в родительский класс или извлечь в качестве проблемы.

В этом есть смысл - это определенно меньше дублированного кода, чем моя текущая форма, но все же имеет частный метод, который по сути копирует / вставляет между контроллерами. Думаю, я надеялся, что есть способ полностью этого избежать. Я понимаю, что на самом деле это невозможно, так как где-то в коде я должен сказать: эй - эта переменная - имя приложения.

Aaron Cohen 07.09.2018 00:21

@AaronCohen: «это, по сути, копирование / вставка между контроллерами» - что, если бы я сказал вам, что у вас может быть более двух уровней в иерархии классов вашего контроллера? :) Я имею в виду, что если у вас есть 30 контроллеров, которые все используют имя @my_app, вы можете создать промежуточный класс контроллера (MyAppController или что-то в этом роде), у которого будет этот частный метод, и унаследовать от него все 30 контроллеров вместо ApplicationController.

Sergio Tulentsev 07.09.2018 00:27

Да, я уже делаю это между действиями CRUD в приложении (подумайте на уровне плагина) и ресурсами в приложении, поскольку у них разные разрешения. Спасибо за помощь!

Aaron Cohen 07.09.2018 00:33
Ответ принят как подходящий

Имейте в виду, что before_action не является специальным синтаксисом, это просто метод класса, как и любой другой. Это означает, что вы можете написать метод класса, который вызывает before_action:

def self.ensure_app_admin_in(var)
  before_action ->{ is_app_admin?(instance_variable_get(var)) }
end

Добавьте это в модуль, контроллер, ApplicationController или другое удобное место, а затем в своих контроллерах скажите:

class Controller1
  ensure_app_admin_in :@app_name
  #...
end

class Controller2
  ensure_app_admin_in :@my_other_app_name
  #...
end

Ах, DSL. Путь рельсов. ?

Sergio Tulentsev 07.09.2018 00:50

Это здорово - я все еще привык к Ruby, это именно то, на что я надеялся.

Aaron Cohen 07.09.2018 01:20

@SergioTulentsev В последнее время я много писал о проблемах и "макросах".

mu is too short 07.09.2018 01:27

Одно продолжение - есть ли еще способ включить только сюда или кроме этого? например ensure_app_admin_in :@app_name, only: i%[update destroy]

Aaron Cohen 07.09.2018 01:57

Вы можете создать модуль и поместить в него аналогичные контроллеры с base_controller, похожим на application_controller, который будет иметь ваши before_actions, и только контроллеры, унаследованные от него, будут использовать их.

Например, если у вас есть несколько контроллеров администратора:

class Admin::BaseController < ApplicationController
  before_action :authorize_admin!

  def authorize_admin!
    redirect_to root_path unless user.admin?
  end
end

class Admin::UsersController < Admin::BaseController
  def index
  end
end

Затем в своих маршрутах вы можете либо пространство имен, либо добавить модуль к маршруту следующим образом:

resources :users, module: 'admin'

Затем поместите контроллеры администратора в app/controllers/admin, а представления - в app/views/admin/users.

Другие вопросы по теме