Search code examples
ruby-on-railsrubymonkeypatchingprepend

How to monkey-patch an ActionView module via prepend?


If you include a module Foo in a class SomeClass, then prepend that module with another module Bar, any method-overwriting inside Bar will not take effect in SomeClass. Example:

module Foo
  def some_method
    puts 'In Foo'
  end
end

class SomeClass
  include Foo
end

SomeClass.new.some_method # => 'In Foo'

module Bar
  def some_method
    puts 'In Bar'
    super
  end
end

Foo.prepend Bar

Foo.ancestors # => [Bar, Foo]

SomeClass.new.some_method # => 'In Foo'

class AnotherClass
  include Foo
end

AnotherClass.new.some_method # => 
# 'In Bar'
# 'In Foo'

I am trying to monkey-patch an ActionView helper method the following way:

In lib/core_extensions/action_view/helpers/url_helper/secure_link_to:

module CoreExtensions
  module ActionView
    module Helpers
      module UrlHelper
        module SecureLinkTo
          def link_to(name = nil, options = nil, html_options = nil, &block)
            html_options ||= {}
            if html_options[:target].present?
              html_options[:rel] = 'noopener noreferrer'
            end

            super(name, options, html_options, &block)
          end
        end
      end
    end
  end
end

and then in an initializer:

ActionView::Helpers::UrlHelper.prepend CoreExtensions::ActionView::Helpers::UrlHelper::SecureLinkTo

However, this doesn't seem to work. My assumption - by the time the initializer executes, ActionView::Helpers::UrlHelper has already been included (in wherever it is supposed to be included) and thus the prepending doesn't seem to take effect. Does anybody know a solution to this?


Solution

  • Without answering your specific question about module prepend, here would be another way:

    Since helpers in Rails are global, you can simply create your own helper with the link_to method overridden.

    module LinkHelper
      def link_to(name = nil, options = nil, html_options = nil, &block)
        html_options ||= {}
        if html_options[:target].present?
          html_options[:rel] = 'noopener noreferrer'
        end
    
        super(name, options, html_options, &block)
      end
    end
    

    Somehow this seems less hacky than creating an initializer, because I don't need to hardcode the inheritence chain in the helper module.