DesignAssembler

備忘録に近い

openstruct

ここを見ていてopenstructが出てきて分からなかったので調べました。

kenn.hatenablog.com

OpenStruct

OpenStructはRubyの標準ライブラリで、ハッシュのような機能を持つデータ構造を提供しています。

OpenStructクラスから作成したオブジェクトをレシーバにして未定義メソッド(x=)を呼ぶとmethod_missingで新しくメソッドが定義されて値が代入できます。メタですね。

アクセサメソッド(attr_accessor)のような働きをしてます。

> require 'ostruct'
true
> struct_var = OpenStruct.new
#<OpenStruct>
> struct_var.dog
nil
> struct_var.dog = "inu"
"inu"
> struct_var
#<OpenStruct dog="inu">

instance_evalで中身を確認します

> struct_var.instance_eval{ p dog  }
"inu"

ちゃんといますね。

削除はdelete_field()でします。

>   struct_var.delete_field("dog")
"inu"
> struct_var.dog
nil

Rubyソースコードを読む

github.com

initializeはハッシュで受け取ってます。

method_missingをモンキーパッチしてます。

#L197:L214
  def method_missing(mid, *args) # :nodoc:
    len = args.length
    if mname = mid[/.*(?==\z)/m]
      if len != 1
        raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
      end
      modifiable?[new_ostruct_member!(mname)] = args[0]
    elsif len == 0
      if @table.key?(mid)
        new_ostruct_member!(mid) unless frozen?
        @table[mid]
      end
    else
      err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
      err.set_backtrace caller(1)
      raise err
    end
  end

mnameってなんやと思ってgrepしたらrespond_to_missing?をモンキーパッチしてました。

#L192:L195
  def respond_to_missing?(mid, include_private = false)
    mname = mid.to_s.chomp("=").to_sym
    @table.key?(mname) || super
  end

mnameには”=“を除いたメソッド名が入ります。

method_missingに戻ると、最初の分岐はメソッド名を正規表現で抜いたやつがmnameと同じか調べてます。

ここがtrueかつ引数が1つなら203行目でメソッド定義します。

modifiableメソッドはこれらしいです。

http://docs.ruby-lang.org/ja/2.1.0/method/OpenStruct/i/modifiable.html

#L203
modifiable?[new_ostruct_member!(mname)] = args[0]

メソッドを定義するメソッド(new_ostruct_member)は173行目〜180行目にあります(メタプロだ・・・)

#new_ostruct_member
  def new_ostruct_member!(name) # :nodoc:
    name = name.to_sym
    unless singleton_class.method_defined?(name)
      define_singleton_method(name) { @table[name] }
      define_singleton_method("#{name}=") {|x| modifiable?[name] = x}
    end
    name
  end

レシーバのオブジェクトに特異メソッドを定義してます。

method_missingから追ってメソッド定義まで辿りました。

参考

ruby-doc.org

class OpenStruct (Ruby 2.2.0)

Random Ruby Tricks: Struct.new - Literate Programming