跨控制器抽象 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
中。
我的 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
:
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
中。