2
\$\begingroup\$

I have categories and subcategories in my view. The code looks like this

<ul>
  <% Category.roots.each do |category| %>
  <li class="col-md-2">
    <a href="#"><%= category.name %></a>
    <% if !category.children.empty? %>
    <ul>
      <%= category.decorate.draw_tree %>
    </ul>
    <% end %>
  </li>
  <% end %>
</ul>

I'm basically using a decorator to make it recursive

class CategoryDecorator < Draper::Decorator

  delegate_all
  decorates :category

  def draw_tree

    children.inject("") do |memo, child|

      memo << "<li class=\"dir\"><a href=\"#\">#{child.name}</a>"
      memo << "<ul>#{child.decorate.draw_tree}</ul>" if not child.children.empty?
      memo << "</li>"

    end.html_safe

  end


end

It works fine. But I'm writing HTML inside a Ruby class and I don't like the idea, I'de like to keep it in the view. I tried to play with &block to turn it like following

<ul>
  <% Category.roots.each do |category| %>
  <li class="col-md-2">
    <a href="#"><%= category.name %></a>
    <% if !category.children.empty? %>
    <ul>
      <% category.decorate.draw_tree do |child| %>

      <li class="dir"><a href="#"><%= child.name %></a>
      <% if child.children.any %>
        <ul><%= child.decorate.draw_tree %></ul>"
      <% end %>
      </li>"

      <% end %>
    </ul>
    <% end %>
  </li>
  <% end %>
</ul>

Sadly, I couldn't make it work so far. I think I lack methodology here. How can I rewrite this code ?

\$\endgroup\$
2
\$\begingroup\$

Here's how I'd do it:

In your view, simply call a recursive method on each category:

<ul>
  <% Category.roots.map do |category| %>
     <%= draw_tree(category) %>
  <% end %>
</ul>

And now in a helper file (app/helpers/category_helper.rb?), this is the "right" place to put HTML helpers. First, in pseudo-code:

def draw_tree(category)
  <li class="col-md-2">
    <a href="#"><%= category.name %></a>
    <% category.children.each do |child| %>
    <ul>
      <%= draw_tree(child) %>
    </ul>
    <% end %>
  </li>
end

Now, to actually make this work, rather than writing a string and calling #html_safe, a much cleaner (and generally safer) approach is to use content_tag. The end result is:

def draw_tree(category)
  content_tag(:li, class: "col-md-2") do
    link_to('#', category.name)
    category.children.map do |child|
      content_tag(:ul, draw_tree(child))
    end
  end
end

I haven't actually run the above code, so apologies if there's some small mistake. But hopefully you get the idea :)

\$\endgroup\$
0
\$\begingroup\$

Working solution

The correct answer to my own question was the method content = capture { yield } which allows you to get the whole block content, so you can manipulate it entirely somewhere else and use recursivity.

I saw the answer in some random code after giving up to organize my code this way, a while ago.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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