From: Timothy Goddard Date: 2006-02-21T05:53:30+09:00 Subject: Re: metakoans.rb (#67) This is my solution after a bit more trimming this morning: class Module def attribute(*arguments, &block) # This will store attribute names for adding writers and testers attributes = [] # Iterate over arguments, defining a getter function and adding each attribute to the attributes list. arguments.each do |argument| if argument.is_a? Hash # Defaults from hash values # Our argument is a hash. Iterate over the hash, treating keys as attributes and values as defaults. argument.each do |attribute, default| attributes << attribute.to_s # Define getter with fixed default define_method((attribute).to_sym) do return default unless instance_variables.include?('@' + attribute.to_s) self.instance_variable_get('@' + attribute.to_s) end end # argument.each elsif block # Default from block attributes << argument.to_s # Our default is a block which should be instance_evaled to get a default value. define_method((argument).to_sym) do return instance_eval(&block) unless instance_variables.include?('@' + argument.to_s) self.instance_variable_get('@' + argument.to_s) end else # No default attributes << argument.to_s define_method((argument).to_sym) do self.instance_variable_get('@' + argument.to_s) end end # if argument.is_a? Hash ... elsif block ... end end # arguments.each # Iterate over the attributes, defining our writer and tester methods. attributes.each do |attribute| # Define the writer. attr_writer attribute.to_sym # Define the tester define_method((attribute + '?').to_sym) do self.send(attribute) ? true : false end end # attributes.each end # def attribute end # class Module I decided to allow the hash to overrule the block for a simple reason: only one block can be provided. This allows me to use (for example) attribute :foo, :bar => 3 do bar * 7 end This means that we can give fixed defaults to some attributes while using a block for those without them.