From: Gregory Seidman Date: 2006-05-14T22:59:45+09:00 Subject: Re: "Seeing Metaclasses Clearly" On Sun, May 14, 2006 at 07:52:08PM +0900, Pat Maddox wrote: [...] } 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