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!