Best Way to Abstract Initializing Attributes
abstraction, ruby
Solution
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.
Problem
What's the best way to abstract this pattern: ``` class MyClass attr_accessor :foo, :bar def initialize(foo, bar) @foo, @bar = foo, bar end end ``` A good solution should take superclasses into consideration and be able to handle still being able to have an initializer to do more things. Extra points for not sacrificing performance in your solution.