module NestedArray::Nested class Error < StandardError end extend ActiveSupport::Concern included do |recipient| NESTED_OPTIONS ||= { # Имена полей для получения/записи информации, чувствительны к string/symbol id: 'id', parent_id: 'parent_id', children: 'children', level: 'level', # Параметры для преобразования в nested hashed: false, root_id: nil, branch_id: nil, # Параметры для преобразования в html tabulated: true, inline: false, tab: "\t", # Параматры для "склеивания" вложенных структур path_separator: '-=path_separator=-', path_key: 'text', # Настройки формирования массива для опций тега # с псевдографикой, позволяющей вывести древовидную структуру. # ``` # [['option_text1', 'option_value1'],['option_text2', 'option_value2'],…] # ``` def nested_to_options(origin_text, origin_value, options = {}) options = NESTED_OPTIONS.merge options ret = [] last = [] downhorizontal, horizontal, left, rightvertical, rightup, space, vertical = options[:thin_pseudographic] ? options[:thin_pseudographics] : options[:pseudographics] each_nested do |node, origin| last[node.level + 1] = node.is_last_children node_text = origin.send(origin_text) node_level = (1..node.level).map{|l| last[l] == true ? space : vertical}.join node_last = node.is_last_children ? rightup : rightvertical node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? downhorizontal : horizontal option_text = "#{node_level}#{node_last}#{node_children}#{left}".html_safe + "#{node_text}" option_value = origin.send(origin_value) ret.push [option_text, option_value] end ret end # Преобразует вложенную структуру данных в плоскую, но добавляет в значение # поля отвечающего за текстовое представление (:name) псевдографику # древовидной структуры. # Это позволяет вывести тэг select в сносном виде для использования с # вложенными структурами. def nested_to_collection_select(options={}) options = NESTED_OPTIONS.merge options ret = [] last = [] each_nested do |node, parents, level, is_last, origin| last[level+1] = is_last node_text = node[options[:option_text]] node_level = (1..level).map{|l| last[l] == true ? ' ' : '┃'}.join node_last = is_last ? '┗' : '┣' node_children = node[options[:children]].present? && node[options[:children]].length > 0 ? '┳' : '━' option_text = "#{node_level}#{node_last}#{node_children}╸".html_safe + "#{node_text}" option_value = node[options[:option_value]] node[options[:option_text]] = option_text ret.push node end ret end # "Скеивание" вложенных структур # ноды склеиваются если путь к ним одинаков; # путь определяется из сложения Текстов (конфигурируемо через :path_key); def concat_nested tree=nil, options={} options = NESTED_OPTIONS.merge options return self if tree.nil? children_cache = {} tree.each_nested options do |node, parents, level| parent_path_names = parents.compact.map{|e| e[options[:path_key]]} parent_path = parent_path_names.join(options[:path_separator]) path = parent_path_names.push(node[options[:path_key]]).join(options[:path_separator]) element = node if !children_cache.keys.include? path if parent_path == '' array = self else array = children_cache[parent_path] end element[options[:children]] = [] array << element children_cache[parent_path] = array children_cache[path] = element[options[:children]] end end self end end