【问题标题】:Override rails helpers with access to original通过访问原始文件覆盖 rails helpers
【发布时间】:2012-05-15 08:07:16
【问题描述】:

我想使用 Rails 熟悉的助手,但功能略有改变。在我看来,我希望能够做这样的事情:

module AwesomeHelper
  #... create alias of stylesheet_link_tag to old_stylesheet_link_tag
  def stylesheet_link_tag(*args)
    if @be_awesome
      awesome_stylesheet_link_tag *args
    else
      old_stylesheet_link_tag *args
    end
  end
end

在我看来,我有三个选择:

  1. 猴子补丁:重新打开 rails helper 模块。如果 Rails 团队更改了他们的帮助模块的名称,我的代码就会变得脆弱。并非不可克服,但并不理想。
  2. 使用不同的方法名: 试图坚持通用的 Rails 接口可能是我的失败。我的更改可能会让其他开发者感到困惑
  3. 分离方法(新):不确定这是否可行,或者它是否具有与 1 相同的缺点。将对此进行研究,但这可能是一个很好的起点。

所以这里的问题是,我是坚持使用这些次优解决方案之一,还是有另一种我没有考虑过的方法?如果我选择选项 3,有没有办法在不直接解决 rails helper 模块的情况下做到这一点?

(注意:我已经删除了上下文,因为它对问题没有任何帮助。)

【问题讨论】:

    标签: ruby-on-rails ruby overriding helpers


    【解决方案1】:

    尝试使用alias_method

    module AwesomeHelper
      alias_method :original_stylesheet_link_tag, :stylesheet_link_tag
    
      def stylesheet_link_tag(*sources)
        if @be_awesome
          awesome_stylesheet_link_tag *sources
        else
          original_stylesheet_link_tag *sources
        end
      end
    end
    

    【讨论】:

      【解决方案2】:

      我真的鼓励你考虑你的选项 #2,以一种对调用者来说显而易见的方式覆盖 rails 方法的行为。

      您的新方法应该被称为awesome_stylesheet_link_tag,以便其他 Rails 开发人员可以阅读您的代码并提出问题“链接标签有什么了不起的地方?”。

      作为一个较小的更改,您可以进行覆盖,但将:awesome => true 作为参数传递,这样他们至少可以知道正在发生的事情。

      更改stylesheet_link_tag 等广泛使用的方法的行为会在未来产生潜在的误解,而无需任何操作。

      【讨论】:

      • 感谢您的意见。虽然通常我同意,但在这种特殊情况下,我认为一致性是合理的——我这样做是为了使用 wicked_pdf 并让完全相同的代码生成 PDF 或网页。虽然默认情况下 wicked_pdf 会按照您所说的那样 (wicked_pdf_stylesheet_link_tag),但它需要太多重复,而且我认为如果您生成 PDF,功能可能会发生变化是可以接受的。但是您提出了一个很好的观点,并提供了一些方便的提示,所以谢谢。
      【解决方案3】:

      有比您列出的任何选项更好的方法。只需使用super

      module AwesomeHelper
        def stylesheet_link_tag(*sources)
          if @be_awesome
            awesome_stylesheet_link_tag *sources
          else
            super
          end
        end
      end
      

      在 AwesomeHelper 中覆盖 stylesheet_link_tag 将确保在调用 stylesheet_link_tag 时,Ruby 会在它到达 ActionView::Helpers::AssetTagHelper 之前在方法查找路径中遇到它。如果@be_awesometrue,那么您就可以负责并在此停止操作,如果不是,则对super 的不带括号的调用将透明地传递到Rails 实现的所有参数。这样,您就不必担心 Rails 核心团队会在您身上转移任何东西!

      【讨论】:

      • 你知道吗...这太明显了,我正在绞尽脑汁想弄清楚为什么我认为它不起作用!今晚我要试一试,如果有效,我的大脑会很严厉,可能涉及一堵砖墙。当然,在接受你的回答之后......:D
      • @user208769 呵呵呵呵。棒极了。据我所知,以这种方式覆盖方法通常在任何情况下都是可取的。 Class#ancestors 非常有助于在方法查找路径中找出一个好的位置来劫持方法调度(或者需要包含具有覆盖的自定义模块以达到最佳效果)。
      • 什么? :) 你在开玩笑吧!这是一个巨大的陷阱!你必须在每个包含 AssetTagHelper 的类中包含你的助手。时间飞逝,您或其他人可能会忘记需要包含您的补丁。您只需包含 AssetTagHelper 并开始怀疑:为什么我的网站现在看起来不同了?当你和补丁制造者是同一个人时,这很好。但如果没有呢?
      • @jdoe 是的,你是对的。为了使用一个人的代码,它需要被包含在内。 :) 我不认为这是一个“陷阱”。在ApplicationController 中使用AbstractController::Helpers::ClassMethods::helper 可能有助于减轻您的顾虑。
      【解决方案4】:

      我不使用这个gem,所以我会以更通用的方式回答你。

      假设您想记录对link_to helper 的调用(是的,人为的示例,但显示了这个想法)。查看 API 可以让您了解 link_to 位于 ActionView::Helpers::UrlHelper 模块内。因此,您在 config/initializers 目录中创建了一些文件,其内容如下:

      # like in config/initializers/link_to_log.rb
      module ActionView::Helpers::UrlHelper
      
          def link_to_with_log(*args, &block)
              logger.info '**** LINK_TO CALL ***'
              link_to_without_log(*args, &block) # calling the original helper
          end
      
          alias_method_chain :link_to, :log
      end
      

      此功能的核心——alias_method_chain(可点击)。在定义方法 xxx_with_feature 之后使用它。

      【讨论】:

      • 是的,这种方法就是我所说的“猴子修补特定的 Rails 模块”的意思——效果很好,但是如果 Rails 核心更改了它们的模块名称,我的代码就会中断。这可能没什么大不了的,但我很好奇是否有其他解决方案。也就是说,忘记了 alias_method_chain,谢谢你提醒我!
      • PS:已更新问题以删除 gem 示例。希望这种布局不那么混乱!谢谢。
      • 风险永远存在!如果您担心alias_method_chain,那么您不应该:它自版本 1.4.0(2007 年)以来就存在。如果您担心程序的其他部分,那么请确保获得不错的测试覆盖率。
      猜你喜欢
      • 2019-04-13
      • 1970-01-01
      • 2017-10-03
      • 2014-04-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多