Shortcut for setting initialize() args to attributes?

ruby

Solution

Here's how you could do that. One instance variable and an associated read/write accessor will be created for each of `initialize`'s parameters, with the variable having the same name, preceded by `@`, and each instance variable will be assigned the value of the associated parameter.

Code

class MyClass
  def initialize(< arbitrary parameters >)
    self.class.params.each { |v|
      instance_variable_set("@#{v}", instance_eval("#{v}")) }

    < other code >
  end

  @params = instance_method(:initialize).parameters.map(&:last)
  @params.each { |p| instance_eval("attr_accessor :#{p}") }

  class << self
    attr_reader :params
  end  

  < other code >     
end

Example

class MyClass
  def initialize(a, b, c)
    self.class.params.each { |v|
      instance_variable_set("@#{v}", instance_eval("#{v}")) }
  end

  @params = instance_method(:initialize).parameters.map(&:last)
  @params.each { |p| instance_eval("attr_accessor :#{p}") }

  class << self
    attr_reader :params
  end  
end

MyClass.methods(false)
  #=> [:params]
MyClass.instance_methods(false)
  #=> [:a, :a=, :b, :b=, :c, :c=]

m = MyClass.new(1,2,3)
m.a #=> 1
m.b #=> 2
m.c #=> 3
m.a = 4
m.a #=> 4

Explanation

When class `MyClass` is parsed, the class instance variable `@params` is assigned an array whose elements are `initialize`'s parameters. This is possible because the method `initialize` been created when the code beginning `@params = ...` is parsed.

The method Method#parameters is used to obtain `initialize`'s parameters. For the example above,

instance_method(:initialize).parameters
  #=> [[:req, :a], [:req, :b], [:req, :c]]

so

@params = instance_method(:initialize).parameters.map(&:last)
  #=> [:a, :b, :c]

We then create the read/write accessors:

@params.each { |p| instance_eval("attr_accessor :#{p}") }

and a read accessor for `@params`, for use by `initialize`:

class << self
  attr_reader :params
end  

When an instance `my_class` of `MyClass` is created, the parameter values passed to `MyClass.new` are passed to `initialize`. `initialize` then loops though the class instance variable `@params` and sets the value of each instance variable. In this example,

MyClass.new(1,2,3)

invokes `initialize(a,b,c)` where

a => 1
b => 2
c => 3

We have:

params = self.class.params
  #=> [:a, :b, :c]

params.each { |v| instance_variable_set("@#{v}", instance_eval("#{v}")) }

For the first element of `params` (`:a`), this is:

instance_variable_set("@a", instance_eval(a) }

which is:

instance_variable_set("@a", 1 }

causing `@a` to be assigned `1`.

Note the accessor for `@params` is not essential:

class MyClass
  def initialize(a, b, c)
    self.class.instance_variable_get(:@params).each { |v|
      instance_variable_set("@#{v}", instance_eval("#{v}")) }
  end

  @params = instance_method(:initialize).parameters.map(&:last)
  @params.each { |p| instance_eval("attr_accessor :#{p}") }
end

Problem

This is a common initializer pattern: ``` def initialize(title, val, type) @title, @val, @type = title, val, type end ``` Is there a shortcut that is equivalent to "take every argument, create an attribute of the same name, and set the attribute equal to the argument's value"? I'm looking for a gem-free solution.

Original source

Related problems