# Transform a nested tructure indicated by dots to a structure as used by the ActiveRecord includes statement # - dslams # - ppls.distribution_cables # => # [:dslams, {ppls: :distribution_cables}] class FlatKeys class << self def as_nested_structure(ary) return ary unless ary.first.is_a?(String) nested_keys, flat_keys = ary.partition{|spec| spec['.']} result = flat_keys.map(&:to_sym) if nested_keys.any? obj = {} nested_keys.map{ |key| key.split('.').map(&:to_sym) }.each do |parts| traverse_nest_structure(parts, obj) end result -= obj.keys result.push obj end result end def traverse_nest_structure(parts, obj) key = parts.shift if parts.size == 1 case obj[key] when nil then obj[key] = parts[0] when Array then obj[key] |= [parts[0]] when Hash raise "Colliding keys for nesting of #{key} -> #{parts[0]}" unless obj[key].has_key?(parts[0]) else # expect symbol obj[key] = Array.wrap(obj[key]).push parts[0] unless obj[key] == parts[0] # no duplicate end else # parts.size > 2 case obj[key] when nil obj[key] = {} traverse_nest_structure(parts, obj[key]) when Array raise "Cannot traverse #{key} -> #{parts.join('.')} because existing array value #{obj[key].inspect}" when Hash traverse_nest_structure(parts, obj[key]) else # expect symbol raise "Cannot add deeper nesting for endpoing with different name #{key} -> #{obj[key]} => #{parts.join('.')}" unless obj[key] = parts[0] # same name, allows deeper nesting obj[key] = {obj[key] => nil} # prepare for nesting traverse_nest_structure(parts, obj[key]) end end end end end