通过访问原始文件覆盖 rails helpers

Posted

技术标签:

【中文标题】通过访问原始文件覆盖 rails helpers【英文标题】:Override rails helpers with access to original 【发布时间】: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

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

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

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

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

【问题讨论】:

【参考方案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 年)以来就存在。如果您担心程序的其他部分,那么请确保获得不错的测试覆盖率。 已弃用。 ***.com/questions/3689736/…

以上是关于通过访问原始文件覆盖 rails helpers的主要内容,如果未能解决你的问题,请参考以下文章

覆盖rails 5的控制器测试脚手架

使用 Rails 3.2.11 和 RSpec 发布原始 JSON 数据

每个人都可以通过 URL 访问 rails 公用文件夹吗?

Rails ActiveRecord::Base before_save 覆盖 write_ 访问器以保存电话号码

Rails 对 csv 格式的原始查询,通过控制器返回

Rails 6 - Shrine - ImageProcessing - 获取原始上传文件