A solution to that problem already (partially) exists, but if you want a more declarative approach in your classes then the following should work.
class Class
def initialize_with(*attrs, &block)
attrs.each do |attr|
attr_accessor attr
end
(class << self; self; end).send :define_method, :new do |*args|
obj = allocate
init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
attrs.zip(init_args) do |attr, arg|
obj.instance_variable_set "@#{attr}", arg
end
obj.send :initialize, *surplus_args
obj
end
end
end
You can now do:
class MyClass < ParentClass
initialize_with :foo, :bar
def initialize(baz)
@initialized = true
super(baz) # pass any arguments to initializer of superclass
end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:@initialized) #=> true
Some characteristics of this solution:
- Specify constructor attributes with
initialize_with
- Optionally use
initialize
to do custom initialization
- Possible to call
super
in initialize
- Arguments to
initialize
are the arguments that were not consumed by attributes specified with initialize_with
- Easily extracted into a Module
- Constructor attributes specified with
initialize_with
are inherited, but defining a new set on a child class will remove the parent attributes
- Dynamic solution probably has performance hit
If you want to create a solution with absolute minimal performance overhead, it would be not that difficult to refactor most of the functionality into a string which can be eval
ed when the initializer is defined. I have not benchmarked what the difference would be.
Note: I found that hacking new
works better than hacking initialize
. If you define initialize
with metaprogramming, you'd probably get a scenario where you pass a block to initialize_with
as a substitute initializer, and it's not possible to use super
in a block.