1
\$\begingroup\$

I have the following scenario:

    class Product < ActiveRecord::Base
        belongs_to :categories
    end

    class Category < ActiveRecord::Base
        has_many :products
    end

Categories table has 2 level nesting, for example. Main category is 'Men', sub-category is 'Accessories' and sub-sub-category is 'Watches'. enter image description here

Now when user creates new product he must choose category(only main category is required, but he can also chose sub and sub-sub category optional).

My idea is this: I created 3 different select boxes with same name "product[category_id]", so only the last selected value will be sent to the server through params[:product][:category_id].

<div class="col-md-2 main-category">
    <small> Main category? required </small>
    <%= f.select :category_id, 
            Category.where('category_id IS ?', nil).collect {|c| [c.name, c.id]},
            { include_blank: "Select category..." }, 
            id: 'main-category', class: 'form-control' %>
</div>
<div class="col-md-2 category-level-1">
    <small> What type? optional </small>
    <%= f.select :category_id, {}, {}, class: 'form-control' %>
</div>

<div class="col-md-2 category-level-2">
    <small> What type? optional </small>
    <%= f.select :category_id, {}, {}, class: 'form-control' %>
</div>

2nd(sub-categories) and 3rd(sub-sub-categories) are initial empty ({}, {}) and hidden through CSS(display: none) and will be populated through Ajax call.

$('#main-category, .category-level-1 > select').change(function() {
    var selectBox = this.id;
    var selectedValue = $(this).val();
    var url = '/categories/' + selectedValue + '/subcategories';

    $.get(url, function(data) { createSelect(data, selectBox); });
});

function createSelect(data, selectBox) {
  var $currentSelect = null;

  if (selectBox == 'main-category') {
    $('.category-level-1').show();
    $('.category-level-2').hide();
    $('.category-level-1 > select').find('option').remove();

    $currentSelect = $('.category-level-1 > select');
  }
  else {
    $('.category-level-2').show();
    $('.category-level-2 > select').find('option').remove();

    $currentSelect = $('.category-level-2 > select');
  }

  $currentSelect.append('<option selected disabled>Select Category...</option>');
  for(var i=0; i<data.length; i++) {
    $currentSelect.append('<option value="' + data[i].id + '">' + data[i].name +
                          '</option>');
  }
}

Where Ajax call is sent to following route (categories controller)

def subcategories
    id = params[:id]
    @subcategories = Category.where('category_id = ?', id)
    render json: @subcategories 
end

So for final result i have following: enter image description here

First of all, is it normal to have different inputs in one form with same names created manually like I did in this example? For some reason, it seem like 'code-smell' to me. I'm relatively new to Rails, so I wanted to ask if I am violating some good practices with this code. Can you suggest better ways of achieving the same results?

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

These are nested selects, you can try awesome_nested_set, and use it like this:

<%= f.select :parent_id, 
             nested_set_options(Category, @category) {|i| "#{'-' * i.level} #{i.name}" } %>
\$\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.