Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 174 lines (160 sloc) 7.177 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
94dba1c -- renamed the orphan strategy
unknown authored
22 # Check value of orphan strategy, only rootify, adopt, restrict or destroy is allowed
23 if [:rootify, :adopt, :restrict, :destroy].include? orphan_strategy
ecea76f @stefankroes Version 1.2.0
authored
24 class_variable_set :@@orphan_strategy, orphan_strategy
25 else
94dba1c -- renamed the orphan strategy
unknown authored
26 raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify,:adopt, :restrict and :destroy.")
ecea76f @stefankroes Version 1.2.0
authored
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
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
39 arrange_nodes scope.all(options)
40 end
41
42 # Arrange array of nodes into a nested hash of the form
43 # {node => children}, where children = {} if the node has no children
44 def arrange_nodes(nodes)
45 # Get all nodes ordered by ancestry and start sorting them into an empty hash
46 nodes.inject(ActiveSupport::OrderedHash.new) do |arranged_nodes, node|
ecea76f @stefankroes Version 1.2.0
authored
47 # Find the insertion point for that node by going through its ancestors
48 node.ancestor_ids.inject(arranged_nodes) do |insertion_point, ancestor_id|
49 insertion_point.each do |parent, children|
50 # Change the insertion point to children if node is a descendant of this parent
51 insertion_point = children if ancestor_id == parent.id
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
52 end
53 insertion_point
54 end[node] = ActiveSupport::OrderedHash.new
55 arranged_nodes
56 end
57 end
58
59 # Pseudo-preordered array of nodes. Children will always follow parents,
60 # but the ordering of nodes within a rank depends on their order in the
61 # array that gets passed in
62 def sort_by_ancestry(nodes)
63 arranged = nodes.is_a?(Hash) ? nodes : arrange_nodes(nodes.sort_by{|n| n.ancestry || '0'})
64 arranged.inject([]) do |sorted_nodes, pair|
65 node, children = pair
66 sorted_nodes << node
67 sorted_nodes += sort_by_ancestry(children) unless children.blank?
68 sorted_nodes
ecea76f @stefankroes Version 1.2.0
authored
69 end
70 end
71
72 # Integrity checking
34a13a9 @stefankroes Fixes for 1.2.1
authored
73 def check_ancestry_integrity! options = {}
ecea76f @stefankroes Version 1.2.0
authored
74 parents = {}
34a13a9 @stefankroes Fixes for 1.2.1
authored
75 exceptions = [] if options[:report] == :list
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
76
6d96228 Removed backwards compatibility workarounds
Stefan Henzen authored
77 self.base_class.unscoped do
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
78 # For each node ...
79 self.base_class.find_each do |node|
80 begin
81 # ... check validity of ancestry column
82 if !node.valid? and !node.errors[node.class.ancestry_column].blank?
83 raise Ancestry::AncestryIntegrityException.new("Invalid format for ancestry column of node #{node.id}: #{node.read_attribute node.ancestry_column}.")
34a13a9 @stefankroes Fixes for 1.2.1
authored
84 end
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
85 # ... check that all ancestors exist
86 node.ancestor_ids.each do |ancestor_id|
87 unless exists? ancestor_id
88 raise Ancestry::AncestryIntegrityException.new("Reference to non-existent node in node #{node.id}: #{ancestor_id}.")
89 end
90 end
91 # ... check that all node parents are consistent with values observed earlier
92 node.path_ids.zip([nil] + node.path_ids).each do |node_id, parent_id|
93 parents[node_id] = parent_id unless parents.has_key? node_id
94 unless parents[node_id] == parent_id
95 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'}")
96 end
97 end
98 rescue Ancestry::AncestryIntegrityException => integrity_exception
99 case options[:report]
100 when :list then exceptions << integrity_exception
101 when :echo then puts integrity_exception
102 else raise integrity_exception
34a13a9 @stefankroes Fixes for 1.2.1
authored
103 end
ecea76f @stefankroes Version 1.2.0
authored
104 end
105 end
106 end
34a13a9 @stefankroes Fixes for 1.2.1
authored
107 exceptions if options[:report] == :list
ecea76f @stefankroes Version 1.2.0
authored
108 end
109
110 # Integrity restoration
111 def restore_ancestry_integrity!
112 parents = {}
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
113 # Wrap the whole thing in a transaction ...
114 self.base_class.transaction do
6d96228 Removed backwards compatibility workarounds
Stefan Henzen authored
115 self.base_class.unscoped do
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
116 # For each node ...
117 self.base_class.find_each do |node|
118 # ... set its ancestry to nil if invalid
119 if !node.valid? and !node.errors[node.class.ancestry_column].blank?
120 node.without_ancestry_callbacks do
121 node.update_attribute node.ancestry_column, nil
122 end
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
123 end
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
124 # ... save parent of this node in parents array if it exists
125 parents[node.id] = node.parent_id if exists? node.parent_id
ecea76f @stefankroes Version 1.2.0
authored
126
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
127 # Reset parent id in array to nil if it introduces a cycle
128 parent = parents[node.id]
129 until parent.nil? || parent == node.id
130 parent = parents[parent]
131 end
132 parents[node.id] = nil if parent == node.id
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
133 end
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
134
135 # For each node ...
136 self.base_class.find_each do |node|
137 # ... rebuild ancestry from parents array
138 ancestry, parent = nil, parents[node.id]
139 until parent.nil?
140 ancestry, parent = if ancestry.nil? then parent else "#{parent}/#{ancestry}" end, parents[parent]
141 end
142 node.without_ancestry_callbacks do
864acf1 fix error in restore_ancestry_integrity! introduced in prev. commit
Stefan Henzen authored
143 node.update_attribute node.ancestry_column, ancestry
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
144 end
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
145 end
ecea76f @stefankroes Version 1.2.0
authored
146 end
147 end
148 end
149
150 # Build ancestry from parent id's for migration purposes
151 def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
6d96228 Removed backwards compatibility workarounds
Stefan Henzen authored
152 self.base_class.unscoped do
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
153 self.base_class.find_each(:conditions => {:parent_id => parent_id}) do |node|
154 node.without_ancestry_callbacks do
155 node.update_attribute ancestry_column, ancestry
156 end
157 build_ancestry_from_parent_ids! node.id, if ancestry.nil? then "#{node.id}" else "#{ancestry}/#{node.id}" end
ecea76f @stefankroes Version 1.2.0
authored
158 end
159 end
160 end
161
162 # Rebuild depth cache if it got corrupted or if depth caching was just turned on
163 def rebuild_depth_cache!
164 raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
165
6d96228 Removed backwards compatibility workarounds
Stefan Henzen authored
166 self.base_class.unscoped do
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
167 self.base_class.find_each do |node|
168 node.update_attribute depth_cache_column, node.depth
169 end
ecea76f @stefankroes Version 1.2.0
authored
170 end
171 end
172 end
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
173 end
Something went wrong with that request. Please try again.