Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 127 lines (119 sloc) 5.273 kb
ecea76f @stefankroes Version 1.2.0
authored
1 module Ancestry
2 module ClassMethods
3 # Fetch tree node if necessary
4 def to_node object
5 if object.is_a?(self.base_class) then object else find(object) end
6 end
7
8 # Scope on relative depth options
9 def scope_depth depth_options, depth
10 depth_options.inject(self.base_class) do |scope, option|
11 scope_name, relative_depth = option
12 if [:before_depth, :to_depth, :at_depth, :from_depth, :after_depth].include? scope_name
13 scope.send scope_name, depth + relative_depth
14 else
15 raise Ancestry::AncestryException.new("Unknown depth option: #{scope_name}.")
16 end
17 end
18 end
19
20 # Orphan strategy writer
21 def orphan_strategy= orphan_strategy
22 # Check value of orphan strategy, only rootify, restrict or destroy is allowed
23 if [:rootify, :restrict, :destroy].include? orphan_strategy
24 class_variable_set :@@orphan_strategy, orphan_strategy
25 else
26 raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify, :restrict and :destroy.")
27 end
28 end
29
30 # Arrangement
31 def arrange options = {}
32 scope =
33 if options[:order].nil?
34 self.base_class.ordered_by_ancestry
35 else
36 self.base_class.ordered_by_ancestry_and options.delete(:order)
37 end
38 # Get all nodes ordered by ancestry and start sorting them into an empty hash
39 scope.all(options).inject({}) do |arranged_nodes, node|
40 # Find the insertion point for that node by going through its ancestors
41 node.ancestor_ids.inject(arranged_nodes) do |insertion_point, ancestor_id|
42 insertion_point.each do |parent, children|
43 # Change the insertion point to children if node is a descendant of this parent
44 insertion_point = children if ancestor_id == parent.id
45 end; insertion_point
46 end[node] = {}; arranged_nodes
47 end
48 end
49
50 # Integrity checking
51 def check_ancestry_integrity!
52 parents = {}
53 # For each node ...
54 self.base_class.all.each do |node|
55 # ... check validity of ancestry column
56 if !node.valid? and node.errors.invalid?(node.class.ancestry_column)
57 raise Ancestry::AncestryIntegrityException.new("Invalid format for ancestry column of node #{node.id}: #{node.read_attribute node.ancestry_column}.")
58 end
59 # ... check that all ancestors exist
60 node.ancestor_ids.each do |ancestor_id|
61 unless exists? ancestor_id
62 raise Ancestry::AncestryIntegrityException.new("Reference to non-existent node in node #{node.id}: #{ancestor_id}.")
63 end
64 end
65 # ... check that all node parents are consistent with values observed earlier
66 node.path_ids.zip([nil] + node.path_ids).each do |node_id, parent_id|
67 parents[node_id] = parent_id unless parents.has_key? node_id
68 unless parents[node_id] == parent_id
69 raise Ancestry::AncestryIntegrityException.new("Conflicting parent id in node #{node.id}: #{parent_id || 'nil'} for node #{node_id}, expecting #{parents[node_id] || 'nil'}")
70 end
71 end
72 end
73 end
74
75 # Integrity restoration
76 def restore_ancestry_integrity!
77 parents = {}
78 # For each node ...
79 self.base_class.all.each do |node|
80 # ... set its ancestry to nil if invalid
81 if node.errors.invalid? node.class.ancestry_column
82 node.without_ancestry_callbacks do
83 node.update_attributes :ancestry => nil
84 end
85 end
86 # ... save parent of this node in parents array if it exists
87 parents[node.id] = node.parent_id if exists? node.parent_id
88
89 # Reset parent id in array to nil if it introduces a cycle
90 parent = parents[node.id]
91 until parent.nil? || parent == node.id
92 parent = parents[parent]
93 end
94 parents[node.id] = nil if parent == node.id
95 end
96 # For each node ...
97 self.base_class.all.each do |node|
98 # ... rebuild ancestry from parents array
99 ancestry, parent = nil, parents[node.id]
100 until parent.nil?
101 ancestry, parent = if ancestry.nil? then parent else "#{parent}/#{ancestry}" end, parents[parent]
102 end
103 node.without_ancestry_callbacks do
104 node.update_attributes node.ancestry_column => ancestry
105 end
106 end
107 end
108
109 # Build ancestry from parent id's for migration purposes
110 def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
111 self.base_class.all(:conditions => {:parent_id => parent_id}).each do |node|
112 node.without_ancestry_callbacks do
113 node.update_attribute ancestry_column, ancestry
114 end
115 build_ancestry_from_parent_ids! node.id, if ancestry.nil? then "#{node.id}" else "#{ancestry}/#{node.id}" end
116 end
117 end
118
119 # Rebuild depth cache if it got corrupted or if depth caching was just turned on
120 def rebuild_depth_cache!
121 raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
122 self.base_class.all.each do |node|
123 node.update_attribute depth_cache_column, node.depth
124 end
125 end
126 end
127 end
Something went wrong with that request. Please try again.