RailsのSlim-Templateで木構造をやっていく。

追記 結果が正確になっていなかったと教えていただいたので後日修正します。 追記2 書き直しました。ついでに木になってなかったのも直しました。ちなみにコメント欄で @jnchito さんに教えてもらったpartialを使う方法が目からウロコでかなりよかったです。

木構造はプログラミングで避けて通れないデータ構造です。最近木構造づいてます。

しかし描画となると、いろいろなアレがあります。

captureを使ったりしますが、使わなかったりもします。

データ

@tree =
  {
    title: :a,
    children: [
      {
        title: :a_a
      },
      {
        title: :a_c,
        children: [
          {
            title: :a_c_a
          },
          {
            title: :a_c_b,
            children: [
              {
                title: :a_c_b_a
              }
            ]
          },
          {
            title: :a_c_c
          }
        ]
      },
      {
        title: :a_a
      }
    ]
  }

できあがり

1. a
    1. a_a
    2. a_c
        1. a_c_a
        2. a_c_b
            1. a_c_b_a
        3. a_c_c
    3. a_a

素朴にslimで書く

ol
  li
    = @tree[:title]
    - if @tree[:children]
      ol
        - @tree[:children].each do |c2|
          li
            = c2[:title]
            - if c2[:children]
              ol
                - c2[:children].each do |c3|
                  li
                    = c3[:title]
                    - if c3[:children]
                      ol
                        - c3[:children].each do |c4|
                          li= c4[:title]

つらいですね。

これを書くために何回かインデント位置を間違えたし、深まるとどうにもなりません。

だったらHelper

views描画の支援メソッドを書くために、hoge_helperというのが用意されています。

# helper側
def write_tree(tree)
  return unless tree

  children = (tree[:children] || []).map { |child|
    write_tree(child)
  }.join.html_safe

  "<li>#{tree[:title]}<ol>#{children}</ol></li>".html_safe
end
# slim側
ol
  = write_tree(@tree)

書けましたし深さも関係ありませんが、せっかくslimを使っているのに、ベタなHTMLやtag_helperの類は書きたくありません。

まぁcaptureなんですけども

みなさんご存知の通り、helperに書かれたメソッドは、テンプレート側で&blockに渡されたテンプレートを描画できます。

ところで、capture&block以外にも引数を取れるんです。

# helper側
def write_capture_tree(tree, proc = nil, &block)
  return unless tree

  pass = block ? block : proc
  capture(tree[:title], tree[:children], pass, &pass)
end
# slim側
ol
  = write_capture_tree(@tree) do |title, children, proc|
    li
      = title
      - if children
        ol
          - children.each do |child|
            = write_capture_tree(child, proc)

この場合では、chilDpassで、contentprocslim側に渡されるやつです。

ブロック内で呼ばれたhelperのメソッドに、再度slimが入ったブロックと、次の子供をわたすことによって、HTMLはslimで書いて、木構造を描画できることとなりました。

captureの処理自体に&blockを使ってしまうため、あらためてblock的なものを渡すためにpass変数が必要になりました。

木構造が描画できて嬉しいですが、一抹の悲しさがあります。

slimかつcaptureを使わない場合

消しました。