Categories
open all | close allTags
名称 | タグ | Subversion | パソコン | テスト | フォーム | JustPosted | 国際化 | rake | スキンエンジン | RESTful | Flash | Migration | Aptana | rubygems | 認証 | CSRF | カテゴリ | デュアル・コア | ドキュメントSearch
メタプログラミングで呼び出される側のプログラムをきれいにする
前のエントリーで書いた方法,確かに呼び出し側はきれいになるのですが,呼び出しを受ける側がちょっと気になってました。具体的に言うとCattreeというクラスを作って,そこでクロージャを保存するためのメソッド(すなわち呼び出しに使われるメソッド),保存するインスタンス変数,インスタンス変数を呼び出すためのattr_readerを書かなければいけません。カテゴリーツリーだけの話ならいいのですが,同じような形を利用できるものは現在用意しているだけでもタグクラウド,ページスイッチとあります。このとき,それぞれについて上記のCattreeのようなクラスを作るのが結構面倒です。同じようなことばかり何度も書くので全然DRY(Don't Repeat Yourself)になっていません。
そこで,この受け皿の部分をDRY化する方法を考えました。
まず,次のようなモジュールBlocksContainer(ファイル名はblocks_container.rb)を作ります。
module BlocksContainer
def method_missing(msg, *arg, &block)
varname = ('@'+msg.to_s).to_sym
if b = instance_variable_get(varname)
b.call(*arg)
elsif block
instance_variable_set(varname, block)
else
raise NameError
end
end
endここではmethod_missingというメソッドを作っていますが,これはメソッドが見つからなかったときに自動的に呼び出されるメソッドです。ここでは,そのメソッド名と同じ名前のインスタンス変数があったらそれをブロックとみなして実行し,インスタンス変数がなくて,ブロックが与えられていたらメソッド名と同じインスタンス変数を作ってブロックを保存します。そうでない場合はメソッドが見つからないものとしてNameErrorを起こします。
これを使う側は次のようになります。
def category_tree(&block)
tempbuffer = ""
level = 0
c = Object.new.extend BlocksContainer
block.call(c)
self.categories.inject(nil) do |last_right, cat|
# cat is under the last leaf
if !last_right || (last_right > cat.cleft)
level += 1
tempbuffer += c.level_up(level)
#
elsif (last_right + 2 <= cat.cleft)
tempbuffer += c.item_close
(last_right+2..cat.cleft).each do |i|
level -= 1
tempbuffer += c.level_down
end
else
tempbuffer += c.item_close
end
tempbuffer += c.item(cat)
last_right = cat.cright
end
if (level > 1)
tempbuffer += c.level_down
end
(level..1).each do |i|
tempbuffer += c.item_close
end
tempbuffer += c.level_down
return tempbuffer
end
前と違うのは
c = Object.new.extend BlocksContainer
として新しいオブジェクトに上記の機能を加えていること。
また,ブロックの呼び出しも,前は
down_block.call
といった形だったのが
level_down
だけで実行するようになります。これで,いちいち新しいクラスを作らなくても済むようになりました。
Comments
No comments yet. You can be the first!