the-teacher/the_sortable_tree

View on GitHub
app/helpers/the_sortable_tree_helper.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module TheSortableTreeHelper
  # Publicated by MIT
  # Nested Set View Helper

  # Ilya Zykin, zykin-ilya@ya.ru, Russia [Ivanovo, Saint Petersburg] 2009-2014
  # github.com/the-teacher

  # Default renderers
  TREE_RENDERERS = {
    :tree           => RenderTreeHelper,
    :sortable       => RenderSortableTreeHelper,
    :expandable     => RenderExpandableTreeHelper,
    :nested_options => RenderNestedOptionsHelper
  }

  ###############################################
  # Common Base Methods
  ###############################################

  def define_class_of_elements_of tree
    case
      when tree.is_a?(ActiveRecord::Relation) then tree.name.to_s.underscore.downcase
      when tree.empty?                        then nil
      else tree.first.class.to_s.underscore.downcase
    end
  end

  def build_tree_html context, render_module, options = {}
    render_module::Render::render_node(self, options)
  end

  ###############################################
  # Shortcuts
  ###############################################

  def just_tree tree, options = {}
    build_server_tree(tree, { :type => :tree }.merge!(options))
  end

  def sortable_tree tree, options = {}
    build_server_tree(tree, { :type => :sortable }.merge!(options))
  end

  def nested_options tree, options = {}
    build_server_tree(tree, { :type => :nested_options }.merge!(options))
  end

  def expandable_tree tree, options = {}
    build_server_tree(tree, { :type => :expandable }.merge!(options))
  end

  ###############################################
  # Server Side Render Tree Helper
  ###############################################

  def build_server_tree(tree, options= {})
    result = ''
    tree   = Array.wrap tree
    opts   = {
      # node and base node params
      :id    => :id,      # node id field
      :title => :title,   # name of title fileld
      :node  => nil,      # node

      # base options
      :type  => :tree,    # tree type
      :root  => false,    # is it root node?
      :level => 0,        # recursion level
      :namespace => [],   # :admin

      # BOOST! hash
      :boost => {}
    }.merge!(options)

    # Basic vars
    root = opts[:root]
    node = opts[:node]

    # namespace prepare [Rails require]
    opts[:namespace] = Array.wrap opts[:namespace]

    # Module with **Render** class
    opts[:render_module] = TREE_RENDERERS[opts[:type]] unless opts[:render_module]

    # Define tree class
    opts[:klass] = define_class_of_elements_of(tree) unless opts[:klass]

    # BOOST PATCH (BUILD ONCE)
    # solution of main perfomance problem
    # magick index-hash, which made me happy!

    # Performance comparison
    # Boost OFF: 8000 nodes, 3 levels => (Views: 176281.9ms | ActiveRecord: 189.8ms)
    # Boost  ON: 8000 nodes, 3 levels => (Views: 8987.8ms   | ActiveRecord: 141.6ms)

    if opts[:boost].empty?
      tree.each do |item|
        num = item.parent_id || 0
        opts[:boost][num.to_s] = [] unless opts[:boost][num.to_s]
        opts[:boost][num.to_s].push item
      end
    end

    unless node
      # RENDER ROOTS
      roots = opts[:boost]['0']

      # Boost OFF
      # roots = tree.select{ |node| node.parent_id.blank? }

      # define roots, if it's need
      if roots.nil? && !tree.empty?
        # Looks like the new algo really does what we need (28 sept. 2016)
        # Thanks to: https://github.com/patrick-nits
        #
        ids = tree.map(&:id)
        opt_ids = opts[:boost].keys.map(&:to_i)
        candidate_ids = (ids + opt_ids).uniq - (ids & opt_ids) # xor
        roots = candidate_ids.map {|c| opts[:boost][c.to_s]}.compact.flatten
      end

      # children rendering
      if roots
        roots.each do |root|
          _opts  =  opts.merge({ :node => root, :root => true, :level => opts[:level].next, :boost => opts[:boost] })
          result << build_server_tree(tree, _opts)
        end
      end
    else
      # RENDER NODE'S CHILDREN
      children_res = ''
      children = opts[:boost][node.id.to_s]

      # Boost OFF
      # children = tree.select{ |_node| _node.parent_id == node.id }

      opts.merge!({ :has_children => children.blank? })

      unless children.nil?
        children.each do |elem|
          _opts        =  opts.merge({ :node => elem, :root => false, :level => opts[:level].next, :boost => opts[:boost] })
          children_res << build_server_tree(tree, _opts)
        end
      end

      result << build_tree_html(self, opts[:render_module], opts.merge({ :root => root, :node => node, :children => children_res }))
    end

    raw result
  end
end