initializeの引数をインスタンス変数に引き渡す

initialize に渡された値を自動的にインスタンス変数にするを読んで。
ちょっと主旨が違うような気もしますが、書いてみました(1)。

# (1)
class Rubyco
  def initialize(name, age)
    %w(name age).each do |s|
      eval("@#{s} = #{s}")
    end
  end
  def to_s
    "name:#{@name}, age:#{@age}"
  end
end

p Rubyco.new("Alice", 23).to_s      #=> "name:Alice, age:23"

nameとageを2回書くのがいやなので、無理矢理メソッドにしてみました(2)。Array#zipが泣かせます♪

# (2)
class Object
  def make_instance_variables(names, *values)
    names.zip(values).each do |nv|
      eval("@#{nv[0]} = #{nv[1].inspect}")
    end
  end
end

class Rubyco
  def initialize(*args)
    make_instance_variables(%w(name age), *args)
  end
  def to_s
    "name:#{@name}, age:#{@age}"
  end
end

p Rubyco.new("Alice", 23).to_s    #=> "name:Alice, age:23"

課題:(2)だとinitializeのarityチェックができない。
疑問:make_instance_variablesのようなメソッドはどのクラス(またはモジュール)に入れるべきか。現在はObjectにしている。
追記:mputさんから「newの引数の数が違ったときに例外が発生しない」というコメントをいただきました。そうですね。おっしゃるとおりです。いちおう課題のところにあるarityチェック(メソッドの引数の数のこと)というのがそのつもりでした。コメントありがとうございます。
追記:上記のmputさんの指摘を受けて、(これまた無理矢理に)arityチェックを入れてみました(3)。

# (3)
class Object
  def make_instance_variables(names, *values)
    if names.length != values.length
      raise ArgumentError.new("wrong number of arguments (#{values.length} for #{names.length})");
    end
    names.zip(values).each do |nv|
      eval("@#{nv[0]} = #{nv[1].inspect}")
    end
  end
end

class Rubyco
  def initialize(*args)
    begin
      make_instance_variables(%w(name age), *args)
    rescue ArgumentError => e
      raise ArgumentError.new(e.to_s)
    end
  end
  def to_s
    "name:#{@name}, age:#{@age}"
  end
end

p Rubyco.new("Alice", 23).to_s            #=> "name:Alice, age:23"
p Rubyco.new("Alice", "Liddel", 23).to_s  #=> wrong number of arguments (3 for 2) (ArgumentError)

上のプログラムでは、コールスタックが気になったので、例外の再スローをしています(汚い)。
新たな課題:メソッドからraiseするとき、callerのコールスタックでraiseする方法を考える。絶対すでにどこかで誰かが考えていてTipsになっているはず。PerlでいうところのCarpのような処理。
追記:さっそく、なかださんからご教示いただきました(4)。$@.shiftで頭落としする方法と、caller(1)を使う方法。そうか、まさにcallerというのがあるのですね!変数に代入して比較というコンパクトな書き方と共に勉強になりました。ありがとうございます。
…そうか、代入で変数が生まれるというのはなかなか素敵仕様ですね…。

# (4)
class Object
  def make_instance_variables(names, *values)
    if (nl = names.length) != (vl = values.length)
      raise ArgumentError, "wrong number of arguments (#{vl} for #{nl})", caller(1)
    end
    names.zip(values).each do |nv|
      eval("@#{nv[0]} = #{nv[1].inspect}")
    end
  end
end

class Rubyco
  def initialize(*args)
    make_instance_variables(%w(name age), *args)
  end
  def to_s
    "name:#{@name}, age:#{@age}"
  end
end

p Rubyco.new("Alice", 23).to_s            #=> "name:Alice, age:23"
p Rubyco.new("Alice", "Liddel", 23).to_s  #=> wrong number of arguments (3 for 2) (ArgumentError)