简体   繁体   中英

Dynamically define a super method for an instance of a class in Ruby

Say we have a class that we cannot change,

class C
  def foo
    super
    puts "Low!"
  end
end

We'll need to dynamically define the method foo in something that we'll be able to inject into C's ancestry chain. The behavior of super must be specific to a given object, not class-wide. We'll be able to enclose that logic into an anonymous module (let's name it for now):

module M
  def foo
    puts "High!"
  end
end

Extending an instance of C with the module:

c = C.new
c.extend(M)
c.foo
# High!

will not work since we've put the method from the module before the method we've defined in the class. Looking at our object's ancestors

c.singleton_class.ancestors
# => [#<Class:#<C:0x00005652be630b20>>, M, C, ...]

I came up with an ugly workaround, which is redefining the methods from our class in our singleton_class, ie

c.define_singleton_method(:foo, c.class.instance_method(:foo))
c.foo
# High!
# Low!

While this works (does it work? I've tested it for a bit and it seems to, but I'm no longer certain), I wonder whether I'm missing something obvious and there's an easier way to dynamically define a "super" method for an instance of a class.

To be clear, we want to be able to extend another instance of C with another module, ie

C.new.extend(Module.new do
  def foo
    puts "Medium!"
  end
end).foo
# Medium!
# Low!

and have its output not tainted by other instances.

Now that I understand you're trying to work around an issue in some third-party code, I can suggest a more reasonable solution. In the code below, I'm thinking that B and C are defined by the gem and you don't want to change their source code, but you want to inject some code into the place where C#foo calls B#foo .

class B
  def foo
    puts "Highest!"
  end
end

class C < B
  def foo
    super
    puts "Low!"
  end
end

module CE
  def foo
    super
    foo_injected
  end
end

C.include(CE)

module M
  def foo_injected
    puts "High!"
  end
end

c = C.new
c.extend(M)
p c.singleton_class.ancestors
c.foo

The output is:

[#<Class:#<C:0x000055ce443366a8>>, M, C, CE, B, Object, Kernel, BasicObject]
Highest!
High!
Low!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM