Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/kueda/ancestry into kueda…
Browse files Browse the repository at this point in the history
…-master
  • Loading branch information
Stefan Kroes committed Apr 22, 2011
2 parents aee269e + adb9acd commit 7514d99
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 9 deletions.
8 changes: 8 additions & 0 deletions README.rdoc
Expand Up @@ -156,6 +156,14 @@ The arrange method takes ActiveRecord find options. If you want your hashes to b

TreeNode.find_by_name('Crunchy').subtree.arrange(:order => :name)

= Sorting

If you just want to sort an array of nodes as if you were traversing them in preorder, you can use the sort_by_ancestry class method:

TreeNode.sort_by_ancestry(array_of_nodes)

Note that since materialised path trees don't support ordering within a rank, the order of siblings depends on their order in the original array.

= Migrating from plugin that uses parent_id column

Most current tree plugins use a parent_id column (has_ancestry, awesome_nested_set, better_nested_set, acts_as_nested_set). With ancestry its easy to migrate from any of these plugins, to do so, use the build_ancestry_from_parent_ids! method on your ancestry model. These steps provide a more detailed explanation:
Expand Down
40 changes: 31 additions & 9 deletions lib/ancestry/class_methods.rb
Expand Up @@ -36,14 +36,36 @@ def arrange options = {}
self.base_class.ordered_by_ancestry_and options.delete(:order)
end
# Get all nodes ordered by ancestry and start sorting them into an empty hash
scope.all(options).inject(ActiveSupport::OrderedHash.new) do |arranged_nodes, node|
arrange_nodes scope.all(options)
end

# Arrange array of nodes into a nested hash of the form
# {node => children}, where children = {} if the node has no children
def arrange_nodes(nodes)
# Get all nodes ordered by ancestry and start sorting them into an empty hash
nodes.inject(ActiveSupport::OrderedHash.new) do |arranged_nodes, node|
# Find the insertion point for that node by going through its ancestors
node.ancestor_ids.inject(arranged_nodes) do |insertion_point, ancestor_id|
insertion_point.each do |parent, children|
# Change the insertion point to children if node is a descendant of this parent
insertion_point = children if ancestor_id == parent.id
end; insertion_point
end[node] = ActiveSupport::OrderedHash.new; arranged_nodes
end
insertion_point
end[node] = ActiveSupport::OrderedHash.new
arranged_nodes
end
end

# Pseudo-preordered array of nodes. Children will always follow parents,
# but the ordering of nodes within a rank depends on their order in the
# array that gets passed in
def sort_by_ancestry(nodes)
arranged = nodes.is_a?(Hash) ? nodes : arrange_nodes(nodes.sort_by{|n| n.ancestry || '0'})
arranged.inject([]) do |sorted_nodes, pair|
node, children = pair
sorted_nodes << node
sorted_nodes += sort_by_ancestry(children) unless children.blank?
sorted_nodes
end
end

Expand All @@ -52,7 +74,7 @@ def check_ancestry_integrity! options = {}
parents = {}
exceptions = [] if options[:report] == :list
# For each node ...
self.base_class.all.each do |node|
self.base_class.find_each do |node|
begin
# ... check validity of ancestry column
if !node.valid? and !node.errors[node.class.ancestry_column].blank?
Expand Down Expand Up @@ -86,7 +108,7 @@ def check_ancestry_integrity! options = {}
def restore_ancestry_integrity!
parents = {}
# For each node ...
self.base_class.all.each do |node|
self.base_class.find_each do |node|
# ... set its ancestry to nil if invalid
if node.errors[node.class.ancestry_column].blank?
node.without_ancestry_callbacks do
Expand All @@ -104,7 +126,7 @@ def restore_ancestry_integrity!
parents[node.id] = nil if parent == node.id
end
# For each node ...
self.base_class.all.each do |node|
self.base_class.find_each do |node|
# ... rebuild ancestry from parents array
ancestry, parent = nil, parents[node.id]
until parent.nil?
Expand All @@ -118,7 +140,7 @@ def restore_ancestry_integrity!

# Build ancestry from parent id's for migration purposes
def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
self.base_class.all(:conditions => {:parent_id => parent_id}).each do |node|
self.base_class.find_each(:conditions => {:parent_id => parent_id}) do |node|
node.without_ancestry_callbacks do
node.update_attribute ancestry_column, ancestry
end
Expand All @@ -129,9 +151,9 @@ def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
# Rebuild depth cache if it got corrupted or if depth caching was just turned on
def rebuild_depth_cache!
raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
self.base_class.all.each do |node|
self.base_class.find_each do |node|
node.update_attribute depth_cache_column, node.depth
end
end
end
end
end
13 changes: 13 additions & 0 deletions test/has_ancestry_test.rb
Expand Up @@ -688,4 +688,17 @@ def test_arrange_order_option
end
end
end

def test_sort_by_ancestry
AncestryTestDatabase.with_model do |model|
n1 = model.create!
n2 = model.create!(:parent => n1)
n3 = model.create!(:parent => n2)
n4 = model.create!(:parent => n2)
n5 = model.create!(:parent => n1)

arranged = model.sort_by_ancestry(model.all.sort_by(&:id).reverse)
assert_equal [n1, n2, n4, n3, n5].map(&:id), arranged.map(&:id)
end
end
end

0 comments on commit 7514d99

Please sign in to comment.