Rails には content_tag という便利なメソッドがあります。これを使うと、view がとてもすっきりと書けるんです。まずは具体例をどうぞ。

例えば view にこのような記述をすると、コメントのような HTML が出力されます。<%= ... %> のようにインライン形式で使うときには第一引数がタグ、第二引数が内容、第三引数がオプション( class とか style とか)ですが、<% ... do -%> <% ... -%> のようにブロック形式を取るときには第一引数がタグ、第二引数がオプション、第三引数がブロックになるので注意してください〜。

# view
<%= content_tag(:h1, 'ふー', :style => 'color:red') %>
  # <h1 style="color:red">ふー</h1>
 
<% content_tag(:div, :class => 'bar') do -%>
<div id="bar">ばー</div>
<% end -%>
  # <div class="bar"><div id="bar">ばー</div></div>


簡単ですね!これを利用して、tag_helper というものを作ってみました。

# app/helpers/application_helper.rb
module ApplicationHelper
  def tag_helper(options = {}, &block)
    options = { 
      :style => 'margin:0px'
    }.merge(options)
    
    tag_type = options.delete(:tag_type) || :div
    content_tag(tag_type, options, &block)         
  end                                              
end 

# view
<% tag_helper(:style => 'width:500px') do -%>
<span>ほげ</span>
<% end -%>
  # <div style="width:500px"><span>ほげ</span></div>
 
<% tag_helper(:tag_type => :span, :align => 'center') do -%>
ふが
<% end -%>
  # <span align="center" style="margin:0px">ふが</span> 
 

この例のように、一度しか書かないのであればあまりメリットは無いですが、同じような内容を何度も書くような場合には、helper を定義してあげるととてもすっきりして見やすいです。DRYですし。

参考までに content_tag メソッドの定義はこんな感じです。ブロックを受け取ることを想定しています。

# /usr/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_view/helpers/tag_helper.rb
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
  if block_given?
    options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
    content_tag = content_tag_string(name, capture(&block), options, escape)

    if block_called_from_erb?(block)
      concat(content_tag)
    else
      content_tag
    end
  else
    content_tag_string(name, content_or_options_with_block, options, escape)
  end
end

private
  BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'

  if RUBY_VERSION < '1.9.0'
    def block_called_from_erb?(block)
      block && eval(BLOCK_CALLED_FROM_ERB, block)
    end
  else
    def block_called_from_erb?(block)
      block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
    end
  end

  def content_tag_string(name, content, options, escape = true)
    tag_options = tag_options(options, escape) if options
    "<#{name}#{tag_options}>#{content}"
  end

  def tag_options(options, escape = true)
    unless options.blank?
      attrs = []
      if escape
        options.each_pair do |key, value|
          if BOOLEAN_ATTRIBUTES.include?(key)
            attrs << %(#{key}="#{key}") if value
          else
            attrs << %(#{key}="#{escape_once(value)}") if !value.nil?
          end
        end
      else
        attrs = options.map { |key, value| %(#{key}="#{value}") }
      end
      " #{attrs.sort * ' '}" unless attrs.empty?
    end
  end

ブロック形式のときにはそのままでは出力されないため、capture と concat をする必要があります。capture はブロックを評価して文字列として返します。また、concat すると output_buffer に引数の内容が書き込まれていき、最終的には output_buffer の内容が出力されるようです。

さらに、content_tag メソッドでは capture メソッドに引数を渡すことはできませんが、自分で helper を定義してあげればこのようにブロック内に引数を渡すことも可能です。

# view
<% output_now do |time| -%>
現在時刻:<%= h(time) -%>
<% end -%>
  # 現在時刻:Sun Aug 30 02:48:44 +0900 2009

# app/helpers/application_helper.rb
def output_now(&block)
  concat( capture(Time.now, &block) ) # Time.now がブロックに渡される
end   
このエントリーをはてなブックマークに追加