ancestry 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>./script/generate migration add_ancestry_to_[table] ancestry:string</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 only works for Ruby 1.9 and later since before that hashes weren't ordered. For example:
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>./script/generate migration add_ancestry_to_[table] ancestry:string</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>./script/generate migration remove_parent_id_from_[table]</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 and recommended version of ancestry is 1.2.2. 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.
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. You can also join the #ancestry channel on IRC (irc.freenode.net).
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
@@ -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.3'
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).inject(ActiveSupport::OrderedHash.new) do |arranged_nodes, node|
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; insertion_point
46
- end[node] = ActiveSupport::OrderedHash.new; arranged_nodes
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.all.each do |node|
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.all.each do |node|
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.all.each do |node|
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.all(:conditions => {:parent_id => parent_id}).each do |node|
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.all.each do |node|
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 !respond_to?(:acts_as_tree)
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(&:to_i)
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: 25
5
- prerelease: false
4
+ hash: 23
5
+ prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 2
9
- - 3
10
- version: 1.2.3
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: 2010-10-28 00:00:00 +02:00
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.3.7
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.