У меня есть серия контроллеров в моем 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, но в самих контроллерах, что приведет к менее читаемому коду.
Есть ли стандартный способ абстрагироваться от повторяющегося кода в этом типе сценария?





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 теперь идентичны, и вы можете перетащить их в родительский класс или извлечь в качестве проблемы.
@AaronCohen: «это, по сути, копирование / вставка между контроллерами» - что, если бы я сказал вам, что у вас может быть более двух уровней в иерархии классов вашего контроллера? :) Я имею в виду, что если у вас есть 30 контроллеров, которые все используют имя @my_app, вы можете создать промежуточный класс контроллера (MyAppController или что-то в этом роде), у которого будет этот частный метод, и унаследовать от него все 30 контроллеров вместо ApplicationController.
Да, я уже делаю это между действиями CRUD в приложении (подумайте на уровне плагина) и ресурсами в приложении, поскольку у них разные разрешения. Спасибо за помощь!
Имейте в виду, что 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. Путь рельсов. ?
Это здорово - я все еще привык к Ruby, это именно то, на что я надеялся.
@SergioTulentsev В последнее время я много писал о проблемах и "макросах".
Одно продолжение - есть ли еще способ включить только сюда или кроме этого? например ensure_app_admin_in :@app_name, only: i%[update destroy]
Вы можете создать модуль и поместить в него аналогичные контроллеры с 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.
В этом есть смысл - это определенно меньше дублированного кода, чем моя текущая форма, но все же имеет частный метод, который по сути копирует / вставляет между контроллерами. Думаю, я надеялся, что есть способ полностью этого избежать. Я понимаю, что на самом деле это невозможно, так как где-то в коде я должен сказать: эй - эта переменная - имя приложения.