ancestry 1.2.3 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +18 -6
- data/ancestry.gemspec +1 -1
- data/lib/ancestry/class_methods.rb +31 -9
- data/lib/ancestry/has_ancestry.rb +4 -4
- data/lib/ancestry/instance_methods.rb +26 -14
- metadata +6 -6
data/README.rdoc
CHANGED
@@ -15,7 +15,7 @@ To apply Ancestry to any ActiveRecord model, follow these simple steps:
|
|
15
15
|
- Install required gems: <b>bundle install</b>
|
16
16
|
|
17
17
|
2. Add ancestry column to your table
|
18
|
-
- Create migration: <b
|
18
|
+
- Create migration: <b>rails g migration add_ancestry_to_[table] ancestry:string</b>
|
19
19
|
- Add index to migration: <b>add_index [table], :ancestry</b> (UP) / <b>remove_index [table], :ancestry</b> (DOWN)
|
20
20
|
- Migrate your database: <b>rake db:migrate</b>
|
21
21
|
|
@@ -152,16 +152,24 @@ The arrange method also works on a scoped class, for example:
|
|
152
152
|
|
153
153
|
TreeNode.find_by_name('Crunchy').subtree.arrange
|
154
154
|
|
155
|
-
The arrange method takes ActiveRecord find options. If you want your hashes to be ordered, you should pass the order to the arrange method instead of to the scope. This
|
155
|
+
The arrange method takes ActiveRecord find options. If you want your hashes to be ordered, you should pass the order to the arrange method instead of to the scope. This also works for Ruby 1.8 since an OrderedHash is returned. For example:
|
156
156
|
|
157
157
|
TreeNode.find_by_name('Crunchy').subtree.arrange(:order => :name)
|
158
158
|
|
159
|
+
= Sorting
|
160
|
+
|
161
|
+
If you just want to sort an array of nodes as if you were traversing them in preorder, you can use the sort_by_ancestry class method:
|
162
|
+
|
163
|
+
TreeNode.sort_by_ancestry(array_of_nodes)
|
164
|
+
|
165
|
+
Note that since materialised path trees don't support ordering within a rank, the order of siblings depends on their order in the original array.
|
166
|
+
|
159
167
|
= Migrating from plugin that uses parent_id column
|
160
168
|
|
161
169
|
Most current tree plugins use a parent_id column (has_ancestry, awesome_nested_set, better_nested_set, acts_as_nested_set). With ancestry its easy to migrate from any of these plugins, to do so, use the build_ancestry_from_parent_ids! method on your ancestry model. These steps provide a more detailed explanation:
|
162
170
|
|
163
171
|
1. Add ancestry column to your table
|
164
|
-
- Create migration: <b
|
172
|
+
- Create migration: <b>rails g migration add_ancestry_to_[table] ancestry:string</b>
|
165
173
|
- Add index to migration: <b>add_index [table], :ancestry</b> (UP) / <b>remove_index [table], :ancestry</b> (DOWN)
|
166
174
|
- Migrate your database: <b>rake db:migrate</b>
|
167
175
|
|
@@ -185,7 +193,7 @@ Most current tree plugins use a parent_id column (has_ancestry, awesome_nested_s
|
|
185
193
|
- Check if all your data is intact and all tests pass
|
186
194
|
|
187
195
|
6. Drop parent_id column:
|
188
|
-
- Create migration: <b
|
196
|
+
- Create migration: <b>rails g migration remove_parent_id_from_[table]</b>
|
189
197
|
- Add to migration: <b>remove_column [table], :parent_id</b> (UP) / <b>add_column [table], :parent_id, :integer</b> (DOWN)
|
190
198
|
- Migrate your database: <b>rake db:migrate</b>
|
191
199
|
|
@@ -228,8 +236,12 @@ The materialised path pattern requires Ancestry to use a 'like' condition in ord
|
|
228
236
|
|
229
237
|
= Version history
|
230
238
|
|
231
|
-
The latest
|
239
|
+
The latest version of ancestry is recommended. The three numbers of each version numbers are respectively the major, minor and patch versions. We started with major version 1 because it looks so much better and ancestry was already quite mature and complete when it was published. The major version is only bumped when backwards compatibility is broken. The minor version is bumped when new features are added. The patch version is bumped when bugs are fixed.
|
232
240
|
|
241
|
+
- Version 1.2.4 (2011-4-22)
|
242
|
+
- Prepended table names to column names in queries (thx raelik)
|
243
|
+
- Better check to see if acts_as_tree can be overloaded (thx jims)
|
244
|
+
- Performance inprovements (thx kueda)
|
233
245
|
- Version 1.2.3 (2010-10-28)
|
234
246
|
- Fixed error with determining ActiveRecord version
|
235
247
|
- Added option to specify :primary_key_format (thanks goes to rolftimmermans)
|
@@ -300,6 +312,6 @@ I will try to keep Ancestry up to date with changing versions of Rails and Ruby
|
|
300
312
|
|
301
313
|
Bug report? Faulty/incomplete documentation? Feature request? Please post an issue on 'http://github.com/stefankroes/ancestry/issues'. Please also contact me at s.a.kroes[at]gmail.com if it's urgent.
|
302
314
|
|
303
|
-
Question? Contact me at s.a.kroes[at]gmail.com, make sure you read the documentation.
|
315
|
+
Question? Contact me at s.a.kroes[at]gmail.com, make sure you read the documentation.
|
304
316
|
|
305
317
|
Copyright (c) 2009 Stefan Kroes, released under the MIT license
|
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.2.
|
6
|
+
s.version = '1.2.4'
|
7
7
|
|
8
8
|
s.author = 'Stefan Kroes'
|
9
9
|
s.email = 's.a.kroes@gmail.com'
|
@@ -36,14 +36,36 @@ module Ancestry
|
|
36
36
|
self.base_class.ordered_by_ancestry_and options.delete(:order)
|
37
37
|
end
|
38
38
|
# Get all nodes ordered by ancestry and start sorting them into an empty hash
|
39
|
-
scope.all(options)
|
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|
|
40
47
|
# Find the insertion point for that node by going through its ancestors
|
41
48
|
node.ancestor_ids.inject(arranged_nodes) do |insertion_point, ancestor_id|
|
42
49
|
insertion_point.each do |parent, children|
|
43
50
|
# Change the insertion point to children if node is a descendant of this parent
|
44
51
|
insertion_point = children if ancestor_id == parent.id
|
45
|
-
end
|
46
|
-
|
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
|
47
69
|
end
|
48
70
|
end
|
49
71
|
|
@@ -52,7 +74,7 @@ module Ancestry
|
|
52
74
|
parents = {}
|
53
75
|
exceptions = [] if options[:report] == :list
|
54
76
|
# For each node ...
|
55
|
-
self.base_class.
|
77
|
+
self.base_class.find_each do |node|
|
56
78
|
begin
|
57
79
|
# ... check validity of ancestry column
|
58
80
|
if !node.valid? and !node.errors[node.class.ancestry_column].blank?
|
@@ -86,7 +108,7 @@ module Ancestry
|
|
86
108
|
def restore_ancestry_integrity!
|
87
109
|
parents = {}
|
88
110
|
# For each node ...
|
89
|
-
self.base_class.
|
111
|
+
self.base_class.find_each do |node|
|
90
112
|
# ... set its ancestry to nil if invalid
|
91
113
|
if node.errors[node.class.ancestry_column].blank?
|
92
114
|
node.without_ancestry_callbacks do
|
@@ -104,7 +126,7 @@ module Ancestry
|
|
104
126
|
parents[node.id] = nil if parent == node.id
|
105
127
|
end
|
106
128
|
# For each node ...
|
107
|
-
self.base_class.
|
129
|
+
self.base_class.find_each do |node|
|
108
130
|
# ... rebuild ancestry from parents array
|
109
131
|
ancestry, parent = nil, parents[node.id]
|
110
132
|
until parent.nil?
|
@@ -118,7 +140,7 @@ module Ancestry
|
|
118
140
|
|
119
141
|
# Build ancestry from parent id's for migration purposes
|
120
142
|
def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
|
121
|
-
self.base_class.
|
143
|
+
self.base_class.find_each(:conditions => {:parent_id => parent_id}) do |node|
|
122
144
|
node.without_ancestry_callbacks do
|
123
145
|
node.update_attribute ancestry_column, ancestry
|
124
146
|
end
|
@@ -129,9 +151,9 @@ module Ancestry
|
|
129
151
|
# Rebuild depth cache if it got corrupted or if depth caching was just turned on
|
130
152
|
def rebuild_depth_cache!
|
131
153
|
raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
|
132
|
-
self.base_class.
|
154
|
+
self.base_class.find_each do |node|
|
133
155
|
node.update_attribute depth_cache_column, node.depth
|
134
156
|
end
|
135
157
|
end
|
136
158
|
end
|
137
|
-
end
|
159
|
+
end
|
@@ -51,8 +51,8 @@ class << ActiveRecord::Base
|
|
51
51
|
send scope_method, :descendants_of, lambda { |object| {:conditions => to_node(object).descendant_conditions} }
|
52
52
|
send scope_method, :subtree_of, lambda { |object| {:conditions => to_node(object).subtree_conditions} }
|
53
53
|
send scope_method, :siblings_of, lambda { |object| {:conditions => to_node(object).sibling_conditions} }
|
54
|
-
send scope_method, :ordered_by_ancestry, :order => "(case when #{ancestry_column} is null then 0 else 1 end), #{ancestry_column}"
|
55
|
-
send scope_method, :ordered_by_ancestry_and, lambda { |order| {:order => "(case when #{ancestry_column} is null then 0 else 1 end), #{ancestry_column}, #{order}"} }
|
54
|
+
send scope_method, :ordered_by_ancestry, :order => "(case when #{table_name}.#{ancestry_column} is null then 0 else 1 end), #{table_name}.#{ancestry_column}"
|
55
|
+
send scope_method, :ordered_by_ancestry_and, lambda { |order| {:order => "(case when #{table_name}.#{ancestry_column} is null then 0 else 1 end), #{table_name}.#{ancestry_column}, #{order}"} }
|
56
56
|
|
57
57
|
# Update descendants with new ancestry before save
|
58
58
|
before_save :update_descendants_with_new_ancestry
|
@@ -83,7 +83,7 @@ class << ActiveRecord::Base
|
|
83
83
|
end
|
84
84
|
|
85
85
|
# Alias has_ancestry with acts_as_tree, if it's available.
|
86
|
-
if !
|
86
|
+
if !defined?(ActsAsTree)
|
87
87
|
alias_method :acts_as_tree, :has_ancestry
|
88
88
|
end
|
89
|
-
end
|
89
|
+
end
|
@@ -18,7 +18,7 @@ module Ancestry
|
|
18
18
|
descendant.update_attribute(
|
19
19
|
self.base_class.ancestry_column,
|
20
20
|
descendant.read_attribute(descendant.class.ancestry_column).gsub(
|
21
|
-
/^#{self.child_ancestry}/,
|
21
|
+
/^#{self.child_ancestry}/,
|
22
22
|
if read_attribute(self.class.ancestry_column).blank? then id.to_s else "#{read_attribute self.class.ancestry_column }/#{id}" end
|
23
23
|
)
|
24
24
|
)
|
@@ -55,7 +55,7 @@ module Ancestry
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
# The ancestry value for this record's children
|
60
60
|
def child_ancestry
|
61
61
|
# New records cannot have children
|
@@ -66,7 +66,7 @@ module Ancestry
|
|
66
66
|
|
67
67
|
# Ancestors
|
68
68
|
def ancestor_ids
|
69
|
-
read_attribute(self.base_class.ancestry_column).to_s.split('/').map(
|
69
|
+
read_attribute(self.base_class.ancestry_column).to_s.split('/').map { |id| cast_primary_key(id) }
|
70
70
|
end
|
71
71
|
|
72
72
|
def ancestor_conditions
|
@@ -76,7 +76,7 @@ module Ancestry
|
|
76
76
|
def ancestors depth_options = {}
|
77
77
|
self.base_class.scope_depth(depth_options, depth).ordered_by_ancestry.scoped :conditions => ancestor_conditions
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
def path_ids
|
81
81
|
ancestor_ids + [id]
|
82
82
|
end
|
@@ -88,11 +88,11 @@ module Ancestry
|
|
88
88
|
def path depth_options = {}
|
89
89
|
self.base_class.scope_depth(depth_options, depth).ordered_by_ancestry.scoped :conditions => path_conditions
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
def depth
|
93
93
|
ancestor_ids.size
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
def cache_depth
|
97
97
|
write_attribute self.base_class.depth_cache_column, depth
|
98
98
|
end
|
@@ -171,7 +171,7 @@ module Ancestry
|
|
171
171
|
|
172
172
|
# Descendants
|
173
173
|
def descendant_conditions
|
174
|
-
["#{self.base_class.ancestry_column} like ? or #{self.base_class.ancestry_column} = ?", "#{child_ancestry}/%", child_ancestry]
|
174
|
+
["#{self.base_class.table_name}.#{self.base_class.ancestry_column} like ? or #{self.base_class.table_name}.#{self.base_class.ancestry_column} = ?", "#{child_ancestry}/%", child_ancestry]
|
175
175
|
end
|
176
176
|
|
177
177
|
def descendants depth_options = {}
|
@@ -181,10 +181,10 @@ module Ancestry
|
|
181
181
|
def descendant_ids depth_options = {}
|
182
182
|
descendants(depth_options).all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym)
|
183
183
|
end
|
184
|
-
|
184
|
+
|
185
185
|
# Subtree
|
186
186
|
def subtree_conditions
|
187
|
-
["#{self.base_class.primary_key} = ? or #{self.base_class.ancestry_column} like ? or #{self.base_class.ancestry_column} = ?", self.id, "#{child_ancestry}/%", child_ancestry]
|
187
|
+
["#{self.base_class.table_name}.#{self.base_class.primary_key} = ? or #{self.base_class.table_name}.#{self.base_class.ancestry_column} like ? or #{self.base_class.table_name}.#{self.base_class.ancestry_column} = ?", self.id, "#{child_ancestry}/%", child_ancestry]
|
188
188
|
end
|
189
189
|
|
190
190
|
def subtree depth_options = {}
|
@@ -194,20 +194,20 @@ module Ancestry
|
|
194
194
|
def subtree_ids depth_options = {}
|
195
195
|
subtree(depth_options).all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym)
|
196
196
|
end
|
197
|
-
|
197
|
+
|
198
198
|
# Callback disabling
|
199
199
|
def without_ancestry_callbacks
|
200
200
|
@disable_ancestry_callbacks = true
|
201
201
|
yield
|
202
202
|
@disable_ancestry_callbacks = false
|
203
203
|
end
|
204
|
-
|
204
|
+
|
205
205
|
def ancestry_callbacks_disabled?
|
206
206
|
!!@disable_ancestry_callbacks
|
207
207
|
end
|
208
|
-
|
208
|
+
|
209
209
|
private
|
210
|
-
|
210
|
+
|
211
211
|
# Workaround to support Rails 2
|
212
212
|
def add_error_to_base error
|
213
213
|
if rails_3
|
@@ -216,5 +216,17 @@ module Ancestry
|
|
216
216
|
errors.add_to_base error
|
217
217
|
end
|
218
218
|
end
|
219
|
+
|
220
|
+
def cast_primary_key(key)
|
221
|
+
if primary_key_type == :string
|
222
|
+
key
|
223
|
+
else
|
224
|
+
key.to_i
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def primary_key_type
|
229
|
+
@primary_key_type ||= column_for_attribute(self.class.primary_key).type
|
230
|
+
end
|
219
231
|
end
|
220
|
-
end
|
232
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ancestry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 1.2.
|
9
|
+
- 4
|
10
|
+
version: 1.2.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Stefan Kroes
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-04-22 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
83
|
requirements: []
|
84
84
|
|
85
85
|
rubyforge_project:
|
86
|
-
rubygems_version: 1.
|
86
|
+
rubygems_version: 1.4.1
|
87
87
|
signing_key:
|
88
88
|
specification_version: 3
|
89
89
|
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.
|