【问题标题】:Rails: dynamically include helpers per-request at runtimeRails:在运行时为每个请求动态包含帮助程序
【发布时间】:2014-11-07 10:20:29
【问题描述】:

我的应用拥有普通用户和管理员。它有一些显示小部件的渲染助手:

module ApplicationHelper
  def widget
    "widget"
  end
end

对于管理员,这些小部件有扩展版本:

module AdminHelper
  def widget
    "admin-widget"
  end
end

通过config.action_controller.include_all_helpers = false,该应用默认配置为不包含所有助手。

当管理员查看页面时 - 相同的控制器,相同的视图模板 - 我想通过使用其 AdminHelper 版本重载助手来呈现它的扩展版本。如果当前登录的用户是管理员,我如何在每个请求的基础上使用 AdminHelper 重载 ApplicationHelper?

我试过了

class ApplicationController < ActionController::Base  
  before_action :include_backend_helpers

    protected

  def include_backend_helpers
    self.class.helper "pica/backend" if admin_signed_in?
  end
end

但这不起作用,除非在请求之间重新加载 ApplicationController。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 actioncontroller


    【解决方案1】:

    尝试动态加载代码以覆盖小部件是错误的做法。相反,widget 方法应该根据请求决定显示哪个版本。在我们研究一些方法之前,让我们解释一下为什么模块覆盖不起作用。


    ApplicationController 在 Rails 应用程序启动时加载。这会自动包含ApplicationHelper 模块,该模块定义了widget 方法。由于 config.action_controller.include_all_helpers 设置,AdminHelper 未加载 - 通过将其设置为 false,Rails 将仅加载与控制器同名的助手。

    当普通用户调用widget方法时,返回字符串"widget"。但是只要管理员访问该站点include_backend_helpers 回调就会加载您的 AdminHelper 模块,覆盖 widget 方法。管理员得到了返回的字符串"admin-widget" - 但是每个未来访问该站点的访问者也是如此! widget 方法不根据用户类型进行区分。

    您可以通过在每次请求后重新加载 ApplicationController 来解决此问题,但这需要时间并且会降低性能。你might be able to unload the module,但这似乎是一种充满困难的方法。您还可以尝试在每个请求上重新加载帮助程序:

    def include_backend_helpers
      if admin_signed_in?
        self.class.helper "admin/backend"
      else
        self.class.helper "user/backend"
      end
    end
    

    但我不确定这是否可行,也不建议这样做。


    相反,决定适合显示的内容似乎是小部件的工作。例如,如果这是一个导航组件,它应该足够聪明,只显示管理链接。您仍然可以将其分解为单独的方法:

    module ApplicationHelper
      def widget
        if admin_signed_in?
          user_widget
        else
          admin_widget
        end
      end
    
      private
      def user_widget
        "widget"
      end
    
      def admin_widget
        "admin-widget"
      end
    end
    

    您可以对 Rails 部分使用类似的方法 - 您可以有一个 _navigation.html.erb 部分,包括基于 admin_signed_in?_user_navigation.html.erb_admin_navigation.html.erb。你的视图包含`",不用担心内部细节。

    如果你有足够多的代码让上面开始觉得笨拙,那么你应该看看Cells for Rails。它是一个可以更轻松地构建封装显示组件的 gem - 控制器逻辑和视图代码的组合。

    【讨论】:

    • 感谢您的详细解答!我不希望在每个单独的小部件中检查用户角色——当你有几十个小部件时,这是相当多余和麻烦的。对于我的观点和部分,我只是 prepend_view_path '&lt;path_to_admin_overrides&gt;' if admin_signed_in? - 这样我可以在必要时单独覆盖它们;我所缺少的只是一种与助手打交道的同样优雅的方法。
    • 如果你有几十个组件,我会尝试找到我可以定义为基类的东西。然后,在基类中,我会在其中执行类似m = "admin_#{method}"; return self.call(m) if admin_signed_in? &amp;&amp; self.respond_to?(m) 的操作。这似乎是您可以很好地使用cells 做的事情。
    • 我会考虑一下这个想法,谢谢。对于这个项目,我已经看过 Cells 并出于概念原因将其关闭,但我会再试一次。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-06
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    相关资源
    最近更新 更多