Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove or bypass a superclass method in Ruby?

Some background: I have an external library that uses explicit type checking instead of duck-typing in one of its methods. Something like:

def a_method(value)
  case value
  when Array then 'an Array'
  when Hash  then 'a Hash'
  when Foo   then 'a Foo'
  end
end

Foo is defined in the library. I would like to pass another object to this method that should be treated like a Foo. Therefore, I'm subclassing Foo:

class Bar < Foo
end

which works just fine:

bar = Bar.new
a_method(bar)
#=> 'a Foo'

Unfortunately, Foo implements several methods that will break the way Bar is supposed to work, including method_missing and respond_to?. For example:

class Foo
  def respond_to?(method_name)
    false
  end
end

Because Foo is Bar's superclass, Foo#respond_to? is invoked when calling Bar#respond_to?:

class Bar < Foo
  def hello
  end
end

bar = Bar.new
bar.respond_to?(:hello)  #=> false
bar.method(:respond_to?) #=> #<Method: Bar(Foo)#respond_to?>

I would like to remove or bypass Foo's method in this case (i.e. from within Bar) so that:

bar.respond_to?(:hello)  #=> true
bar.method(:respond_to?) #=> #<Method: Bar(Kernel)#respond_to?>

just as if Foo#respond_to? did not exist.

Any suggestions?

like image 539
Stefan Avatar asked Nov 21 '25 08:11

Stefan


1 Answers

Ruby's method lookup seems to be "hard-coded". I couldn't find a way to alter it from within Ruby.

Based on Sergio Tulentsev's suggestion to re-implement the methods, however, I came up with a helper method to replace / overwrite every superclass (instance) method with its "super" method (or undefine it if there is none):

def self.revert_superclass_methods
  superclass.instance_methods(false).each do |method|
    super_method = instance_method(method).super_method
    if super_method
      puts "reverting #{self}##{method} to #{super_method}"
      define_method(method, super_method)
    else
      puts "undefining #{self}##{method}"
      undef_method(method)
    end
  end
end

This has basically the same effect for my purposes. Example usage:

module M
  def foo ; 'M#foo' ; end
end

class A
  include M
  def to_s ; 'A#to_s' ; end
  def foo  ; 'A#foo'  ; end
  def bar  ; 'A#bar'  ; end
end

class B < A
  def self.revert_superclass_methods
    # ...
  end
end

b = B.new
b.to_s #=> "A#to_s"
b.foo  #=> "A#foo"
b.bar  #=> "A#bar"

B.revert_superclass_methods
# reverting B#to_s to #<UnboundMethod: Object(Kernel)#to_s>
# reverting B#foo to #<UnboundMethod: Object(M)#foo>
# undefining B#bar

b.to_s #=> "#<B:0x007fb389987490>"
b.foo  #=> "M#foo"
b.bar  #=> undefined method `bar' for #<B:0x007fb389987490> (NoMethodError)
like image 101
Stefan Avatar answered Nov 23 '25 22:11

Stefan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!