A Code Example of Ruby Metaprogramming

I'll let the code speak for itself:
class Animal
  def sound
    puts 'A non-descript sound is emitted from the animal'
  end
end
class Frog < Animal
  def sound
    puts 'Ribbit!'
  end
end
class Cat < Animal
  def sound
    puts 'Meow!'
  end
end
class Person
  attr_accessor :name
  def initialize(name)
    @injured = false
    @name = name
  end
  def injure!(by = nil)
    print "#{name}: Help I'm being attacked"
    print " by a #{by.class}" if by
    print '!'
    puts
    @injured = true
  end
  def injured?
    @injured
  end
end
module Aggression
  def self.apply_to_all_animals
    animal_classes = Module.constants.map { |x| Module.const_get(x) }.select { |x| x.respond_to?(:superclass) && x.superclass == Animal }
    animal_classes.each do |animal_class| 
      animal_class.module_eval do
        include Aggression
      end
    end
  end
  def attack(person)
    sound
    puts "*Munch*"
    person.injure!(self)
    sound
    puts
  end
end
bonsai = Cat.new
brian = Frog.new
people = [Person.new('Ollie'), Person.new('manitoba98')]
begin
  bonsai.attack(people[0])
rescue => e
  puts 'Aww bonsai isn\'t capable of agressition. How cute'
end
begin
  brian.attack(people[0])
rescue => e
  puts 'Nor is brian awwww'
end
puts
# and then god said:
Aggression.apply_to_all_animals
bonsai.attack(people[0])
brian.attack(people[1])

Output:

Aww bonsai isn't capable of agressition. How cute
Nor is brian awwww
Meow!
*Munch*
Ollie: Help I'm being attacked by a Cat!
Meow!
Ribbit!
*Munch*
manitoba98: Help I'm being attacked by a Frog!
Ribbit!
Found here.

Needless to say, I'm really enjoying Ruby.

#1 Derick Bailey avatar
Derick Bailey
2.15.2010
9:55 PM

nice. :)

You can simplify this quite a bit, too.

The animal class doesn't need to define the sound method, really. what happens if you have an animal that doesn't make sounds? then you have a leaky abstraction. :)

and you definitely don't need to use exception handling to get the "can't attack" message - use a general method_missing instead.

and the code you have for finding the animal instances is also very complex for what you are doing. this can be done with a simple Animal.module_eval call instead of all those nested loops and if-then statements. this is where the inheritance chain from animal really comes in handy. :)

the end result looks like this:

class Animal

def method_missing(method, *args)

puts "#{self.class} cannot #{method}"

end

end

class Frog < Animal

def sound

puts 'Ribbit!'

end

end

class Cat < Animal

def sound

puts 'Meow!'

end

end

class Person

attr_accessor :name

def initialize(name)

@injured = false

@name = name

end

def injure!(by = nil)

print "#{name}: Help I'm being attacked"

print " by a #{by.class}" if by

print '!'

puts

@injured = true

end

def injured?

@injured

end

end

module Aggression

def attack(person)

sound

puts "*Munch*"

person.injure!(self)

sound

puts

end

end

bonsai = Cat.new

brian = Frog.new

people = [Person.new('Ollie'), Person.new('manitoba98')]

bonsai.attack(people[0])

brian.attack(people[0])

puts

# and then god said:

Animal.module_eval do

include Aggression

end

bonsai.attack(people[0])

brian.attack(people[1])


#2 Brian Armstrong avatar
Brian Armstrong
3.28.2010
2:58 PM

Nice improvement Derick!