At 02:27 PM 9/5/01, you wrote: >class Module > def attr_default(symbol, default) > default = "'#{default}'" if default.type == String > module_eval "def #{symbol}() if @#{symbol}.nil?; #{default}; else >@#{symbol}; end; end" > end > > def attr_filter(symbol, *filters) > filtered_setter = "def #{symbol}=(value) " > filters.each { |f| > filtered_setter += " value=#{f}(value);" > } > filtered_setter += " @#{symbol}=value; end" > module_eval filtered_setter > end >end Thanks very much for posting this. I was really in the mood for tinkering with code, and this was just the thing I needed. A crummy day has ended as a fun one. I've made a series of changes that I hope are useful. Warning: they haven't been tested well. First, I noticed a problem. The code doesn't work with arrays as the default: class Cat attr_default :lives, [1, 2, 3, 4, 5, 6, 7, 8, 9] end > Cat.new.lives 123456789 The problem is #{default} here: module_eval "def #{symbol}() if @#{symbol}.nil?; #{default}; else @#{symbol}; end; end" #{default.inspect} would work for Arrays (and Strings, as well, eliminating the need for the String test in the line above). But 'inspect' isn't quite the thing, since its contract is to produce a human-readable form. What we want is a method that produces a string which, when evaled, produces a copy of the original object. I'm going to call that "to_e". For a lot of things, to_e can be the same as inspect. For convenience, I'll stick it on Object: class Object def to_e() inspect; end end But for my own classes, it can be defined differently than inspect: class Cat def initialize(name) @name = name; end def inspect() "<The cat named '#{@name}'>"; end def to_e() "Cat.new(#{@name.to_e})"; end end Now I can define attr_default like this: class Module def attr_default(symbol, default) module_eval "def #{symbol}() if @#{symbol}.nil?; #{default.to_e}; else @#{symbol}; end; end" end end So now I can initialize Cat with lives: class Cat attr_default :lives, [1, 2, 3, 4, 5, 6, 7, 8, 9] end > Buffy = Cat.new("buffy") <The cat named 'buffy'> > Buffy.lives [1, 2, 3, 4, 5, 6, 7, 8, 9] Moreover, Cats can be the default values for attributes of other classes: class Dog attr_default :friend, Buffy end (Buffy is a constant so that it's in scope for the class definition.) > Dog.new.friend <The cat named 'buffy'> But this raises a question: how can I have all Dogs share the same friend? Because right now they don't: > towser = Dog.new #<Dog:0xa0ab860> > ralph = Dog.new #<Dog:0xa09d790> > towser.friend <The cat named 'buffy'> > ralph.friend <The cat named 'buffy'> > towser.friend == ralph.friend false I decided to store the defaults in the class. Along the way, I also decided to change something that bugged me a bit about the original: that you couldn't set the created variables to nil: class Var attr_default :val, true def val=(val) @val = val end end > v = Var.new #<Var:0xa092480> > v.val true > v.val=nil nil > v.val # expect nil true I can fix this by initially setting the instance variable to the default *unless* it already exists. If it already exists, it's already been explicitly assigned to. Instance variables don't exist until the first assignment. Here's the code. I also added a setter method. class Module attr_accessor :attribute_defaults def attr_default(symbol, default) @attribute_defaults ||= Hash.new @attribute_defaults[symbol] = default getter = %Q{ def #{symbol}() unless instance_variables.include? '@#{symbol}' @#{symbol} = self.class.attribute_defaults[:#{symbol}] end @#{symbol} end } puts getter module_eval getter setter = %Q{ def #{symbol}=(val) @#{symbol}=val end } puts setter module_eval setter end end Both the Var and Dog examples work as I'd prefer. (Note: for some reason I haven't tried to figure out, pasting the above into irb causes parse errors, but loading it from a file does not.) Finally, I thought that it would be better to use the existing name attr_accessor, passing in the defaults as keyword arguments, like this: attr_accessor :ordinary, ordinary2, :defaulted=>5, :other=>9 I suspect there's a better way to do this, but the kids have come home from skating and my hacking time is through for tonight: class Module alias_method :old_attr_accessor, :attr_accessor attr_accessor :attribute_defaults def attr_accessor(*accessors) accessors.each { | e | if e.type == Hash e.each { | key, value | install_default_accessor(key, value) } else old_attr_accessor e end } end def install_default_accessor(symbol, default) @attribute_defaults ||= Hash.new @attribute_defaults[symbol] = default getter = %Q{ def #{symbol}() unless instance_variables.include? '@#{symbol}' @#{symbol} = self.class.attribute_defaults[:#{symbol}] end @#{symbol} end } puts getter module_eval getter setter = %Q{ def #{symbol}=(val) @#{symbol}=val end } puts setter module_eval setter end end class Multi attr_accessor :ordinary, :ordinary2, :defaulted=>5, :other=>9 end > m = Multi.new #<Multi:0xa0ac2b0> > m.ordinary nil > m.ordinary2 nil > m.defaulted 5 > m.other 9 -- Brian Marick, marick / testing.com www.testing.com - Software testing services and resources www.testingcraft.com - Where software testers exchange techniques www.visibleworkings.com - Adequate understanding of system internals