Rubyの標準ライブラリを読んでみようってことで今回は「OpenStruct」を読むことにしました。
OpenStructとは
OpenStructを使えばJavaScriptみたいにオブジェクトへの要素の追加や削除が簡単に出来るようになります。
require 'ostruct' pos = OpenStruct.new(x: 10, y: 20) puts "x = #{pos.x}" puts "y = #{pos.y}" pos.z = 30 pos.x = 40 puts "x = #{pos.x}" puts "z = #{pos.z}"
実行結果
x = 10 y = 20 x = 40 z = 30
ということで早速ostruct.rbを読んでいきたいと思います。(バージョンは2.0.0-p247)
初期化部分
まずは初期化部分。initializeメソッド部分を抜粋
def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end
ここではhash引数が渡されているかどうかをチェックして、何か渡されている場合はHash.each_pairを呼び出してその中身を繰り返し処理しています。
hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end
ブロックの中では@tableにキーを追加してnew_ostruct_memberメソッドを呼び出すという処理を行っています。(ちなみに@tableはOpenStructクラス内で使用されているインスタンス変数でHashです)
new_ostruct_member
def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name] } define_singleton_method("#{name}=") { |x| modifiable[name] = x } end name end
new_ostruct_memberでは、まずrespond_to?メソッドを使用してメソッドが呼び出せるかどうかをチェックして、もし呼び出せない場合はdefine_singleton_methodを使用してメソッドを定義します。定義するメソッドは2つで、1つは値を読み込むためのメソッド。
define_singleton_method(name) { @table[name] }
そして、もう1つが値を書き込むためのメソッドです。
define_singleton_method("#{name}=") { |x| modifiable[name] = x }
ここでmodifiableメソッドが登場します。
def modifiable begin @modifiable = true rescue raise TypeError, "can't modify frozen #{self.class}", caller(3) end @table end
ここでは「@modifiableに値を代入して、例外が発生しなければ@tableを返す」という処理を行っています。@modifialbeへの値の代入はOpenStructクラスのオブジェクトがfreezeされていた場合、例外が発生します。
例外が発生しない場合は、modifiableメソッドの返り値が@tableなので、@tableに対して[name]=とすることで代入を実現しています。
define_singleton_method("#{name}=") { |x| modifiable[name] = x }
ここまでが初期化の部分です。
要素の追加
オブジェクトに対しての要素の追加はmethod_missingを利用しています。
method_missing
def method_missing(mid, *args) # :nodoc: mname = mid.id2name len = args.length if mname.chomp!('=') 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 @table[mid] else raise NoMethodError, "undefined method `#{mid}' for #{self}", caller(1) end end
method_missingは存在しないメソッドを呼び出した時に呼ばれるメソッドで、上のサンプルの場合は
pos.z = 30
この部分がmethod_missingが呼ばれる部分となります。
method_missingが呼び出されると、呼びだそうとしたメソッド名が「name=」のような形式だった場合に、chomp!メソッドを使用して'='を取り除き、それをnew_ostruct_memberの引数として渡し実行し、新たに要素が追加します。
要素の削除
オブジェクトから要素を削除するためにはdelete_fieldメソッドを使用する必要があります。
def delete_field(name) sym = name.to_sym singleton_class.__send__(:remove_method, sym, "#{name}=") @table.delete sym end
delete_fieldメソッドでは、remove_methodを使用して、引数で渡されたnameとname=メソッドを削除した後に、deleteメソッドを使用して@tableからも削除しています。。
require 'ostruct' pos = OpenStruct.new(x: 10, y: 20) p pos pos.delete_field(:y) p pos
実行結果
#<OpenStruct x=10, y=20> #<OpenStruct x=10>
ここまでで大体の処理は読み終わりました。
始めて標準ライブラリを読んでみましたが、色々知らない構文を見ることが出来てよかったです。次もまた何か別のライブラリを読んでみようかなと思います。