ancestry 1.2.5 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ancestry.gemspec +2 -2
- data/lib/ancestry.rb +4 -1
- data/lib/ancestry/class_methods.rb +64 -53
- data/lib/ancestry/has_ancestry.rb +0 -4
- data/lib/ancestry/instance_methods.rb +10 -4
- metadata +11 -8
data/ancestry.gemspec
CHANGED
@@ -3,7 +3,7 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.description = 'Organise ActiveRecord model into a tree structure'
|
4
4
|
s.summary = 'Ancestry allows the records of a ActiveRecord model to be organised in a tree structure, using a single, intuitively formatted database column. It exposes all the standard tree structure relations (ancestors, parent, root, children, siblings, descendants) and all of them can be fetched in a single sql query. Additional features are named_scopes, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.'
|
5
5
|
|
6
|
-
s.version = '1.
|
6
|
+
s.version = '1.3.0'
|
7
7
|
|
8
8
|
s.author = 'Stefan Kroes'
|
9
9
|
s.email = 's.a.kroes@gmail.com'
|
@@ -22,5 +22,5 @@ Gem::Specification.new do |s|
|
|
22
22
|
'README.rdoc'
|
23
23
|
]
|
24
24
|
|
25
|
-
s.add_dependency 'activerecord', '>= 2.
|
25
|
+
s.add_dependency 'activerecord', '>= 2.3.14'
|
26
26
|
end
|
data/lib/ancestry.rb
CHANGED
@@ -1 +1,4 @@
|
|
1
|
-
require 'ancestry/
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/class_methods')
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/instance_methods')
|
3
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/exceptions')
|
4
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/has_ancestry')
|
@@ -73,31 +73,34 @@ module Ancestry
|
|
73
73
|
def check_ancestry_integrity! options = {}
|
74
74
|
parents = {}
|
75
75
|
exceptions = [] if options[:report] == :list
|
76
|
-
|
77
|
-
self.base_class.
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
node.ancestor_ids.each do |ancestor_id|
|
85
|
-
unless exists? ancestor_id
|
86
|
-
raise Ancestry::AncestryIntegrityException.new("Reference to non-existent node in node #{node.id}: #{ancestor_id}.")
|
76
|
+
|
77
|
+
self.base_class.send(:with_exclusive_scope) do
|
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}.")
|
87
84
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
94
103
|
end
|
95
|
-
end
|
96
|
-
rescue Ancestry::AncestryIntegrityException => integrity_exception
|
97
|
-
case options[:report]
|
98
|
-
when :list then exceptions << integrity_exception
|
99
|
-
when :echo then puts integrity_exception
|
100
|
-
else raise integrity_exception
|
101
104
|
end
|
102
105
|
end
|
103
106
|
end
|
@@ -109,33 +112,36 @@ module Ancestry
|
|
109
112
|
parents = {}
|
110
113
|
# Wrap the whole thing in a transaction ...
|
111
114
|
self.base_class.transaction do
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
node.
|
117
|
-
node.
|
115
|
+
self.base_class.send(:with_exclusive_scope) do
|
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
|
118
123
|
end
|
119
|
-
|
120
|
-
|
121
|
-
parents[node.id] = node.parent_id if exists? node.parent_id
|
124
|
+
# ... save parent of this node in parents array if it exists
|
125
|
+
parents[node.id] = node.parent_id if exists? node.parent_id
|
122
126
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
130
|
-
# For each node ...
|
131
|
-
self.base_class.find_each do |node|
|
132
|
-
# ... rebuild ancestry from parents array
|
133
|
-
ancestry, parent = nil, parents[node.id]
|
134
|
-
until parent.nil?
|
135
|
-
ancestry, parent = if ancestry.nil? then parent else "#{parent}/#{ancestry}" end, parents[parent]
|
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
|
136
133
|
end
|
137
|
-
|
138
|
-
|
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
|
143
|
+
node.update_attribute node.ancestry_column, ancestry
|
144
|
+
end
|
139
145
|
end
|
140
146
|
end
|
141
147
|
end
|
@@ -143,19 +149,24 @@ module Ancestry
|
|
143
149
|
|
144
150
|
# Build ancestry from parent id's for migration purposes
|
145
151
|
def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
|
146
|
-
self.base_class.
|
147
|
-
|
148
|
-
node.
|
152
|
+
self.base_class.send(:with_exclusive_scope) do
|
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
|
149
158
|
end
|
150
|
-
build_ancestry_from_parent_ids! node.id, if ancestry.nil? then "#{node.id}" else "#{ancestry}/#{node.id}" end
|
151
159
|
end
|
152
160
|
end
|
153
161
|
|
154
162
|
# Rebuild depth cache if it got corrupted or if depth caching was just turned on
|
155
163
|
def rebuild_depth_cache!
|
156
164
|
raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
|
157
|
-
|
158
|
-
|
165
|
+
|
166
|
+
self.base_class.send(:with_exclusive_scope) do
|
167
|
+
self.base_class.find_each do |node|
|
168
|
+
node.update_attribute depth_cache_column, node.depth
|
169
|
+
end
|
159
170
|
end
|
160
171
|
end
|
161
172
|
end
|
@@ -12,7 +12,7 @@ module Ancestry
|
|
12
12
|
# If node is valid, not a new record and ancestry was updated ...
|
13
13
|
if changed.include?(self.base_class.ancestry_column.to_s) && !new_record? && valid?
|
14
14
|
# ... for each descendant ...
|
15
|
-
|
15
|
+
unscoped_descendants.each do |descendant|
|
16
16
|
# ... replace old ancestry with new ancestry
|
17
17
|
descendant.without_ancestry_callbacks do
|
18
18
|
descendant.update_attribute(
|
@@ -34,16 +34,16 @@ module Ancestry
|
|
34
34
|
unless ancestry_callbacks_disabled?
|
35
35
|
# If this isn't a new record ...
|
36
36
|
unless new_record?
|
37
|
-
# ... make
|
37
|
+
# ... make all children root if orphan strategy is rootify
|
38
38
|
if self.base_class.orphan_strategy == :rootify
|
39
|
-
|
39
|
+
unscoped_descendants.each do |descendant|
|
40
40
|
descendant.without_ancestry_callbacks do
|
41
41
|
descendant.update_attribute descendant.class.ancestry_column, (if descendant.ancestry == child_ancestry then nil else descendant.ancestry.gsub(/^#{child_ancestry}\//, '') end)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
# ... destroy all descendants if orphan strategy is destroy
|
45
45
|
elsif self.base_class.orphan_strategy == :destroy
|
46
|
-
|
46
|
+
unscoped_descendants.each do |descendant|
|
47
47
|
descendant.without_ancestry_callbacks do
|
48
48
|
descendant.destroy
|
49
49
|
end
|
@@ -228,5 +228,11 @@ module Ancestry
|
|
228
228
|
def primary_key_type
|
229
229
|
@primary_key_type ||= column_for_attribute(self.class.primary_key).type
|
230
230
|
end
|
231
|
+
|
232
|
+
def unscoped_descendants
|
233
|
+
self.base_class.send(:with_exclusive_scope) do
|
234
|
+
self.base_class.all(:conditions => descendant_conditions)
|
235
|
+
end
|
236
|
+
end
|
231
237
|
end
|
232
238
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 1.
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 1.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Stefan Kroes
|
@@ -14,21 +14,22 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-
|
17
|
+
date: 2012-05-04 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: activerecord
|
22
22
|
prerelease: false
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
24
25
|
requirements:
|
25
26
|
- - ">="
|
26
27
|
- !ruby/object:Gem::Version
|
27
28
|
segments:
|
28
29
|
- 2
|
29
|
-
-
|
30
|
-
-
|
31
|
-
version: 2.
|
30
|
+
- 3
|
31
|
+
- 14
|
32
|
+
version: 2.3.14
|
32
33
|
type: :runtime
|
33
34
|
version_requirements: *id001
|
34
35
|
description: Organise ActiveRecord model into a tree structure
|
@@ -60,6 +61,7 @@ rdoc_options: []
|
|
60
61
|
require_paths:
|
61
62
|
- lib
|
62
63
|
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
63
65
|
requirements:
|
64
66
|
- - ">="
|
65
67
|
- !ruby/object:Gem::Version
|
@@ -67,6 +69,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
69
|
- 0
|
68
70
|
version: "0"
|
69
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
70
73
|
requirements:
|
71
74
|
- - ">="
|
72
75
|
- !ruby/object:Gem::Version
|
@@ -76,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
79
|
requirements: []
|
77
80
|
|
78
81
|
rubyforge_project:
|
79
|
-
rubygems_version: 1.3.
|
82
|
+
rubygems_version: 1.3.7
|
80
83
|
signing_key:
|
81
84
|
specification_version: 3
|
82
85
|
summary: Ancestry allows the records of a ActiveRecord model to be organised in a tree structure, using a single, intuitively formatted database column. It exposes all the standard tree structure relations (ancestors, parent, root, children, siblings, descendants) and all of them can be fetched in a single sql query. Additional features are named_scopes, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.
|