Why base class method is being called inside inherited? - ruby

In self.inherited method of base class, subclass is passed, calling subclass's name method instead calls base class method. Though same thing works If same method is called on same class elsewhere
class A
def self.name
"a"
end
def self.inherited(subclass)
puts B.hash
puts B.name
end
end
class B < A
def self.name
"b"
end
end
puts B.hash
puts B.name
output:
1428955046062147697
a
1428955046062147697
b

No magic here.
When you declare B the things happen in the following order (roughly speaking):
B (an instance of Class) is created (which inherits everything from A). At this moment it doesn't have anything specific.
A.inherited hook is invoked.
B class is opened and processed. Only at this point, it gets its own properties and methods (except the ones that could be created inside the hook).
So, when (2) happens the only name that is available for B is the one defined in A.
This is very easy to check using the following code:
class A
def self.name
"a"
end
def self.inherited(subclass)
puts "B own methods, point 1: #{subclass.methods(false).join(', ')}"
end
end
class B < A
puts "B own methods, point 2: #{self.methods(false).join(', ')}"
def self.name
"b"
end
puts "B own methods, point 3: #{self.methods(false).join(', ')}"
end
# B own methods, point 1:
# B own methods, point 2:
# B own methods, point 3: name
Everything is clear now, right?

Related

How to determine the class a method was defined in?

I would like to dynamically determine the class the current method was defined in.
Here's a static example of what I'm trying to do:
class A
def foo
puts "I was defined in A"
end
end
class B < A
def foo
puts "I was defined in B"
super
end
end
A.new.foo
# I was defined in A
B.new.foo
# I was defined in B
# I was defined in A <- this is the tricky one
How can I replace A and B in the strings above with a dynamic expression?
Apparently, #{self.class} does not work. (it would print I was defined in B twice for B)
I suspect that the answer is "you can't", but maybe I'm overlooking something.
What about this?
class A
def foo
puts "I was defined in #{Module.nesting.first}"
end
end
class B < A
def foo
puts "I was defined in #{Module.nesting.first}"
super
end
end
Corrected following WandMaker's suggestion.
You could use Module.nesting.first.
However, note that this works purely lexically, the same way constants resolution works, so it won't cut it if you have more dynamic needs:
Foo = Class.new do
def foo
Module.nesting
end
end
Foo.new.foo # => []
I have this nagging feeling that if you could do this, it would violate object-orientated encapsulation, although I can't quite place my finger on exactly why. So, it shouldn't come as a surprise that it's hard.
I can see a way if you are open to modifying the method definitions:
class A
this = self
define_method(:foo) do
puts "I was defined in #{this}"
end
end
class B < A
this = self
define_method(:foo) do
puts "I was defined in #{this}"
super()
end
end
A.new.foo
# I was defined in A
B.new.foo
# I was defined in B
# I was defined in A

How can "super" within a refinement call an overridden method?

I was under the impression that refinements fell outside the usual inheritance scheme in Ruby; that overriding a method within a refinement replaced the original method for all code using the refinement.
But then, I tried this experiment with super, and it appears that the overridden method gets called:
class MyClass
def my_instance_method
puts "MyClass#my_instance_method"
end
end
module MyRefinement
refine(MyClass) do
def my_instance_method
puts "MyClass#my_instance_method in MyRefinement"
super
end
end
end
using MyRefinement
MyClass.new.my_instance_method
The above code outputs:
MyClass#my_instance_method in MyRefinement
MyClass#my_instance_method
My question is, how? Is the refinement inserted into the class hierarchy in some way?
Based on the documentation, the method lookup for a refinement's built in behaviour is the same as you have observed.
Your assumption was correct that it is not typical inheritance, that can be seen by invoking superclass
class C
def foo
puts "C#foo"
end
end
module M
refine C do
def foo
puts "C#foo in M"
puts "class: #{self.class}"
puts "superclass: #{self.class.superclass}"
super
end
end
end
using M
x = C.new
x.foo
The output:
C#foo in M
class: C
superclass: Object
C#foo

Does calling super() cause further methods in the parent class to be used?

I have a question about super that I wanted confirmed. Consider the following code example:
class InFasionHello
def hello person
greet person.name
end
def greet name
p 'Dude, hey ' + name
end
end
class OldFasionedHello < InFasionHello
def hello person
greet person.name if person.old_fashioned
super(person) if !person.old_fashioned
end
def greet name
p 'Good Day to you ' + name + '!'
end
end
My question is, if I was using OldFasionedHello, would infasionHello use the local greet to it self or the one from the class that called it via super?
The proof of the pudding is in the eating.
class Parent
def foo; p self; bar; end # This calls bar on the current object
def bar; puts "parent bar"; end
end
class Child < Parent
def foo; super; end # Removing this line changes nothing
def bar; puts "child bar"; end
end
Child.new.foo
#=> #<Child:0x007f980b051f40>
#=> child bar # NOTE! Not "parent bar"
Calling super doesn't change the self, as seen above. As such, methods you call on self (explicitly or implicitly, by not providing a receiver) still act upon the original instance, and use it for method lookup.
Calling super() is equivalent to calling:
self.class.superclass.instance_method(__method__).bind(self).call
…which helps to illustrate that you are calling the implementation of the method as though it is on the current instance. Note also that super is not the same as super(), since the former will magically pass along whatever parameters were supplied to the current method.
All the method calls inside given method are executed against self. self within an instance method is an instance itself and and it is an instance who is a receiver of this method. Hence it is initiating standard method lookup for given object so it will always execute the very top method with given name.
An extreme good example is class method:
class A
def foo
self.class
end
end
class B < A
end
B.new.foo #=> B even though foo comes from A

Letting the Parent call the original Method instead of the overwritten one

Given two Classes
class A
def method_a ()
method_b()
end
def method_b ()
puts "Comes from A"
end
end
and B inheriting from A
class B < A
def method_a ()
super()
end
def method_b ()
puts "Comes from B"
end
end
When i call B.method_a the Outcome would be: Comes from B. Is there a possibility to tell A to call its method_b instead of my overwritten one? (So that the result would be Comes from A)
There isn't an equivalent of C++'s A::method_a
You could however do
class A
def method_a
A.instance_method(:method_b).bind(self).call
end
def method_b
puts "Comes from A"
end
end
class B < A
def method_a
super
end
def method_b
puts "Comes from B"
end
end
What is happening here in method_a is that we're retrieving A's implementation of method_b and calling it directly.
This does somewhat fly in the face of using inheritance in the first place though
I don't think you can do it straight forward, without putting a hack, but wouldn't it defeat the purpose of inheritance and template pattern, as there can be a C class as well inheriting from A, and you may want to call method_b function of class C only, as it has been over-written and required.
What you ask is not supported, since it defeats the point of inheritance. What you can do, is split the implementation into two methods - one that does As implementation of method_b, which is not overridden, and one that delegates to that implementation, which may be overridden:
class A
def method_a
dont_override_me
end
def method_b
dont_override_me
end
private
def dont_override_me
puts "Comes from A"
end
end
class B < A
def method_a
super
end
def method_b
puts "Comes from B"
end
end
class A
def method_a; comes_from_A end
def method_b; puts "Comes from A" end
alias comes_from_A method_b
end
class B < A
def method_a; super end
def method_b; puts "Comes from B" end
end
You could modify the child class as follows:
class B < A
def method_a
self.class.send(:remove_method, :method_b)
super()
self.class.send(:alias_method, :method_b, :method_b_alias)
end
def method_b
puts "Comes from B"
end
alias_method :method_b_alias, :method_b
end
b = B.new
b.method_b
# Comes from B
b.method_a
# Comes from A
b.method_b
# Comes from B
The alias :method_b_alias is created when the child class is created. Whenever method_a is called on an instance of of B, B's method method_b is removed before super is invoked, so A's method_a will not find it and use its own method_a instead (since the receiver of A's method_a (self) is the instance of B.) After super returns, alias_method is used to once again make B's method_a available to instances of B.
We need to use Module#remove_method rather than Module#undef_method. If the latter were used, A's method_a would give up looking for method_b after finding that B did not have that method.
If you prefer, you could change two lines to use the keyword alias rather than alias_method:
self.class.send(:alias :method_b :method_b_alias)
alias :method_b_alias :method_b

How to pass a method to instance_eval?

I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end

Resources