Skip to content

Commit

Permalink
Arrange no longer drops nodes
Browse files Browse the repository at this point in the history
Sorting now keeps closer to the input values
No longer blows away original sort when no order specified
It still has trouble sorting with custom sort method when not all
parent objects are present (it can't sort something not present)
  • Loading branch information
kbrock committed Nov 1, 2018
1 parent 2cfbdb3 commit 512f457
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 31 deletions.
54 changes: 23 additions & 31 deletions lib/ancestry/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,25 @@ def orphan_strategy= orphan_strategy

# Arrangement
def arrange options = {}
# Get all nodes ordered by ancestry and start sorting them into an empty hash
arrange_nodes self.ancestry_base_class.reorder(options.delete(:order)).where(options)
# Get all nodes and start sorting them into an empty hash
scope = self.ancestry_base_class
if (order = options.delete(:order))
scope = scope.order(order)
end
arrange_nodes scope.where(options)
end

# Arrange array of nodes into a nested hash of the form
# {node => children}, where children = {} if the node has no children
# If a node's parent is not included, the node will be included as if it is a top level node
def arrange_nodes(nodes)
arranged = ActiveSupport::OrderedHash.new
nodes_ids = nodes.map(&:id)
min_depth = nodes.map(&:depth).min
index = Hash.new { |h, k| h[k] = ActiveSupport::OrderedHash.new }

nodes.each do |node|
children = index[node.id]
index[node.parent_id][node] = children
node_ids = Set.new(nodes.map(&:id))

if node.depth == min_depth || !nodes_ids.include?(node.parent_id)
arranged[node] = children
end
index = Hash.new { |h, k| h[k] = {} }
nodes.each_with_object({}) do |node, arranged|
index[node.parent_id][node] = children = index[node.id]
arranged[node] = children unless node_ids.include?(node.parent_id)
end

arranged
end

# Arrangement to nested array
Expand All @@ -67,24 +64,19 @@ def arrange_serializable options={}, nodes=nil, &block

# Pseudo-preordered array of nodes. Children will always follow parents,
# for ordering nodes within a rank provide block, eg. Node.sort_by_ancestry(Node.all) {|a, b| a.rank <=> b.rank}.
# TODO: Fix case when parents are missing and a sort is specified
def sort_by_ancestry(nodes, &block)
if nodes.is_a?(Hash)
arranged = nodes
else
presorted_nodes = nodes.sort do |a, b|
r = a.ancestor_ids <=> b.ancestor_ids
r = yield(a, b) if r == 0 && block_given?
r
if nodes.is_a?(Hash) || block_given?
nodes = arrange_nodes(nodes) unless nodes.is_a?(Hash)

nodes = nodes.sort { |(a, a_children), (b, b_children)| yield(a, b) } if block_given?
nodes.inject([]) do |sorted_nodes, (node, children)|
sorted_nodes << node
sorted_nodes += sort_by_ancestry(children, &block) unless children.blank?
sorted_nodes
end

arranged = arrange_nodes(presorted_nodes)
end

arranged.inject([]) do |sorted_nodes, pair|
node, children = pair
sorted_nodes << node
sorted_nodes += sort_by_ancestry(children, &block) unless children.blank?
sorted_nodes
else
nodes.sort { |a, b| (b.path_ids - a.path_ids).empty? ? 1 : 0 }
end
end

Expand Down
17 changes: 17 additions & 0 deletions test/concerns/arrangement_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,21 @@ def test_arrangement_nesting
assert_equal 1, model.arrange.count
end
end

def test_arrange_partial
AncestryTestDatabase.with_model do |model|
# - n1
# - n2
# - n3
# - n4
# - n5
# - n6
n1 = model.create!
n2 = model.create!(parent: n1)
n3 = model.create!(parent: n2)
n4 = model.create!(parent: n2)
n5 = model.create!(parent: n1)
assert_equal({n3 => {}, n5 => {}}, model.arrange_nodes([n5, n3]))
end
end
end
7 changes: 7 additions & 0 deletions test/concerns/sort_by_ancestry_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ def test_sort_by_ancestry_with_block
assert_equal [n1, n5, n3, n2, n4, n6].map(&:id), records.map(&:id)
# all parents, some children
assert_equal [n1, n5, n2].map(&:id), model.sort_by_ancestry([n2, n1, n5],&sort).map(&:id)
# does not work for all children no parents
# assert_equal [n5, n3, n4, n6].map(&:id), model.sort_by_ancestry([n3, n4, n5, n6],&sort).map(&:id)
# paginated mid section with missing parent, children, parent and children
# works for non ranked case (uses select order)
assert_equal [n3, n2, n4].map(&:id), model.sort_by_ancestry([n3, n2, n4]).map(&:id)
# does not work for ranked case with missing parents
# assert_equal [n3, n2, n4].map(&:id), model.sort_by_ancestry([n3, n2, n4],&sort).map(&:id)
end
end
end

0 comments on commit 512f457

Please sign in to comment.