クロージャを使うメソッドをかっこよくする

ツリー型カテゴリーの表示など,ロジックが必要な表示ルーチンで,ロジックと表示そのものの部分を分けるためにクロージャを使う話は以前に書きました。そのときのロジック側(Blogモデル)はこうなってました。

  def category_tree
      tempbuffer = ""
      level = 0
      self.categories.inject(nil) do |last_right, cat|
         
        # cat is under the last leaf
        if !last_right || (last_right > cat.cleft)
          level += 1
          tempbuffer += yield(:level_up, level)
        
        elsif (last_right + 2 <= cat.cleft)
          tempbuffer += yield(:item_close, level)
          (last_right+2..cat.cleft).each do |i|
            level -= 1
            tempbuffer += yield(:level_down, level)
          end
        else
          tempbuffer += yield(:item_close, level)
        end
        tempbuffer += yield(:item, level, cat)
        last_right = cat.cright
      end
      if (level > 1)
        tempbuffer += yield(:level_down, level)
      end
      (level..1).each do |i|
        tempbuffer += yield(:item_close, level)
      end
      tempbuffer += yield(:level_down, level)
      return tempbuffer        
  end

一方,呼び出し側(パーサ内のparse_categorylist)は

  def parse_categorylist params
    blog = params[2] ? Blog.find_by_bname(params[2]) : @controller.blog
    template = _template(params[1])
    bd = blog.basic_data
    data = Hash.new
    
    blog.category_tree do |type, level, cat|
      data['level'] = level.to_s
      case type
      when :level_up
        fill(template['CATLIST_HEADER'], bd.merge(data))
      when :level_down
        fill(template['CATLIST_FOOTER'], bd.merge(data))
      when :item
        data['catlink'] = url_for(:controller=>'categories', :catid=>cat.catid, :action=>'show')
        fill(template['CATLIST_LISTITEM'], data.merge(cat.attrs))
      when :item_close
        fill(template['CATLIST_LISTITEM_END'], bd.merge(data))
      end
    end
  end

これで実用上は不自由ないのですが,どうも呼び出し側があまりかっこよくない。case文で場合分けというのが「ださい」感じがします。yieldで呼び出されるたびにこのcase文を通るのが効率的にももう一つ。ここの部分を

    blog.category_tree do |cat|
      cat.level_up { |level| 
        data['level'] = level.to_s
        fill(template['CATLIST_HEADER'], bd.merge(data))
      }
      cat.level_down { fill(template['CATLIST_FOOTER'], bd.merge(data)) }
      cat.item { |cat|
        data['catlink'] = url_for(:controller=>'categories', :catid=>cat.catid, :action=>'show')
        fill(template['CATLIST_LISTITEM'], data.merge(cat.attrs))
      }
      cat.item_close { fill(template['CATLIST_LISTITEM_END'], bd.merge(data)) }
    end

と書けたら,かなり「Railsっぽい」感じになります。で,やってみました。

こういう形で書くためには,何かオブジェクトを返してあげて,itemとかlevel_upというメソッドがあればいいわけです。そこでBlogクラスの中に

  class Cattree
    attr_reader :up_block, :down_block, :item_block, :close_block
    
    def level_up(&b)
      @up_block = b
    end
    
    def level_down(&b)
      @down_block = b
    end
    
    def item(&b)
      @item_block = b
    end
    
    def item_close(&b)
      @close_block = b
    end
  end

とCattreeという内部クラスを作り,ここで与えられたブロックをインスタンス変数に保存しておきます。ブログのcategory_treeメソッド内で,これを呼び出せばいいわけです。

  def category_tree2(&block)
      tempbuffer = ""
      level = 0
      c = Cattree.new
      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.up_block.call(level)
        
        elsif (last_right + 2 <= cat.cleft)
          tempbuffer += c.close_block.call
          (last_right+2..cat.cleft).each do |i|
            level -= 1
            tempbuffer += c.down_block.call
          end
        else
          tempbuffer += c.close_block.call
        end
        tempbuffer += c.item_block.call(cat)
        last_right = cat.cright
      end
      
      if (level > 1)
        tempbuffer += c.down_block.call
      end
      (level..1).each do |i|
        tempbuffer += c.close_block.call
      end
      tempbuffer += c.down_block.call
      return tempbuffer
  end

ロジック部分の構成は同じですが,その前にcという名前でCattreeオブジェクトをつくり,parse_categorylistで与えられたブロックを実行しています。そして,それぞれ割り当てられたブロックを表示ロジックの中から呼んでいます。

これで動作としては,以前のものと同じになりました。

デザイン・パターンに詳しいと,こういうのもすぐに分かるんでしょうかねえ。

02 Sep, 2008 | Foodyn ( 実装メモ , スキン ) , Rails | | Andy
« Prev item - Next Item »
---------------------------------------------

Comments


No comments yet. You can be the first!


Leave comment

© 2007 yoursite.com | Designed by DesignsByDarren
Ported to Nucleus CMS: Suvoroff