module Ancestry::ClassMethods
Public Instance Methods
arrange(options = {})
click to toggle source
Arrangement
# File lib/ancestry/class_methods.rb, line 31 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) end
arrange_nodes(nodes)
click to toggle source
Arrange array of nodes into a nested hash of the form {node => children}, where children = {} if the node has no children
# File lib/ancestry/class_methods.rb, line 38 def arrange_nodes(nodes) arranged = ActiveSupport::OrderedHash.new min_depth = Float::INFINITY index = Hash.new { |h, k| h[k] = ActiveSupport::OrderedHash.new } nodes.each do |node| children = index[node.id] index[node.parent_id][node] = children depth = node.depth if depth < min_depth min_depth = depth arranged.clear end arranged[node] = children if depth == min_depth end arranged end
arrange_serializable(options={}) { |parent, arrange_serializable(options, children, &block)| ... }
click to toggle source
Arrangement to nested array
# File lib/ancestry/class_methods.rb, line 59 def arrange_serializable options={}, nodes=nil, &block nodes = arrange(options) if nodes.nil? nodes.map do |parent, children| if block_given? yield parent, arrange_serializable(options, children, &block) else parent.serializable_hash.merge 'children' => arrange_serializable(options, children) end end end
build_ancestry_from_parent_ids!(parent_id = nil, ancestry = nil)
click to toggle source
Build ancestry from parent id's for migration purposes
# File lib/ancestry/class_methods.rb, line 176 def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil self.ancestry_base_class.unscoped do self.ancestry_base_class.where(:parent_id => parent_id).find_each do |node| node.without_ancestry_callbacks do node.update_attribute ancestry_column, ancestry end build_ancestry_from_parent_ids! node.id, if ancestry.nil? then "#{node.id}" else "#{ancestry}/#{node.id}" end end end end
check_ancestry_integrity!(options = {})
click to toggle source
Integrity checking
# File lib/ancestry/class_methods.rb, line 98 def check_ancestry_integrity! options = {} parents = {} exceptions = [] if options[:report] == :list self.ancestry_base_class.unscoped do # For each node ... self.ancestry_base_class.find_each do |node| begin # ... check validity of ancestry column if !node.valid? and !node.errors[node.class.ancestry_column].blank? raise Ancestry::AncestryIntegrityException.new("Invalid format for ancestry column of node #{node.id}: #{node.read_attribute node.ancestry_column}.") end # ... check that all ancestors exist node.ancestor_ids.each do |ancestor_id| unless exists? ancestor_id raise Ancestry::AncestryIntegrityException.new("Reference to non-existent node in node #{node.id}: #{ancestor_id}.") end end # ... check that all node parents are consistent with values observed earlier node.path_ids.zip([nil] + node.path_ids).each do |node_id, parent_id| parents[node_id] = parent_id unless parents.has_key? node_id unless parents[node_id] == parent_id raise Ancestry::AncestryIntegrityException.new("Conflicting parent id found in node #{node.id}: #{parent_id || 'nil'} for node #{node_id} while expecting #{parents[node_id] || 'nil'}") end end rescue Ancestry::AncestryIntegrityException => integrity_exception case options[:report] when :list then exceptions << integrity_exception when :echo then puts integrity_exception else raise integrity_exception end end end end exceptions if options[:report] == :list end
orphan_strategy=(orphan_strategy)
click to toggle source
Orphan strategy writer
# File lib/ancestry/class_methods.rb, line 21 def orphan_strategy= orphan_strategy # Check value of orphan strategy, only rootify, adopt, restrict or destroy is allowed if [:rootify, :adopt, :restrict, :destroy].include? orphan_strategy class_variable_set :@@orphan_strategy, orphan_strategy else raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify,:adopt, :restrict and :destroy.") end end
rebuild_depth_cache!()
click to toggle source
Rebuild depth cache if it got corrupted or if depth caching was just turned on
# File lib/ancestry/class_methods.rb, line 188 def rebuild_depth_cache! raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column self.ancestry_base_class.transaction do self.ancestry_base_class.unscoped do self.ancestry_base_class.find_each do |node| node.update_attribute depth_cache_column, node.depth end end end end
restore_ancestry_integrity!()
click to toggle source
Integrity restoration
# File lib/ancestry/class_methods.rb, line 136 def restore_ancestry_integrity! parents = {} # Wrap the whole thing in a transaction ... self.ancestry_base_class.transaction do self.ancestry_base_class.unscoped do # For each node ... self.ancestry_base_class.find_each do |node| # ... set its ancestry to nil if invalid if !node.valid? and !node.errors[node.class.ancestry_column].blank? node.without_ancestry_callbacks do node.update_attribute node.ancestry_column, nil end end # ... save parent of this node in parents array if it exists parents[node.id] = node.parent_id if exists? node.parent_id # Reset parent id in array to nil if it introduces a cycle parent = parents[node.id] until parent.nil? || parent == node.id parent = parents[parent] end parents[node.id] = nil if parent == node.id end # For each node ... self.ancestry_base_class.find_each do |node| # ... rebuild ancestry from parents array ancestry, parent = nil, parents[node.id] until parent.nil? ancestry, parent = if ancestry.nil? then parent else "#{parent}/#{ancestry}" end, parents[parent] end node.without_ancestry_callbacks do node.update_attribute node.ancestry_column, ancestry end end end end end
scope_depth(depth_options, depth)
click to toggle source
Scope on relative depth options
# File lib/ancestry/class_methods.rb, line 9 def scope_depth depth_options, depth depth_options.inject(self.ancestry_base_class) do |scope, option| scope_name, relative_depth = option if [:before_depth, :to_depth, :at_depth, :from_depth, :after_depth].include? scope_name scope.send scope_name, depth + relative_depth else raise Ancestry::AncestryException.new("Unknown depth option: #{scope_name}.") end end end
sort_by_ancestry(nodes) { |a, b| ... }
click to toggle source
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}.
# File lib/ancestry/class_methods.rb, line 72 def sort_by_ancestry(nodes, &block) arranged = nodes if nodes.is_a?(Hash) unless arranged presorted_nodes = nodes.sort do |a, b| a_cestry, b_cestry = a.ancestry || '0', b.ancestry || '0' if block_given? && a_cestry == b_cestry yield a, b else a_cestry <=> b_cestry end 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 end end
to_node(object)
click to toggle source
Fetch tree node if necessary
# File lib/ancestry/class_methods.rb, line 4 def to_node object if object.is_a?(self.ancestry_base_class) then object else find(object) end end