This is the first in a series of entries which I’m pulling over articles from an old blog, revising, etc…
The Problem
One of the neat things about Ruby is it’s ability to create accessor methods for you, by simply declaring attr_reader
, attr_writer
, or attr_accessor
. However, there’s not an easy way to define a default value for an attribute. The original version of this code was based off of Create getter and setter on a valorized variable and ideas from Ruby Quiz #67: Metakoans. The main difference between it and Sandro Paganotti’s version is that you can pass in a block which will be evaluated each time it is invoked (in case the value might depend on something else). Additionally, it will work with boolean types.
The Solution
class Object
def self.attribute(*arg,&block)
(name, default) = arg
short_name = name.to_s.sub(/\?/,"")
self.send(:define_method, name) {
if instance_variables.include? "@#{short_name}"
self.instance_eval "@#{short_name}"
else
if block_given?
instance_eval &block
else
default
end
end
}
self.send(:define_method, "#{short_name}="){ |value|
self.instance_eval "@#{short_name} = value"
}
end
end
In order to use it, you could do something like this:
class Foo
attribute :bar
attribute(:fud) {instance_variables.include?("@bar") ? @bar : ' '}
attribute :fi, 10
attribute :flag?, true
end
And, in action, it would look like this:
>> f=Foo.new
=> #
>> f.bar # defined, but not set to anything (nil)
=> nil
>> f.fud # since bar is not defined, return an empty space
=> " "
>> f.fi # just return the default
=> 10
>> f.flag? # and here's a boolean
=> true
>> f.bar = 5 # now we set bar
=> 5
>> f.fud # Since bar is set, then fud will return its value
=> 5
>> f.fud = 20 # now we explicitly set fud, and the block is no longer used
=> 20
>> f.fud # fud returns 20 as expected
=> 20
>> f.bar # and, as expected, changing fud has no effect on bar
=> 5
There might be a better way to do this; but one of the things I love about Ruby is that you can add new features to the language very easily.