Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 196 lines (177 sloc) 7.842 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
4e57f32 @NOX73 base_class replace ancestry_base_class
NOX73 authored
5 if object.is_a?(self.ancestry_base_class) then object else find(object) end
ad2a8c5 removing whitespace
Sara Trice authored
6 end
7
ecea76f @stefankroes Version 1.2.0
authored
8 # Scope on relative depth options
9 def scope_depth depth_options, depth
4e57f32 @NOX73 base_class replace ancestry_base_class
NOX73 authored
10 depth_options.inject(self.ancestry_base_class) do |scope, option|
ecea76f @stefankroes Version 1.2.0
authored
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
ad2a8c5 removing whitespace
Sara Trice authored
29
ecea76f @stefankroes Version 1.2.0
authored
30 # Arrangement
31 def arrange options = {}
32 scope =
33 if options[:order].nil?
4e57f32 @NOX73 base_class replace ancestry_base_class
NOX73 authored
34 self.ancestry_base_class.ordered_by_ancestry
ecea76f @stefankroes Version 1.2.0
authored
35 else
4e57f32 @NOX73 base_class replace ancestry_base_class
NOX73 authored
36 self.ancestry_base_class.ordered_by_ancestry_and options.delete(:order)
ecea76f @stefankroes Version 1.2.0
authored
37 end
38 # Get all nodes ordered by ancestry and start sorting them into an empty hash
66fee13 @adammck Fix remaining Rails 4 deprecation warnings
adammck authored
39 arrange_nodes scope.where(options)
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
40 end
ad2a8c5 removing whitespace
Sara Trice authored
41
42 # Arrange array of nodes into a nested hash of the form
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
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
e56807e Added arrange_serializable
Kris Handley authored
58
4cd51a1 give arrange_serializable the ability to take an options hash
Sara Trice authored
59 # Arrangement to nested array
60 def arrange_serializable options={}, nodes=nil
61 nodes = arrange(options) if nodes.nil?
e56807e Added arrange_serializable
Kris Handley authored
62 nodes.map do |parent, children|
4cd51a1 give arrange_serializable the ability to take an options hash
Sara Trice authored
63 parent.serializable_hash.merge 'children' => arrange_serializable(options, children)
e56807e Added arrange_serializable
Kris Handley authored
64 end
65 end
ad2a8c5 removing whitespace
Sara Trice authored
66
67 # Pseudo-preordered array of nodes. Children will always follow parents,
4b59888 @iliya-gr Added block to sort_by_ancestry class method.
iliya-gr authored
68 # for ordering nodes within a rank provide block, eg. Node.sort_by_ancestry(Node.all) {|a, b| a.rank <=> b.rank}.
69 def sort_by_ancestry(nodes, &block)
70 arranged = nodes if nodes.is_a?(Hash)
ad2a8c5 removing whitespace
Sara Trice authored
71
4b59888 @iliya-gr Added block to sort_by_ancestry class method.
iliya-gr authored
72 unless arranged
73 presorted_nodes = nodes.sort do |a, b|
74 a_cestry, b_cestry = a.ancestry || '0', b.ancestry || '0'
ad2a8c5 removing whitespace
Sara Trice authored
75
4b59888 @iliya-gr Added block to sort_by_ancestry class method.
iliya-gr authored
76 if block_given? && a_cestry == b_cestry
77 yield a, b
78 else
79 a_cestry <=> b_cestry
80 end
81 end
ad2a8c5 removing whitespace
Sara Trice authored
82
4b59888 @iliya-gr Added block to sort_by_ancestry class method.
iliya-gr authored
83 arranged = arrange_nodes(presorted_nodes)
84 end
ad2a8c5 removing whitespace
Sara Trice authored
85
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
86 arranged.inject([]) do |sorted_nodes, pair|
87 node, children = pair
88 sorted_nodes << node
4b59888 @iliya-gr Added block to sort_by_ancestry class method.
iliya-gr authored
89 sorted_nodes += sort_by_ancestry(children, &block) unless children.blank?
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
90 sorted_nodes
ecea76f @stefankroes Version 1.2.0
authored
91 end
92 end
93
94 # Integrity checking
34a13a9 @stefankroes Fixes for 1.2.1
authored
95 def check_ancestry_integrity! options = {}
ecea76f @stefankroes Version 1.2.0
authored
96 parents = {}
34a13a9 @stefankroes Fixes for 1.2.1
authored
97 exceptions = [] if options[:report] == :list
ad2a8c5 removing whitespace
Sara Trice authored
98
99 self.ancestry_base_class.unscoped do
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
100 # For each node ...
a759e6b Merge branch 'master' of git://github.com/kaize/ancestry into kaize-m…
Stefan Henzen authored
101 self.ancestry_base_class.find_each do |node|
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
102 begin
103 # ... check validity of ancestry column
104 if !node.valid? and !node.errors[node.class.ancestry_column].blank?
105 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
106 end
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
107 # ... check that all ancestors exist
108 node.ancestor_ids.each do |ancestor_id|
109 unless exists? ancestor_id
110 raise Ancestry::AncestryIntegrityException.new("Reference to non-existent node in node #{node.id}: #{ancestor_id}.")
111 end
112 end
113 # ... check that all node parents are consistent with values observed earlier
114 node.path_ids.zip([nil] + node.path_ids).each do |node_id, parent_id|
115 parents[node_id] = parent_id unless parents.has_key? node_id
116 unless parents[node_id] == parent_id
117 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'}")
118 end
119 end
120 rescue Ancestry::AncestryIntegrityException => integrity_exception
121 case options[:report]
122 when :list then exceptions << integrity_exception
123 when :echo then puts integrity_exception
124 else raise integrity_exception
34a13a9 @stefankroes Fixes for 1.2.1
authored
125 end
ecea76f @stefankroes Version 1.2.0
authored
126 end
127 end
128 end
34a13a9 @stefankroes Fixes for 1.2.1
authored
129 exceptions if options[:report] == :list
ecea76f @stefankroes Version 1.2.0
authored
130 end
131
132 # Integrity restoration
133 def restore_ancestry_integrity!
134 parents = {}
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
135 # Wrap the whole thing in a transaction ...
4e57f32 @NOX73 base_class replace ancestry_base_class
NOX73 authored
136 self.ancestry_base_class.transaction do
ad2a8c5 removing whitespace
Sara Trice authored
137 self.ancestry_base_class.unscoped do
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
138 # For each node ...
a759e6b Merge branch 'master' of git://github.com/kaize/ancestry into kaize-m…
Stefan Henzen authored
139 self.ancestry_base_class.find_each do |node|
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
140 # ... set its ancestry to nil if invalid
141 if !node.valid? and !node.errors[node.class.ancestry_column].blank?
142 node.without_ancestry_callbacks do
143 node.update_attribute node.ancestry_column, nil
144 end
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
145 end
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
146 # ... save parent of this node in parents array if it exists
147 parents[node.id] = node.parent_id if exists? node.parent_id
ecea76f @stefankroes Version 1.2.0
authored
148
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
149 # Reset parent id in array to nil if it introduces a cycle
150 parent = parents[node.id]
151 until parent.nil? || parent == node.id
152 parent = parents[parent]
153 end
ad2a8c5 removing whitespace
Sara Trice authored
154 parents[node.id] = nil if parent == node.id
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
155 end
ad2a8c5 removing whitespace
Sara Trice authored
156
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
157 # For each node ...
a759e6b Merge branch 'master' of git://github.com/kaize/ancestry into kaize-m…
Stefan Henzen authored
158 self.ancestry_base_class.find_each do |node|
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
159 # ... rebuild ancestry from parents array
160 ancestry, parent = nil, parents[node.id]
161 until parent.nil?
162 ancestry, parent = if ancestry.nil? then parent else "#{parent}/#{ancestry}" end, parents[parent]
163 end
164 node.without_ancestry_callbacks do
864acf1 fix error in restore_ancestry_integrity! introduced in prev. commit
Stefan Henzen authored
165 node.update_attribute node.ancestry_column, ancestry
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
166 end
770f2e3 Fix bug in restore_ancestry_integrity!, added test to verify we didn'…
Arthur Holstvoogd authored
167 end
ecea76f @stefankroes Version 1.2.0
authored
168 end
169 end
170 end
ad2a8c5 removing whitespace
Sara Trice authored
171
ecea76f @stefankroes Version 1.2.0
authored
172 # Build ancestry from parent id's for migration purposes
173 def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
ad2a8c5 removing whitespace
Sara Trice authored
174 self.ancestry_base_class.unscoped do
a759e6b Merge branch 'master' of git://github.com/kaize/ancestry into kaize-m…
Stefan Henzen authored
175 self.ancestry_base_class.where(:parent_id => parent_id).find_each do |node|
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
176 node.without_ancestry_callbacks do
177 node.update_attribute ancestry_column, ancestry
178 end
179 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
180 end
181 end
182 end
ad2a8c5 removing whitespace
Sara Trice authored
183
ecea76f @stefankroes Version 1.2.0
authored
184 # Rebuild depth cache if it got corrupted or if depth caching was just turned on
185 def rebuild_depth_cache!
186 raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
ad2a8c5 removing whitespace
Sara Trice authored
187
188 self.ancestry_base_class.unscoped do
a759e6b Merge branch 'master' of git://github.com/kaize/ancestry into kaize-m…
Stefan Henzen authored
189 self.ancestry_base_class.find_each do |node|
eeadeef ancestry should skip default scopes for some of the node update methods.
Gerjan Stokkink authored
190 node.update_attribute depth_cache_column, node.depth
191 end
ecea76f @stefankroes Version 1.2.0
authored
192 end
193 end
194 end
5614a3b @kueda Added class method to sort nodes by ancestry.
kueda authored
195 end
Something went wrong with that request. Please try again.