Hi,

I am looking for ways to handle extensions of classes while reusing saved
object (e.g. through Marshal). New methods are no problem, but new
attributes are. What to do with old object that does not yet have these
attributes? I don't like handling the nil case everywhere i use the
attributes, because it clutters up the code. Futhermore, I would like to
decide in one place what the default value should be when that attribute
is undefined. Finally, it would be really convient to know all declared
attributes, even for old objects.

Writing _load myself to ensure that new variables exist would be tedious
and error prone (I have really many classes). As an alternative I could
add my own method to all classes the ensure that the new variables exist
and call this method after I have loaded my object. Yet, changing the
default values for new attributes in such a method would render old and
new objects with different default values.

I have come up with the following scheme and is posting here as a request
for comments. I have extended the Class class with a new attribute
accessor creator, property_attr which takes a hash mapping symbols to they
default values. It creates accessor methods sym= and sym. The reader will
return the default value of the attribute if it isn't defined. This is my
mock-up:


class Class

  def has_properties?
    instance_variables.include? '@properties'
  end

  # define accessor methods for each of the keys in hsh (must be
  # symbols) and set they default value to the value in the hash.
  # E.g. "property_attr :foo => 2" creates accessor methods for
  # attribute `foo` which is 2 when not defined in an instance of the
  # class.

  def property_attr(hsh)
    @properties = {}  unless has_properties?
    hsh.each { |sym, defaultvalue|
      @properties[sym] = defaultvalue
      class_eval "def #{sym}
                    if instance_variables.include?('@#{sym}')
                      @#{sym}
                    else
                      self.class.get_default_value :#{sym}
                    end
                  end"
      class_eval "def #{sym}=(value); @#{sym} = value; end"
    }
  end

  # returns a list of property symbols in this classes and its
  # superclasses.

  def properties
    myprops = has_properties? ? @properties.keys : []
    if superclass.nil?
      myprops
    else
      (superclass.properties + myprops).uniq
    end
  end

  # find the default value for some property

  def get_default_value(asymbol)
    if has_properties? && @properties.has_key?(asymbol)
      @properties[asymbol]
    else
      superclass.get_default_value(asymbol)
    end
  end

end


Example:

class Foo
  property_attr :foo => 2, :bar => "Hello World."
end

o = Foo.new
p o.foo				# => 2
o.foo = 3
p o.foo                         # => 3
p o.bar				# => "Hello World."
p o.class.properties		# => [:foo, :bar]

class Bar < Foo
  property_attr :baz => [1,2,3], :foo => 4
end

b = Bar.new
p b.foo				# => 4
p b.bar				# => "Hello World."
p b.baz				# => [1, 2, 3]
p b.class.properties		# => [:foo, :bar, :baz]



Maybe "property" is a bad name but I couldn't figure out a really
descriptive name.

Please share your comments on my implementation and thoughts on how to
handle extentions to existing objects, even other approaches than mine.
Thank you!

A note on the code: I use instance_variables.include?('@sym')  instead of
@sym.nil? in order to avoid warnings with the -w option. Is there a better
way? instance_variables inefficiently creates a new array on each
invokation.

-- 
Lars Christensen, larsch / cs.auc.dk