2010/05/16

self.send()[1]

gdata-1.1.1 を読んでいたら gdata-1.1.1/lib/gdata/client/base.rb に

41           self.send("#{key}=", value)

とあった。

それで
クラスを初期化する際 hash をもちいてインスタンス変数を一気に設定したい場合について考えてみた。

1) 最初にgdataでも使われている self.send() を使った場合

  3 class My_1
  4   attr_writer :id, :name
  5   attr_reader :name
  6   def initialize(h={})
  7     h.each{|k,v| self.send("#{k}=", v)}
  8   end
  9 end
 10 
 11 h = {:id=>'123', :name=>'abc'}
 12 p My_1.new(h)


実行結果
#<My_1:0xxxxxxxx @id="123", @name="abc">

ハッシュのキーと値がinstance 変数に設定されていることがわかる。
また attr_writer によって :id と :name 以外のkey を受け付けないよう設定されている。
これを確認する為、
h = {:id=>'123', :name=>'abc', :title=>'ddd'}
に変更し実行してみると
...(NoMethodError)

:title は attr_writer に定義されていないので instance 変数として定義できないことが確認できた。

2) 次に self[]= をつかってinstance 変数を設定するクラスの場合

 15 class My_2
 16   attr_writer :id, :name
 17   attr_reader :name
 18   def initialize(h={})
 19     h.each{|k,v| self[:"@#{k.downcase}"] = v}
 20   end
 21    alias []= instance_variable_set
 22    alias [] instance_variable_get
 23    private :[], :[]=
 24 end
 25 p My_2.new(h)


実行結果

#<My_2:0xxxxxxxx @id="123", @name="abc">

My_1 の場合と全く同じ結果を得ることができる。
では、My_1 の場合と同様に h を変更し
h = {:id=>'123', :name=>'abc', :title=>'ddd'}
として実行してみるとどうなるか?

実行結果

#<My_2:0xxxxxxxx @id="123", @name="abc", @title="ddd">


attr_writer とは無関係になんなく :title が定義されてしまった。
15行目のattr_writer :id, :name は class My_2 においては無意味のようだ。

では、あたかも attr_writer を設定したかのようにさせるにはどうすればいいのだろうか?

 14 class My_2
 15   @@ins_keys = [:id, :name]
 16   @@ins_keys.each{|x| attr_reader x}
 17   def initialize(h={})
 18     h.each{|k,v|
 19       key = k.downcase.to_sym 
 20       self[:"@#{key}"] = v if @@ins_keys.index(key)}
 21   end
 22   alias []= instance_variable_set
 23   alias [] instance_variable_get
 24   private :[], :[]=
 25 end
 26 h = {:id=>'123', :name=>'abc', :title=>'ddd'}
 27 p m = My_2.new(h)
 28 p m.name

実行結果

#<My_2:0xxxxxxxx @id="123", @name="abc">
"abc"


@@ins_keys を定義し19行目に if @@ins_keys.has_key?(k) を追加することで
attr_writer に「似た」役目をはたしてもらうことにした。
attr_reader は16行目にまとめた。
配列の要素を全て attr_reader にした。
配列の要素をチョイスして attr_reader したい場合はさらに2行加える必要あり。

@@ins_keys に登録されていない :title は定義されなくなった。
つまり定義したい instance 以外 が定義されるようなことはおこらないという意味で My_1 と同じ結果を得られるようになった。
ただし、My_1 ではerrorが返ってくるが My_2 では error にはならない。
error を返す必要があれば20行目直前に raise unless @@ins_keys.index(key) などすればいいのかも。

+++ メモ +++

0) self[] を initialize で使用しつつどの key を instance 変数として定義するかをちゃんと制御したい時
attr_writer で設定したつもりになっていてもそれは意味をなさないので、別途 式を用意するなどする必要がある。
あるいは最初から My_1 のような方法を採用した方がいいかも。
あるいは通常どおりに def initialize で @id = nil などと定義しておいて instance_variable_defined? を使う。

1) (派手に間違えているかもしれない)書きながら浮かんだイメージ
self.send()
は、一旦廊下に出て attr_writer ドアをノックしてから self 部屋に入るが、
self[]=
は、既にself 部屋の内側に入っていて attr_writer ドアの存在を意識していない。
といったようなイメージ。

2) git にあげてある 2010-amazon-api-jp に、上記の内容を反映する予定は未定。

続き

0 件のコメント: