[...]
} Actually, it doesn't. Your code lets me do
} HappyTruck.new.company
}
} but why's code doesn't. His code does
} HappyTruck.company
}
} Which struck me as kind of useless, to be honest. If you're going to
} make it a class-level method, why do all the metaclass stuff? What's
} wrong with:
}
} class HappyTruck < MailTruck
} def self.company; "Happy Truck Company"; end
} end
[...]
} I'm REALLY not understanding this stuff now :\
For purposes of exposition, _why was using a simple example. A bit further
down the post he uses a more complex example involving Creatures. I'll
rewrite his example a little more simply, without his meta_def and such
(which I find somewhat confusing, honestly), and with some stylistic
changes I prefer:
class Creature
class << self
attr_reader :traits
def traits(*args)
@traits ||= {}
return @traits if args.empty?
attr_accessor *args
args.each { |trait|
#need to use send because define_method is private
self.class.send(:define_method, trait) { |val|
@traits[trait] = val
}
}
end
def set_traits_defaults(obj)
@traits.each { |k,v|
obj.send(:instance_variable_set, "@#{k}", v)
}
end
end
def initialize
self.class.set_traits_defaults(self)
end
end
class WhimperingWhuffle < Creature
traits :fear_level, :whimpering_volume
fear_level 2
whimpering_volume 8
end
class SarcasticSnark < Creature
traits :snarkiness_level, :toxicity
snarkiness_level 7
toxicity 200
end
First off, what is the goal of this code? We want to be able to create
Creature subclasses that have creature traits with default values. For our
WhimperingWhuffle, new instances are created with a fear_level of 2 and a
whimpering_volume of 8.
In the WhimperingWhuffle class definition, the traits line calls the method
we defined on the Creature class. The Creature.traits method initializes
the @traits instance variable, but the instance in question is the
WhimperingWhuffle class object, i.e. an instance of Class. The @traits
instance variable can be thought of as metadata for the WhimperingWhuffle
class. The Creature.traits method also call attr_accessor, but this is
again called on the WhimperingWhuffle class rather than the Creature class.
When we instantiate (e.g. foo = WhimperingWhuffle.new), the initialize
method gets called. The initialize method sets the associated instance
variables to the defaults in the traits metadata.
Now consider the following version, which is a bit kinky but doesn't
require the repetition inherent in declaring the traits then setting their
default values:
class Creature
class << self
attr_reader :traits
def method_missing(method_name, *args)
if /[=?]/ !~ method_name.to_s && args.length == 1
@traits ||= {}
attr_accessor method_name
@traits[method_name] = args[0]
else
super method_name, *args
end
end
def set_traits_defaults(obj)
@traits.each { |k,v|
obj.send(:instance_variable_set, "@#{k}", v)
}
end
end
def initialize
self.class.set_traits_defaults(self)
end
end
class WhimperingWhuffle < Creature
fear_level 2
whimpering_volume 8
end
class SarcasticSnark < Creature
snarkiness_level 7
toxicity 200
end
We have the same sort of metaprogramming going on, but it's a bit simpler.
We simply assume that any statements (that do not have a ? or = in them and
are called with a single argument) that are otherwise unrecognized are
trait declarations, with a default value. The code is much the same as
before, except we don't have to create class methods for the traits.
Does this help your understanding?
} Pat
--Greg
···
On Sun, May 14, 2006 at 07:52:08PM +0900, Pat Maddox wrote: