Thursday, March 30, 2006

Ruby Attribute Initializer

update: following recommendation by Dan North, changed implementation by moving eval. This should result in better performance, see comment 1 for more information.

On my current project I found we often duplicated the pattern:
def method_name
@method_name ||= Klass.new
end
I saw this often enough that I decided to create a module to reduce the duplication. The module allows you to specify an attribute that will be initialized on the first execution. Additionally, it allows you to specify what type of class you want initialized and it allows you to provide constructor arguments if necessary.
# module definition
module AttributeInitializer
def attr_init(name, klass, *ctor_args)
eval "define_method(name) { @#{name} ||= klass.new(*ctor_args) }"
end

def self.append_features(mod)
mod.extend(self)
end
end

# example of usage
class Foo
include AttributeInitializer
attr_init :bar, Array, 1, "some_val"
end

# tests to ensure it works as expected
require 'test/unit'

class AttributeInitTest < Test::Unit::TestCase
def test_same_instance_is_returned
f = Foo.new
assert_equal f.bar, f.bar
end

def test_ctor_args_are_correctly_passed
f = Foo.new
assert_equal ["some_val"], f.bar
end
end
*Note: adding the constructor arguments is completely optional and the above code will also work if you change
attr_init :bar, Array, 1, "some_val"
to
attr_init :bar, Array
and
assert_equal ["some_val"], f.bar
to
assert_equal [], f.bar
Post a Comment