【问题标题】:Elegant way to abstract before_actions across controllers?跨控制器抽象 before_actions 的优雅方式?
【发布时间】:2018-09-06 21:47:21
【问题描述】:

我的 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 这样的通用名称,但在控制器本身中会导致代码的可读性降低。

有没有一种标准方法可以在这种场景中抽象出重复的代码?

【问题讨论】:

    标签: ruby-on-rails ruby controller code-duplication


    【解决方案1】:

    请记住,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]
    【解决方案2】:

    有没有一种标准方法可以在这种场景中抽象出重复的代码?

    是的。它是,嗯,抽象。在具有有意义名称的方法中隐藏该变化的名称。例如,如果您有这些:

    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
    

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

    【讨论】:

    • 这是有道理的——与我当前的表单相比,它的重复代码肯定更少,但仍然具有本质上是跨控制器复制/粘贴的私有方法。我想我希望有办法完全避免这种情况。我知道那是不可能的,因为在代码中的某个地方我必须说嘿——这个变量是应用程序名。
    • @AaronCohen:“这本质上是跨控制器的复制/粘贴”——如果我告诉你你的控制器类层次结构中可以有两个以上的级别怎么办? :) 我的意思是,如果您有 30 个控制器都使用名称 @my_app,您可以创建一个具有该私有方法的中间控制器类(MyAppController 或其他东西),并从中继承所有 30 个控制器,而不是 ApplicationController。
    • 是的——我已经这样做了,在应用程序的 CRUD 操作(想想插件级别)和应用程序内的资源之间,因为它们具有不同的权限。感谢您的帮助!
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-31
      • 2012-11-06
      • 2018-12-22
      相关资源
      最近更新 更多