Ruby, modules, super and alias_method
In general, I really like the way Ruby handles class inheritance & module mix-ins. But, recently, I found an odd behavior - maybe a bug? - in Ruby 1.8.6 (the Ruby shipping with Leopard) when using alias_method
and super
inside an included module. (Other versions of Ruby may or may not have the behavior - I haven't tested them exhaustively.) Here's hoping this saves someone else some head-scratching down the line.
So, here's the scenario: You have one or more classes that inherit from a base class, and these include a module which conditionally overrides a method in the base class. All of this goes reasonably well, until you throw alias_method
into the mix - then things get weird.
Oh, I guess there might be spoilers here, if you haven't seen the first season of Heroes yet.
Let's say this is our base class:
class Human
def initialize(name)
@name = name
end
def special
puts "#{@name} is normal, no special abilities."
end
alias_method :abilities, :special
end
mohinder = Human.new('Mohinder')
mohinder.special # Mohinder is normal, no special abilities.
mohinder.abilities # Mohinder is normal, no special abilities.
Okay, cool. That works as we expect. Now, let's make the descendent classes:
class Hero < Human
attr_accessor :powers
def special
unless @powers
super
else
puts "#{@name} is a super - he/she can #{@powers}"
end
end
alias_method :abilities, :special
end
class BadGuy < Human
attr_accessor :powers
def special
unless @powers
super
else
puts "#{@name} is a super - he/she can #{@powers}"
end
end
alias_method :abilities, :special
end
But, we're not being very DRY here. Let's modularize!
module SuperPower
attr_accessor :powers
def special
unless @powers
super
else
puts "#{@name} is a super - he/she can #{@powers}"
end
end
alias_method :abilities, :special
end
class Hero < Human
include SuperPower
end
class BadGuy < Human
include SuperPower
end
Right on! Fire that up!
mohinder = Human.new('Mohinder')
mohinder.special # Mohinder is normal, no special abilities.
mohinder.abilities # Mohinder is normal, no special abilities.
peter = Hero.new('Peter Petrelli')
peter.powers = "mimic the powers of other supers nearby, fly, and read minds"
peter.special # Peter Petrelli is a super - he/she can mimic the ...
peter.abilities # Peter Petrelli is a super - he/she can mimic the ...
sylar = BadGuy.new('Sylar')
sylar.powers = "steal powers from other supers, telekinetics, and super-hearing"
sylar.special # Sylar is a super - he/she can steal powers ...
sylar.abilities # Sylar is a super - he/she can steal powers ...
hrg = Hero.new('Horn-Rimmed Glasses')
hrg.special # Horn-Rimmed Glasses is normal, no special abilities.
hrg.abilities # NoMethodError: super: no superclass method 'special'
D'oh! Hunh? I think there's two really odd things about this error:
super
is apparently trying to callspecial when invoked asabilities
- there actually is a
special
method defined,abilities
is "fake" method
I don't entirely comprehend why, but I suspect that the way Ruby internally masks the name of the invoked method confuses the heck out of both super
and the standard error reporting mechanism.
Anyway... If you're actually seeing this oddity, the solution I found is to just avoid alias_method
in modules. The old-fashioned way works just fine:
module SuperPower
attr_accessor :powers
def special
unless @powers
super
else
puts "#{@name} is a super - he/she can #{@powers}"
end
end
def abilities
special
end
end
mohinder = Human.new('Mohinder')
mohinder.special # Mohinder is normal, no special abilities.
mohinder.abilities # Mohinder is normal, no special abilities.
peter = Hero.new('Peter Petrelli')
peter.powers = "mimic the powers of other supers nearby, fly, and read minds"
peter.special # Peter Petrelli is a super - he/she can mimic the powers ...
peter.abilities # Peter Petrelli is a super - he/she can mimic the powers ...
sylar = BadGuy.new('Sylar')
sylar.powers = "steal powers from other supers, telekinetics, and super-hearing"
sylar.special # Sylar is a super - he/she can steal powers from ...
sylar.abilities # Sylar is a super - he/she can steal powers from ...
hrg = Hero.new('Horn-Rimmed Glasses')
hrg.special # Horn-Rimmed Glasses is normal, no special abilities.
hrg.abilities # Horn-Rimmed Glasses is normal, no special abilities.
Permalink • Posted in: heroes, programming, ruby, super