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.