startupsmith
startupsmith

Reputation: 5764

simple_form many to many association as tree?

I am trying to build a UI for updating a model ("Profile") that has a many to many relationship with another model ("Category"). The "Category" model has a self referential relationship with itself for "Sub Categories".

In my simple_form I want to display the categories as checkboxes with their sub categories nested below them as check boxes.

In my current code all I have is this for the association field:

= f.association :categories, as: :check_boxes, collection: @categories

I am just retrieving the top level categories into the variable @categories.

I'm not sure where to go from here. What is the best way to do this?

Upvotes: 1

Views: 1347

Answers (1)

Richard Peck
Richard Peck

Reputation: 76774

Ancestry

We've done this before using the ancestry gem:

enter image description here

enter image description here

The problem you have is your self-referential association won't give you the scope required to create a "real" nested dropdown; it has to be able to consider all the nested data.

Instead, what we did was to firstly employee the ancestry gem, and then use a partial and helper to get the nested dropdown affect:

#app/models/category.rb
Class Category < ActiveRecord::Base
   has_ancestry
end

enter image description here


Display

If you store the dependent data like that, it allows you to create a partial-based nested effect:

#app/views/admin/categories/index.html.erb
<%= render partial: "category", locals: { collection: collection } %>


#app/views/categories/_category.html.erb
<!-- Categories -->
<ol class="categories">
    <% collection.arrange.each do |category, sub_item| %>
        <li>
            <!-- Category -->
            <div class="category">
                <%= link_to category.title, edit_admin_category_path(category) %>
                <%= link_to "+", admin_category_new_path(category), title: "New Categorgy", data: {placement: "bottom"} %>
            </div>

            <!-- Children -->
            <% if category.has_children? %>
                <%= render partial: "category", locals: { collection: category.children } %>
            <% end %>

        </li>
    <% end %>
</ol>

Dropdown

#app/helpers/application_helper.rb
Class ApplicationHelper
    def nested_dropdown(items)
        result = []
        items.map do |item, sub_items|
            result << [('- ' * item.depth) + item.name, item.id]
            result += nested_dropdown(sub_items) unless sub_items.blank?
        end
        result
    end
end

This will allow you to call:

= f.input :categories, as: :select, collection: nested_dropdown(@categories)

Upvotes: 2

Related Questions