Let's say I have a base class with three sub-classes. The base class has a method common to most sub-classes, and it has an alias:
class Beer
def bottle_content
'250 ml'
end
alias_method :to_s, :bottle_content
end
class Heineken < Beer
end
class Stella < Beer
end
class Duvel < Beer
def bottle_content
'330 ml'
end
end
Now, if the to_s
method is called on the diverging sub-class instance of Duvel
, 250 ml
is returned instead of 330 ml
.
I get why; the alias is made at the level of the super class. And I know this can be fixed by re-defining the alias_method
in the diverging class. But is there another way to do this?
Obviously, using a method for to_s
would work:
class Beer
def bottle_content
'250 ml'
end
def to_s; bottle_content; end
end
But maybe there is more elegant approach?
If all you want is the delegation behavior without having to write a new method by hand, I might suggest Forwardable
.
require 'forwardable'
class Beer
extend Forwardable
def bottle_content
'250 ml'
end
def_delegator :self, :bottle_content, :to_s
end
It's really intended to be used for delegating methods to other objects, but there's nothing saying we can't do this, simply by passing it a :self
as the first argument.
irb(main):001:0> puts Duvel.new
330 ml
=> nil
This is because alias_method
treats self
at runtime.
Edit
As @Jörg commented, my solution can cause some troubles depending of your application (multithreading environment…). The to_s
implementation like you suggested is probably the best, and not especially less elegant IMO.
One way would be to declare the alias at initialize (on the class).
class Beer
def initialize
self.class.alias_method :to_s, :bottle_content
end
def bottle_content
'250 ml'
end
end
This way the alias is created on the class of the instance, ie. Duvel
in your example.
For reasons I will explain, you cannot, from Beer
, create an alias to a method in a subclass, for that subclass, at the time the subclass is created.
Using the callback Class#inherited, however, we can do something close to what you want to achieve:
class Beer
def bottle_content
'250 ml'
end
alias_method :to_s, :bottle_content
def self.inherited(klass)
klass.define_method(:to_s) { bottle_content }
end
end
class Duvel < Beer
def bottle_content
'330 ml'
end
end
Beer.new.to_s
#=> "250 ml"
Duvel.new.to_s
#=> "330 ml"
The problem is that Beer::inherited
is called right after class Duvel < Beer
is executed, before the method Duvel#bottle_content
is defined. If we were to change the operative line of Beer::inherited
to
klass.alias_method(:to_s, :bottle_content)
that would bind Duvel#to_s
to Beer#bottle_content
, as Duvel#bottle_content
has not yet been defined.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With