Metaprogramming in Ruby
# Getters and setters in Ruby:
class Duck
def name
@name
end
def name=(name)
@name = name
end
def quack!
puts "#{name} goes quack!!"
end
end
donald = Duck.new
donald.name = "Donald (the good kind)"
p donald.name
donald.quack!
# `@name` is how we refer to an instance variable.
#
# `donald.name = "Donald"` is **syntactic sugar** for calling the `name=` method.
# But wait! This is the same:
class Duck
attr_accessor :name
def quack!
puts "#{name} goes quack!!"
end
end
donald = Duck.new
donald.name = "Donald (the good kind)"
p donald.name
donald.quack!
# Where does `attr_accessor` come from? It's a method we inherit from the class
# named `Class`. It's a form of **metaprogramming**: the `attr_accessor` is code
# that generates other code.
# Let's do our own metaprogramming to add an `action` method we can use when
# defining an `Animal` subclass:
class Animal
attr_accessor :name
define_singleton_method("action") do |action_name|
define_method("#{action_name}!") do
puts "#{name} #{action_name}s!!"
end
end
end
class Duck < Animal
action :quack
action :waddle
end
class Moose < Animal
action :trample
end
donald = Duck.new
donald.name = "Donald (the good kind)"
donald.quack! # Where did this method come from?? Metaprogramming!
donald.waddle!
danger = Moose.new
danger.name = "Danger Moose"
danger.trample!
# Can't do this because Moose is not Duck:
# danger.quack!
# Looking for a good puzzle? Here's an even wackier example that takes what we
# did in class a little further:
class Animal
attr_accessor :name
define_singleton_method("action") do |action_name|
define_method("#{action_name}!") do
puts "#{name} #{action_name}s!!"
end
define_singleton_method("#{action_name}_type") do |adverb|
define_method("#{action_name}_#{adverb}!") do
puts "#{name} #{action_name}s #{adverb}!!"
end
end
end
end
class Duck < Animal
action :quack
quack_type :enthusiastically # The quack_type method now exists because of the previous line
action :waddle
waddle_type :gracefully
waddle_type :comically
end
class Moose < Animal
action :trample
trample_type :dangerously
end
donald = Duck.new
donald.name = "Donald (the good kind)"
donald.quack!
donald.quack_enthusiastically!
donald.waddle_gracefully!
danger = Moose.new
danger.name = "Danger Moose"
danger.trample_dangerously!