How do I override a class method through an instance_eval? - ruby

I have a class Foo with a method bar. How do I override foo's bar with
Foo.instance_eval do
def bar
#do something a little different
end
end
whats to be overridden:
module Lorem
class Foo
def bar
# do something
end
end
end
instance_eval needs to be used, class_eval works with overriding but doesnt keep the context

Assuming you want to change module Lorem so that whenever it is included, your new bar() will be used, you were very close:
module Lorem
class Foo
def bar
puts "old bar"
end
end
end
Lorem::Foo.class_eval do
def bar
puts "new bar"
end
end
class A
include Lorem
Foo.new.bar #=> "new bar"
end

I think the issue with what you're trying to achieve is that you're trying to override the method for objects that have been already created.
In order to do so, you need to iterate over those objects and override their method using ObjectSpace as in https://stackoverflow.com/a/14318741/226255
The following does not help:
class Foo
def bar
puts "hello"
end
end
x = Foo.new
Foo.instance_eval do
def bar
puts "bar"
end
end
x.bar
You get the following output:
hello
=> nil
In order to override the method for objects created BEFORE you do the instance_eval, do the following:
x = Foo.new
ObjectSpace.each_object(Foo) { |obj|
def obj.bar
puts "bar"
end
}
You get the output:
x.bar
bar
=> nil

Related

Creating a method that functions as both a class and instance method

Let's say I had a class and I wanted to be able to call the same method on the class itself and an instance of that class:
class Foo
def self.bar
puts 'hey this worked'
end
end
This lets me do the following:
Foo.bar #=> hey this worked
But I also want to be able to do:
Foo.new.bar #=> NoMethodError: undefined method `bar' for #<Foo:0x007fca00945120>
So now I modify my class to have an instance method for bar:
class Foo
def bar
puts 'hey this worked'
end
end
And now I can call bar on both the class and an instance of the class:
Foo.bar #=> hey this worked
Foo.new.bar #=> hey this worked
Now my class Foo is 'wet':
class Foo
def self.bar
puts 'hey this worked'
end
def bar
puts 'hey this worked'
end
end
Is there a way to avoid this redundancy?
Have one method call the other. Otherwise, no, there is no way to avoid this "redundancy", because there is no redundancy. There are two separate methods that happen to have the same name.
class Foo
def self.bar
puts 'hey this worked'
end
def bar
Foo.bar
end
end

inheritance changes class of method

The following prints Bar twice:
class Foo
def foo
p self.class # => prints Bar
end
end
class Bar < Foo
def foo
p self.class # => prints Bar
super
end
end
b = Bar.new
b.foo
How do I get it to print
Bar
Foo
? i.e. I want to know what class each method is defined on.
To capture the context in which a method was originally defined, you can use define_method instead of def to get the appropriate closure. A simple example:
class Foo
klass = self
define_method(:foo){p klass}
end
class Bar < Foo
def foo
p self.class
super
end
end
b = Bar.new
b.foo
You could change Foo#foo like so (provided there is just one subclass level):
class Foo
def foo
if self.class == Foo
p self.class
else
p self.class.superclass
end
end
end
class Bar < Foo
def foo
p self.class
super
end
end
Foo.new.foo
Foo
Bar.new.foo
Bar
Foo
You can use
b.class.superclass <= "Foo"
The problem you are having there is that self is the instance of Bar, b.
b.class <= always going to be Bar
self.class <= always going to be Bar if you are invoking Bar.
You say that you are defining a method at runtime, and that you don't know the class name. I don't really know what you mean ... the way I would handle this would be something like
class Bar
def initialize
puts 'In BAR class'
end
def foo
p self.class.name # => prints Bar
end
end
and then
Bar.class_eval do
def brand_new_method
# do something new
p "Still in Bar, but this is dynamically added"
end
end
Maybe you are talking about dynamically adding methods to classes higher in the inheritance chain ... to "Foo" in your example ... based on some conditional happening in an instance of "Bar". If that is the case, then why don't you use a single module to define your inherited methods:
module Foo
def foo
p self.class
end
end
and then use module_eval the same way as class_eval?

Get included method names

How I can get all instance method names in the baz method call, which are only present in the Bar module (without other instance methods of this class) ?
class Foo
include Bar
def a
end
def b
end
def baz
#HERE
end
end
class Foo
include Bar
def a
end
def b()
end
def baz
Bar.instance_methods(false)
end
end
puts Foo.new.baz

How can I override a module's singleton method?

Given a module with a singleton method like this:
module Foo
class << self
def bar
puts "method bar from Foo"
end
end
end
How can I override Foo.bar using another module?
This code
module Foo
class << self
def bar
puts "method bar from Foo"
end
end
end
is equal to
class << Foo
def bar
puts "method bar from Foo"
end
end
that is also equal to
def Foo.bar
puts "method bar from Foo"
end
So, you can call it to redefine this method everywhere where Foo is defined (ever withing another module, without including Foo).
Extend and alias
My problem was that I forgot to think through the inheritance chain. I was
looking for a way to override the method by modifying the inheritance chain, but
that's not possible.
The reason is that bar is defined on Foo itself, so it never looks up its inheritance chain for the method. Therefore, to change bar, I have to change it on Foo itself.
While I could just re-open Foo, like this:
module Foo
def self.bar
puts "new foo method"
end
end
... I prefer a way to be able to wrap the original bar method, as though I
were subclassing and could call super. I can achieve that by setting up an
alias for the old method.
module Foo
class << self
def bar
"method bar from Foo"
end
end
end
puts Foo.bar # => "method bar from Foo"
module FooEnhancement
# Add a hook - whenever a class or module calls `extend FooEnhancement`,
# run this code
def self.extended(base)
# In the context of the extending module or class
# (in this case, it will be Foo), do the following
base.class_eval do
# Define this new method
def self.new_bar
"#{old_bar} - now with more fiber!"
end
# Set up aliases.
# We're already in the context of the class, but there's no
# `self.alias`, so we need to call `alias` inside this block
class << self
# We can call the original `bar` method with `old_bar`
alias :old_bar :bar
# If we call `bar`, now we'll get our `new_bar` method
alias :bar :new_bar
end
end
end
end
# This will fire off the self.extended hook in FooEnhancement
Foo.extend FooEnhancement
# Calls the enhanced version of `bar`
puts Foo.bar # => 'method bar from Foo - now with more fiber!'
You can do the following:
module Foo
class << self
def bar
puts "method bar from Foo"
end
def baz
puts "method baz from Foo"
end
end
end
module Foo2
def Foo.bar
puts "new version of bar"
end
end
include Foo
Foo.baz #=> method baz from Foo
Foo.bar #=> new version of bar
Or instead of naming it Foo2 simply re-open Foo.
module Foo
class << self
def bar
puts "method bar from Foo"
end
def baz
puts "method baz from Foo"
end
end
end
module Foo
class << self
def bar
puts "new version of bar"
end
end
end
include Foo
Foo.baz #=> method baz from Foo
Foo.bar #=> new version of bar

Calling Super Methods in Ruby

I am trying to define some classes in Ruby that have an inheritance hierarchy, but I want to use one of the methods in the base class in the derived class. The twist is that I don't want to call the exact method I'm in, I want to call a different one. The following doesn't work, but it's what I want to do (basically).
class A
def foo
puts 'A::foo'
end
end
class B < A
def foo
puts 'B::foo'
end
def bar
super.foo
end
end
Probably, this is what you want?
class A
def foo
puts 'A::foo'
end
end
class B < A
alias bar :foo
def foo
puts 'B::foo'
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo
A more general solution.
class A
def foo
puts "A::foo"
end
end
class B < A
def foo
puts "B::foo"
end
def bar
# slightly oddly ancestors includes the class itself
puts self.class.ancestors[1].instance_method(:foo).bind(self).call
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo

Resources