diff --git a/README.rdoc b/README.rdoc index cba5dc17..907e1f90 100644 --- a/README.rdoc +++ b/README.rdoc @@ -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: diff --git a/lib/ancestry/class_methods.rb b/lib/ancestry/class_methods.rb index 0887d341..c7184ef1 100644 --- a/lib/ancestry/class_methods.rb +++ b/lib/ancestry/class_methods.rb @@ -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 @@ -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? @@ -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 @@ -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? @@ -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 @@ -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 \ No newline at end of file +end diff --git a/test/has_ancestry_test.rb b/test/has_ancestry_test.rb index 7fe6687e..dca1d90c 100644 --- a/test/has_ancestry_test.rb +++ b/test/has_ancestry_test.rb @@ -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