From: Lars Christensen Date: 2001-07-01T23:05:38+09:00 Subject: [ruby-talk:17126] Extending existing object pools 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