Генерация дерева каталогов в один запрос

Извечная проблема: вывести дерево вложенных друг в друга каталог. Самый простой способ сделать это через рекурсивную функцию, в которой будет выбираться информация по каталогу. Минус такого подхода в том, что на каждый каталог будет генерироваться...

Автор . Дата: 31.10.2014

Извечная проблема: вывести дерево вложенных друг в друга каталог. Самый простой способ сделать это через рекурсивную функцию, в которой будет выбираться информация по каталогу. Минус такого подхода в том, что на каждый каталог будет генерироваться по запросу. Что не есть хорошо. Поэтому сегодня мы с вами рассмотрим способ генерации дерева каталогов в один запрос и две функции. Способ не претендует на идеальность, но позволяет значительно снизить нагрузку на СУБД. Итак.

Для начала создадим хелпер, назовем его, например, categories_helper.rb. Если вы генерировали контроллер через rails g controller, то скорее всего этот хелпер уже у вас есть. Запишем в него следующий текст:

module CategoriesHelper
  def generate_menu
    categories = Category.all
    cat_sort = []
    categories.each do |category|
      if cat_sort[category.parent_id].nil?
        cat_sort[category.parent_id] = Array.new
      end
      cat_sort[category.parent_id] << [category]
    end

    return build_menu_tree cat_sort
  end
  
  def build_menu_tree(cat_sort, parent_id = 0, result = '')
    elements = cat_sort[parent_id]
    if !elements.nil?
      result += '<ul class="nav nav-sidebar nav-stacked">'
      elements.each do |category|
        result += '<li>'
        result += link_to(category[0].title, category_path(category[0]))
        result += build_menu_tree(cat_sort, category[0].id)
        result += '</li>'
      end
      result += '</ul>'
    end
    return result
  end
end

А теперь рассмотрим код подробней. Вызывать мы будем процедуру generate_menu. В ней мы сначала выбираем полный список наших категорий. Затем начием обходить получившийся хеш. Класть каждый объект класса Category мы будем в двумерный массив cat_sort, где первичный ключ это родительский каталог нашей категории. После выполнения кода мы получим примерно такой массив:

[
    [
        [
            #<Category id: 5,
            parent_id: 0, 
            title: "Web-программирование",
        ]
    ], 
    nil, 
    nil, 
    nil, 
    nil, 
    [
        [
            #
            <Category id: 1, 
            parent_id: 5, 
            title: "1C Битрикс", 
        ], 
        [
            #<Category id: 4, 
            parent_id: 5, 
            title: "Ruby on Rails", 
        ],
        [
            #<Category id: 6, 
            parent_id: 5, 
            title: "JavaScript", 
        ]
    ]
] => nil

После генерации массива запускается процедура build_menu_tree, которая собственно и построит наш древовидный список. Единственный обязательный для неё параметр это наш специально подготовленный массив, остальные параметры необходимы для работы рекурсивного алгоритма. А дальше все просто, выбираем вложенный массив соотвествующий parent_id (при первом запуске это будет 0 - родительские каталоги). Затем начинаем обходить этот массив. Строим элемент списка и вызываем опять процедуру build_menu_tree, где на этот раз parent_id это номер текущего элемента (ведь он может быть родителем ещё для каких-то элементов). Таким образом мы проходим весь массив и получаем готовое дерево на выходе, которое можно потом вывести в вьюхе с помощью:

<%= generate_menu.html_safe %>

Вот собственно и все. Если вы планируете использовать этот код в application.html.erb, то не забудьте подключить наш хелпер в application_controller.rb:

include CategoriesHelper

Иначе подключайте его в том контроллере, где планируете использовать построение дерева.


comments powered by HyperComments