3
\$\begingroup\$

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
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much for your help! That's what I needed. \$\endgroup\$
    – just so
    Oct 25, 2015 at 20:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.