A Code Example of Ruby Metaprogramming
Friday, February 12 2010 2 Comments
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.



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])