Thursday, April 19, 2007

Ruby: Assigning instance variables in a constructor

Every time I assign an instance variable in a constructor I remember that I've been meaning to write something that takes care of it for me.
class DomainObject
attr_reader :arg1, :arg2

def initialize(arg1, arg2)
@arg1, @arg2 = arg1, arg2
end
end
So, without further ado.
class Module
def initializer(*args, &block)
define_method :initialize do |*ctor_args|
ctor_named_args = (ctor_args.last.is_a?(Hash) ? ctor_args.pop : {})
(0..args.size).each do |index|
instance_variable_set("@#{args[index]}", ctor_args[index])
end
ctor_named_args.each_pair do |param_name, param_value|
instance_variable_set("@#{param_name}", param_value)
end
initialize_behavior
end

define_method :initialize_behavior, &block
end
end
The above code allows you to create a constructor that takes arguments in order or from a hash. Here's the tests that demonstrate the behavior of the initializer method.
class ModuleExtensionTest < Test::Unit::TestCase
def test_1st_argument
klass = Class.new do
attr_reader :foo

initializer :foo
end
foo = klass.new('foo')
assert_equal 'foo', foo.foo
end

def test_block_executed
klass = Class.new do
attr_reader :bar

initializer do
@bar = 1
end
end
foo = klass.new
assert_equal 1, foo.bar
end

def test_2nd_argument
klass = Class.new do
attr_reader :foo, :baz

initializer :foo, :baz
end
foo = klass.new('foo', 'baz')
assert_equal 'baz', foo.baz
end

def test_used_hash_to_initialize_attrs
klass = Class.new do
attr_reader :foo, :baz, :cat

initializer :foo, :baz, :cat
end
foo = klass.new(:cat => 'cat', :baz => 2, :foo => 'foo')
assert_equal 'foo', foo.foo
assert_equal 2, foo.baz
assert_equal 'cat', foo.cat
end
end
There are limitations, such as not being able to use default values. But, for 80% of the time, this is exactly what I need.
Post a Comment