Exploring Metaprogramming

I’ve got some downtime this weekend, so I’m pooring over various
examples of
metaprogramming w/ Ruby.

I really like _why’s example regarding Dwemthy, but am curious as to how
to
achieve my end.

The article in question is here:

http://poignantguide.net/ruby/chapter-6.html#section3

_why’s method allows us to build ‘traits’ for the character (by calling
the
‘trait’ class method), and the meta programming behind the scenes builds
an
‘initialize’ instance method for the class.

What if I want to build a second method called ‘command’? It should
place
the argument in an array that can be referenced later. It will need to
also
build an initialize method for my class, but one of them will have to be
overwritten.

Does anyone have any smart ways to combine or chain these two initialize
methods together?

I ultimately want to do something like this:

class Dragon < Creature
traits :life
life 100

command :fly

def fly
puts “I am flying…”
end

end

After an instance is created, it will contain the instance variable
‘commands’, which is an array holding the symbol ‘fly’.

Does anyone have any ideas? In the end, I’d be adding more and more of
these commands to my DSL. This is a DSL, right? :wink:

On Sep 3, 2006, at 10:39 PM, Michael G. wrote:

http://poignantguide.net/ruby/chapter-6.html#section3
need to also
class Dragon < Creature

After an instance is created, it will contain the instance variable
‘commands’, which is an array holding the symbol ‘fly’.

here’s something to get you started:

class Creature
def self.command(com)
@commands ||= []
@commands |= [com]
end

def self.commands
@commands ||= []
end
end

class Dragon < Creature
command :fly
end

p Dragon.commands #=> [:fly]

On Sep 3, 2006, at 10:39 PM, Michael G. wrote:

initialize
def fly
these commands to my DSL. This is a DSL, right? :wink:
Sorry I seem to have under estimated what you wanted.

try this instead:

% cat dragon.rb
class Creature
def self.command(cmd)
old_initialize = instance_method(:initialize)
define_method(:initialize) do |*args|
@commands ||= []
@commands |= [cmd]
old_initialize.bind(self).call(*args)
end
end

def commands
@commands ||= []
end
end

class Dragon < Creature
command :fly
def fly
puts “I’m flying”
end

command :breathe_fire

def breathe_fire
puts “I’m breathing fire!”
end
end

dragon = Dragon.new
p dragon.commands

% ruby dragon.rb
[:breathe_fire, :fly]

Thanks a ton for this! This is amazing!

-----Original Message-----
[…]
On 9/3/06, Logan C. [email protected] wrote:
[…]

def commands
@commands ||= []
end
end

class Dragon < Creature
command :fly
def fly
puts “I’m flying”
end
[…]

I’m doing a very similar thing, but my approach was different. I have
something vaguely like:

class Creature

class << self; attr_reader :commands end

def self.command( cmd )
	@commands ||= []
	@commands |= [cmd]
end
[...]
def initialize( *args )
	self.class.commands.each do |command| #do something
[...or maybe even...]
	@commands=self.class.commands

Which kind of puts a sticky note on the fridge of the derived class that
it
needs to do something, but allows you to put off what you’re doing until
you’re actually instantiating the object. This way when you get to #do
something, you can do it based on the arguments passed at initialize
time,
like say you get dragon=Dragon.new(AmazinglyPowerful) - you can make
your
breathe_fire command hot enough to melt sand.

I have also actually been passing code as blocks:

Class Dragon < Creature
command :fly, proc {puts “Lookit me, Mom!”}
end

Which lets you instance_eval the block later in the correct namespace,
or
just define the method at runtime with define_method. That can also be
used
in combination with a trick I found here on ruby-lang via google to
instance_eval a block with arguments; said trick will soon not be
neccesary,
so I’ll just add a note to google instance_exec and you’ll probably dig
it
up fine.

Anyway, I really like the line above that rebinds the initialize method
to
perform a kind of ‘reverse super’…

old_initialize.bind(self).call(*args)

…and I’m trying to work out if it provides some benefits I have
overlooked, apart from the fact that it will make the initialize method
look
a little cleaner and the things like self.command look a little messier.

Cheers,

ben