Inheritable aliases in Ruby
Ruby’s method aliases are pretty handy. So is inheritance. It’s too bad that the two don’t work well together:
class Greeter
def greeting
fail NotImplementedError, "#greeting is not implemented"
end
alias_method :salutation, :greeting
end
class Employee < Greeter
def greeting
"Howdy"
end
end
employee = Employee.new
employee.greeting # => "Howdy"
employee.salutation # =>
# ~> -:3:in `greeting': #greeting is not implemented (NotImplementedError)
# ~> from -:16:in `<main>'
Wat? Turns out that alias_method
creates an alias that references the original method rather than the overwritten one. Fortunately, the Ruby standard library provides a workaround. By using the Forwardable
module and its def_delegator
method, we can declare a delegator that forwards any call on to the overwritten method.
require 'forwardable'
class Greeter
extend Forwardable
def greeting
fail NotImplementedError, "#greeting is not implemented"
end
def_delegator :self, :greeting, :salutation
end
class Employee < Greeter
def greeting
"Howdy"
end
end
employee = Employee.new
employee.greeting # => "Howdy"
employee.salutation # => "Howdy"
Cool, now we have the alias working with inheritance. The only problem with this approach is maintenance. Let’s make it clear why we’re using something other than alias_method
by defining our own inheritable version.
module Aliases
def inheritable_alias(new_method, original_method)
define_method new_method do |*args, &block|
public_send(original_method, *args, &block)
end
end
end
Now we can clean our code up.
class Greeter
extend Aliases
def greeting
fail NotImplementedError, "#greeting is not implemented"
end
inheritable_alias :salutation, :greeting
end
class Employee < Greeter
def greeting
"Howdy"
end
end
employee = Employee.new
employee.greeting # => "Howdy"
employee.salutation # => "Howdy"
Now there’s clarity around why we’re not using alias_method
. You can tell just by reading the method name. Rad.