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 DomainObjectSo, without further ado.
attr_reader :arg1, :arg2
def initialize(arg1, arg2)
@arg1, @arg2 = arg1, arg2
end
end
class ModuleThe 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.
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
class ModuleExtensionTest < Test::Unit::TestCaseThere are limitations, such as not being able to use default values. But, for 80% of the time, this is exactly what I need.
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
Labels: initializer, metaprogramming, ruby
Comments:
<< Home
Dave,
I think the implementation is good. Perhaps it's even more efficient to to class_eval the way you did since it will only happen once. Mine may be less efficient because I use a block that holds it's context.
Do you use this in your codebases? I just added it to ours, so I'm curious if you have any lessons learned.
I do like the ability to use an options hash also though. Long constructors are always a pain for remembering argument ordering.
Like you, I figured someone did something similar in the past. The easiest way to find out is to write about it. =)
Cheers, Jay
I think the implementation is good. Perhaps it's even more efficient to to class_eval the way you did since it will only happen once. Mine may be less efficient because I use a block that holds it's context.
Do you use this in your codebases? I just added it to ours, so I'm curious if you have any lessons learned.
I do like the ability to use an options hash also though. Long constructors are always a pain for remembering argument ordering.
Like you, I figured someone did something similar in the past. The easiest way to find out is to write about it. =)
Cheers, Jay
It isn't clear to me why your initializer method has that popping-off-the-hash code. There isn't an example in your tests for that.
Brian,
The options pop is for grabbing a hash if one was passed in. When no hash is passed in (first 3 tests) then it uses an empty hash. However, if a hash is passed in (the last test) it initializes the instance variables from the hash.
The options pop is for grabbing a hash if one was passed in. When no hash is passed in (first 3 tests) then it uses an empty hash. However, if a hash is passed in (the last test) it initializes the instance variables from the hash.
Some of us at Atomic Object have done something similar in rails projects with the injection plugin.
It's something we've come up with to let us use simple constructor based dependency injection to put together lots of instances of small and simple classes / components.
In a class we use a 'constructor' helper that takes a list of names, then generates an initialize method that expects to receive a hash that contains those keys, then sets the values to instance variables with the same names.
Post a Comment
It's something we've come up with to let us use simple constructor based dependency injection to put together lots of instances of small and simple classes / components.
In a class we use a 'constructor' helper that takes a list of names, then generates an initialize method that expects to receive a hash that contains those keys, then sets the values to instance variables with the same names.
<< Home


