セッタが未定義もしくはprivateなインスタンス変数の内容をクラスの外部から取得する

追記

エントリ本文の内容は間違い。
instance_variable_getメソッドの引数にはシンボルだが、変数名のみではなく@を含む必要があった。

puts c.instance_variable_get(:write_only) rescue puts $!.message
#=>`instance_variable_get': `write_only' is not allowed as an instance variable name (NameError)

puts c.instance_variable_get(:@write_only) rescue puts $!.message
#=>"write only instance variable"

公式ドキュメントにもそう書いてある。ちゃんと読もう…。


エントリ内容は既に意味を失っているが、一応残しておく。

以下エントリ本文

Rubyではセッタが定義されていないインスタンス変数にはinstance_variable_getメソッドでも取得することが出来ない。

class C
  public
  attr_writer :write_only

  private
  attr_accessor :priv_variable
end

c = C.new
c.write_only = "write only instance variable"

puts c.write_only rescue puts $!.message
#=>undefined method `write_only' for #<C:0x2bb9fac> (NoMethodError)

puts c.instance_variable_get(:write_only) rescue puts $!.message
#=>`instance_variable_get': `write_only' is not allowed as an instance variable name (NameError)

puts c.instance_variable_get(:priv_variable) rescue puts $!.message
#=>`priv_variable' is not allowed as an instance variable name


上記のように、アクセスを許可されていないと言う旨の例外がスローされる。
Rubyとしては上記のような変数に対しては外部から軽々しく操作する事は推奨していないと言うことだろう。
それでもアクセスする方法は存在する。instance_evalメソッドを使えばよい。

puts c.instance_eval("@write_only")
#=>write only instance variable

c.instance_eval("@priv_variable = 'hogehoge'")
puts c.instance_eval("@priv_variable")
#=>hogehoge


instance_evalはレシーバのコンテキストでevalするからアクセス可能なのだと思う。
詳細はよく分からないから推測になってしまうが、クラス外部のコンテキストだった場合にはinstance_variable_getはただ単にセッタを呼び出しているだけなのだろう。
セッタが定義されていてもprivate変数はクラス外部のコンテキストからはアクセスできないので拒否されてしまうのだと思う。


検証用としてprivate_instance_variable_getメソッドを追加してみた。

#※上記ソースの続き
class C
  def private_instance_variable_get(name)
    instance_variable_get("@#{name.to_s}")
  end
end

puts c.private_instance_variable_get(:write_only)
#=>write only instance variable

c.instance_eval("@priv_variable = 'fugafuga'")
puts c.private_instance_variable_get(:priv_variable)
#=>fugafuga


ちゃんと動いた。
例えばこのメソッドをObjectクラスにでも追加すればinstance_evalしなくてもprivateなメソッドにアクセスすることが出来る。