HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyrailsMinor

Table rendering helper

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
helperrenderingtable

Problem

I wrote this little helper to render a table, however, working with nested content_tag is messy.

def semantic_table(table_headers)
     content_tag(:table, class: 'ui basic striped table') do
       (content_tag :thead do
         content_tag(:tr) do
           table_headers.each { |h| concat content_tag(:th, h) }
         end
       end).+ content_tag(:tbody) { yield }
     end
  end


For example

# users/index.html.slim
= semantic_table ['Username', 'Email', 'Registration date'] do
  = render @users


That's the only way I could make it work, without overusing concat. This code smells off, how do I refactor it?

I would also like to extract _user.html.slim partial's functionality to the helper, though I don't know how

# _user.html.slim
 tr
   td = link_to user.name, user
   td = mail_to user.email
   td = time_tag user.created_at

Solution

The answer, which may not be what you were hoping to hear, is that for generating more than a couple lines of HTML, partials are a much better fit than content_tag et al. Put your table code in a partial in e.g. views/shared/_semantic_table.slim.html:

table class="ui basic striped table"
  thead
    tr
      - headers.each do |header|
        th = header
  tbody
    = yield


...and then make your helper render it with the given arguments:

def semantic_table(headers, &block)
  render partial: "shared/semantic_table",
    locals: { headers: headers }, &block
end


Helpers are great for encapsulating logic that doesn't belong in a view/partial or in the controller, or adds a lot of noise: formatting data, choosing/combining several attributes, that sort of thing. But when you just want to spit out a bunch of boring HTML, you can't really beat a simple partial.

Edit

Re: your comment, that's interesting. I'm guessing Rails is using instance_eval somewhere, so your block with render @users is being evaluated in some different context where @users is nil. This is surprising, but I don't have a computer in front of me to dig further.

There may be a workaround that I'm not thinking of, but barring that, the simplest solution is to drop the helper entirely and just use the partial directly in your view:

= render partial: "shared/semantic_table", locals: { headers: headers } do
  = render @users


I'm not 100% sure that'll work, since didn't anticipate the other issue, but I think it will.

Another solution is to get rid of the block and pass in the objects directly:

def semantic_table(headers, objects)
  render partial: "shared/semantic_table",
    locals: { headers: headers, objects: objects }
end


...and then in the partial call render objects instead of yield.

Of course, this is more constraining than a block, but it may suffice for your use case.

Code Snippets

table class="ui basic striped table"
  thead
    tr
      - headers.each do |header|
        th = header
  tbody
    = yield
def semantic_table(headers, &block)
  render partial: "shared/semantic_table",
    locals: { headers: headers }, &block
end
= render partial: "shared/semantic_table", locals: { headers: headers } do
  = render @users
def semantic_table(headers, objects)
  render partial: "shared/semantic_table",
    locals: { headers: headers, objects: objects }
end

Context

StackExchange Code Review Q#108693, answer score: 3

Revisions (0)

No revisions yet.