ancestry 1.2.5 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|