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