Hal Fulton wrote:
> For those curious, what I want to do is establish defaults for
> fields in an object, such that:
> 
>   1. When I dump an object, fields that still have the default
>      values will be left unspecified.
>   2. When I load an object, unspecified fields will be given their
>      default values.
> 

Hi, Hal.  Good for you, bringing this up again.  We didn't finish this 
discussion at all and the clock has struck loudly here: time to continue.

In Hobix, I am currently using a mixin for classes which need the above. 
  This mixin relies upon the presence of a property_map method within 
those classes, which defines the full set of instance variables which I 
want to output in YAML, along with whether those fields are optional or not.

The mixin also requires default_#{ivar} methods for every instance 
variable you plan on having a default for.  The mixin will use nil as 
the default, if no such method is found.  You could include defaults in 
the property_map, but I sometimes base the default value of a field on 
the value of other fields (in the case of file paths), so I use a method 
for each instead.

So, for example, Hobix blog entries only require @title, @content and 
@author.  Other fields are optional and aren't output to YAML if they 
are blank.

The Hobix::Entry#property_map basically looks like this:

   class Hobix::Entry
     include ToYamlExtras
     def property_map
         [
             ['@title', :req],
             ['@author', :req],
             ['@contributors', :opt],
             ['@created', :opt],
             ['@tagline', :opt],
             ['@summary', :opt],
             ['@content', :req]
         ]
     end
   end

I'm using an Array to ensure ordering of the elements upon output.

Here would be the ToYamlExtras mixin:

   module ToYamlExtras
     def to_yaml_properties
         property_map.find_all do |prop, req|
             case req
             when :opt
                 val = nil
                 if respond_to?( "default_#{ prop[1..-1] }" )
                     val = method( "default_#{ prop[1..-1] }" ).call
                 end
                 val != instance_variable_get( prop )
             when :req
                 true
             end
         end.
         collect { |prop, req| prop }
     end
     def initialize
         apply_defaults
         yield self if block_given?
     end
     def apply_defaults
         property_map.each do |prop, req|
             name = prop[1..-1]
             if instance_variable_get( prop ).nil? and
               respond_to?( "default_#{ name }" )
                 instance_variable_set( prop,
                   method( "default_#{ name }" ).call )
             end
         end
         self
     end
   end

As you can see, the mixin simply defines a to_yaml_properties method, 
which is a method understood by the YAML library.  The 
to_yaml_properties method should return an Array of instance variable 
names, in the order they are output.

As you can see, the mixin also defines 'initialize', since you'll 
probably want to apply the defaults even if the object is created 
programmatically.

You'll also need to hook yourself a YAML type for each class.  In the 
case of the Hobix::Entry class, I'm hooking !hobix.com,2004/entry.

   class Hobix::Entry
     def to_yaml_type
         "!hobix.com,2004/entry"
     end
   end

   YAML::add_domain_type( 'hobix.com,2004', 'entry' ) do |type, val|
     YAML::object_maker( Hobix::Entry, val ).apply_defaults
   end

A script including this mixin and a few tests of the above can be found 
at: http://whytheluckystiff.net/ruby/yaml-defaults-mixin.rb

_why