Chris
Chris

Reputation: 12181

Wrap inherited method in aliases

I want to provide a consistent interface by inheriting from a base class. But certain methods on that base class I would like to wrap with additional functionality.

Let's say every subclass will have an eat method:

class ClassyAnimal

  def self.inherited(base)
    base.class_eval do
      alias :eat_without_napkin :eat
      alias :eat :eat_with_napkin 
    end
  end

  def eat_with_napkin
    begin
      eat_without_napkin
    rescue FoodOutOfMouthError
      puts 'phew!'
    end
  end

  def eat
    raise 'Please implement the #eat method.'
  end
end

class ClassyWalrus < ClassyAnimal

  def eat
    puts 'Eating Cronuts.'
    raise FoodOutOfMouthError.new('Damn these tusks!')
    puts 'Finishing my meal.'
  end
end

class FoodOutOfMouthError < StandardError; end

Now I would hope that ClassyWalrus.new.eat would output the following:

Eating Cronuts.
phew!

Unfortunately, we get the following:

Eating Cronuts.
FoodOutOfMouthError: Damn these tusks!

At the time of inheritance, and therefore at time of alias, #eat is defined as the one inside of ClassyAnimal instead of the one inside of ClassyWalrus. The aliased #eat then gets overridden by the subclass, and the wrapping is lost.

What can be done?

Upvotes: 3

Views: 625

Answers (2)

Chris
Chris

Reputation: 12181

One possibility I thought of, which I believe is how ActionMailer does this, is to define the method on the child as an instance variable, but invoke it on the class itself.

This isn't exactly what I'm looking for, so I won't accept this answer, but just to document it.

class ClassyAnimal
  def self.eat
    if instance_methods.include? :eat
      begin
        self.new.eat
      rescue FoodOutOfMouthError
        puts 'phew!'
      end
    end
  end
end

class ClassyWalrus < ClassyAnimal

  def eat
    puts 'Eating Cronuts.'
    raise FoodOutOfMouthError.new('Damn these tusks!')
    puts 'Finishing my meal.'
  end
end

class FoodOutOfMouthError < StandardError; end

Which would be called as:

ClassyWalrus.eat
# outputs => Eating Cronuts.
# outputs => phew!

Upvotes: 0

Малъ Скрылевъ
Малъ Скрылевъ

Reputation: 16507

Try the following additions to the code.

in class ClassyAnimal:

  def eat_with_napkin
    begin
      yield
    rescue FoodOutOfMouthError
      puts 'phew!'
    end
  end

in class ClassyWalrus:

  def eat
    puts 'Eating Cronuts.'
    superclass.eat do
      raise FoodOutOfMouthError.new('Damn these tusks!')
    end
    puts 'Finishing my meal.'
  end

Upvotes: 1

Related Questions