This is I think the sort of pattern you are trying to create:
class Foo
def self.all_properties
@all_properties ||= []
end
def self.property( pr_name )
attr_reader pr_name.to_sym
instance_variable_set( "@default_#{pr_name.to_s}", [pr_name.to_s] )
all_properties << pr_name.to_sym
end
def set_default_properties
self.class.all_properties.each do | pr_name |
default = self.class.instance_variable_get( "@default_#{pr_name.to_s}" )
instance_variable_set( "@#{pr_name}", default.clone )
end
end
def initialize
set_default_properties
end
end
class Bar < Foo
property :foo
end
p Bar.new.foo
It is more complex than you might initially assume. You have to put the list of managed properties, and their default values somewhere. I have done this above with the class instance variables @all_properties and @default_foo. You can get them forwarded into each instance with a little more meta-programming than you have in the question - basically a copy from defaults stashed in the class when it was defined has to be made during instantiation. Why? Because the class definition is not re-run during instantiation, it happens just once beforehand (unless you start modifying the class on-the-fly in the constructor - but that would be unusual!)
Note that the clone in the code above is not quite enough to prevent instances interfering with each other. I haven't implemented a deep clone (left as exercise to anyone reading the code), but the following variation of that line will work for the code as it stands, because the structure of default data is always the same:
instance_variable_set( "@#{pr_name}", [ default[0].clone ] )
bar :foois only called when the class is defined, and the instance variable is set on the classBar. Theattr_readerworks, because that is supposed to be called agains the class. You cannot modify instances that don't exist. However, you could probably stash names and defaults somewhere (perhaps in the target class), and supply a method that you could call frominitializeto read all the default values.