3

I'm trying to override a Rails helper method that's defined like this:

class Foo
  module Bar

    def orig
      # orig code
    end

    alias o orig

    module_function :o
    module_function :orig

  end
end

So that I can override and add functionality to orig and o something like this:

def orig
  # new code
  # super (run orig code)
end
alias o orig

I've looked through several different monkey patching methods but they don't seem to work. I believe the module_function is what's throwing it off.

Anyone know how I can achieve this?

2 Answers2

4

Here's a workaround. You can re-open the module, make an unbound reference to the original instance method, then redefine it to call the original method (with some altered behavior).

First, the original definition:

module Foo
  def bar(arg)
    "self=#{self}, arg=#{arg}"
  end
  module_function :bar
end

Next, reopening and redefining the method:

module Foo
  OrigBarMethod = instance_method(:bar)
  def bar(arg)
    Foo::OrigBarMethod.bind(self).call(arg + 1)
  end
  module_function :bar
end

puts Foo.bar(1) # => "self=Foo, arg=2"

I use bind(self) so that the original method can still make use of self, for example:

class MyClass
  include Foo
end

MyClass.new.send(:bar, 1) # => "self=#<MyClass:0x00007fb66a86cbf8>, arg=2"
max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • I used to be a big proponent of this trick, because unlike `alias_method` it does not pollute the namespace, but it is not necessary anymore. Everything that can be done with `alias_method`, can nowadays also be done with simple inheritance (`prepend`). – Jörg W Mittag Nov 10 '18 at 12:09
  • @JörgWMittag Ok i must admit I still haven't gotten in the habit of using prepend. But, I see how it's way simpler here. Thanks – max pleaner Nov 10 '18 at 19:16
  • `prepend` is a generally powerful inheritance mechanism. It can subsume monkey-patching, Aspect-Oriented Programming (Before-, After-, and Around-Advice), CLOS-style Method Combinators (again, Before-, After-, and Around-Combinators). If you scroll down to the end of [my answer](https://stackoverflow.com/a/4471202/2988) that the OP linked to, I give a couple of examples of ideas for enhancing inheritance in Ruby that were floated before `prepend` was introduced, and how all of them can be replaced with `prepend`, often even with additional features and flexibility. – Jörg W Mittag Nov 11 '18 at 01:41
4

Pretty much any circumstance where you would in the past have used monkey-patching can nowadays be solved with inheritance and Module#prepend:

module Foo
  def bar(arg)
    "self=#{self}, arg=#{arg}"
  end
  module_function :bar
end

module FooExtension
  def bar(arg)
    super(arg + 1)
  end
end

[Foo, Foo.singleton_class].each do |mod|
  mod.prepend FooExtension
end

Foo.bar(1) #=> "self=Foo, arg=2"

class MyClass
  include Foo
end

MyClass.new.bar(1) #=> "self=#<MyClass:0x00007fb66a86cbf8>, arg=2"
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 1
    Nice one, Jörg, Rather than modifying `Foo` (which may be used elsewhere), it may be preferable to do all the including and prepending in the class definition: `[self, singleton_class].each { |k| k.include Foo; k.prepend FooExtension }`. Thomas: note that this does not depend on the statement `module_function :bar`. If you have no need for `bar` being a module method in `Foo` you can remove that statement. I concur with Jörg that this is the approach you should take. – Cary Swoveland Nov 10 '18 at 19:12