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.

Original source

Related problems