跨控制器抽象 before_actions 的优雅方式?

Elegant way to abstract before_actions across controllers?

我的 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

之前的操作现在相同,您可以将它们拉到父级 class 或提取为关注点。

请记住,before_action 不是特殊语法,它只是一个 class 方法,与其他方法一样。这意味着您可以编写一个调用 before_action:

的 class 方法
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

您可以创建一个模块并将类似的控制器放入其中,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 中。