True Soft
True Soft

Reputation: 8796

Using multiple content_tag in one method in Rails

I am trying to update a Rails 2.3 application to a newer Rails version(4/5).
I have there a method that prints a html table using a list as input, and the caller can customize the display of the rows. I also searched some existing gems that do something similar, but they don't have all the requirements I need. So I have to make this work. The code is

  def model_table_2(collection, headers, options = {}, &proc)
    options.reverse_merge!({
        :id           => nil,
        :class        => nil,
        :style        => nil,
        :placeholder  => 'Empty',
        :caption      => nil,
        :summary      => nil,
        :footer       => nil
      })
    placeholder_unless !collection.empty?, options[:placeholder] do
      html_opt = options.slice(:id, :class, :style, :summary)
      content_tag(:table, html_opt) do
        table_sections = []
        table_sections << content_tag(:caption, options[:caption]).to_s if options[:caption]
        table_sections << content_tag(:thead, 
          content_tag(:tr,
              headers.collect { |h| 
                concat(content_tag(:th, h))
              }
            )
        )
        if options[:footer]
          table_sections << content_tag(:tfoot,
            content_tag(:tr, content_tag(:th, concat(options[:footer]), :colspan => headers.size)) 
          )
        end
        table_sections << content_tag(:tbody, 
          collection.each_with_index.collect do |row, row_index|
            concat(
              proc.call(row, cycle('odd', 'even'), row_index)
            )
          end.join
        )
        table_sections.join
      end
    end
  end

  def placeholder(message = t('general.empty'), options = {}, &proc)
    # set default options
    o = { :class => 'placeholder', :tag => 'p' }.merge(options)

    # wrap the results of the supplied block, or just print out the message
    if proc
      t = o.delete(:tag)
      concat tag(t, o, true), proc.binding
      yield
      concat "</#{t}>", proc.binding
    else
      content_tag o.delete(:tag), message, o
    end
  end

  def placeholder_unless(condition, *args, &proc)
    condition ? proc.call : concat(placeholder(args), proc.binding)
  end

In the view file I call it like this:

<% table_cols = ["No.", "Name"] %>
<% obj_list = [{active: true, name: 'First'}, {active: true, name: 'Second'}, {active: false, name: 'Last'}, nil] %>
<%= model_table_2(obj_list, table_cols, {:class=>'table table-bordered', :caption=>'Model Table Test', :footer=>'The Footer'}) do |record, klass, row_index| -%>
  <% if !record.nil? then %>
    <% content_tag :tr, :class => klass + (record[:active] ? '' : ' text-muted') do -%>
        <td><%= row_index+1 -%></td>
        <td><%= record[:name] %></td>
    <% end %>
  <% else %>
    <% content_tag :tr, :class => klass do -%>
        <td style="text-align:center;">*</td>
        <td>render form</td>
    <% end %>
  <% end %>
<% end %>

But the output is not how I would expect:

<table class="table table-bordered">
   <th>No.</th>
   <th>Name</th>
   The Footer
   <tr class="even">
      <td>1</td>
      <td>First</td>
   </tr>
   <tr class="odd">
      <td>2</td>
      <td>Second</td>
   </tr>
   <tr class="even text-muted">
      <td>3</td>
      <td>Last</td>
   </tr>
   <tr class="odd">
      <td>*</td>
      <td>render form</td>
   </tr>
</table>

As you can see, some of the tags are missing, like caption, thead, tbody, tfoot. I guess it's because the content_tag calls are nested. I tried before without the table_sections array, but it didn't work either.

Also, I have an error when the list is empty, and the code goes to the placeholder... methods.

Upvotes: 1

Views: 2718

Answers (1)

engineerDave
engineerDave

Reputation: 3935

It's a weird quirk of content_tag but if you nest them you need to use a concat on each return in the inner tags. Otherwise, you just get the last returned string and the inner tags just disappear into the ether. Sadly, in my experience, I've found complex nesting isn't worth the effort of moving into a helper method.

Perhaps, a better approach would be to DRY up the html with a decorator pattern, rails partials, or using something like the cells gem.

Upvotes: 3

Related Questions