Skip to content
This repository
Newer
Older
100644 274 lines (205 sloc) 16.103 kb
79671c47 »
2009-10-16 Started on rdoc readme
1 = Ancestry
1282d2e8 »
2009-10-16 Initial commit
2
e3ea26a5 »
2009-10-29 - Version 1.1.2 (2009-10-29)
3 Ancestry is a gem/plugin that allows the records of a Ruby on Rails ActiveRecord model to be organised as a tree structure (or hierarchy). It uses a single, intuitively formatted database column, using a variation on the materialised path pattern. 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 STI support, named_scopes, depth caching, depth constraints, easy migration from older plugins/gems, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.
1282d2e8 »
2009-10-16 Initial commit
4
79671c47 »
2009-10-16 Started on rdoc readme
5 = Installation
1282d2e8 »
2009-10-16 Initial commit
6
7 To apply Ancestry to any ActiveRecord model, follow these simple steps:
8
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
9 1. Install gem
10 - Install gemcutter gem: sudo gem install gemcutter (maybe you need: gem update --system)
11 - Add gemcutter.org as default gem source: gem tumble
d43ece50 »
2009-10-16 Some last changes to the readme
12 - Add to config/environment.rb: config.gem 'ancestry'
13 - Install required gems: sudo rake gems:install
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
14 - Alternatively: sudo gem install ancestry
15 - If you don't want gemcutter: config.gem 'ancestry', :source => 'gemcutter.org'
16 - Alternatively: sudo gem install ancestry --source gemcutter.org
1282d2e8 »
2009-10-16 Initial commit
17
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
18 2. Add ancestry column to your table
d43ece50 »
2009-10-16 Some last changes to the readme
19 - Create migration: ./script/generate migration add_ancestry_to_[table] ancestry:string
df705c35 »
2009-10-22 Version 1.1.0 done!
20 - Add index to migration: add_index [table], :ancestry (UP) / remove_index [table], :ancestry (DOWN)
d43ece50 »
2009-10-16 Some last changes to the readme
21 - Migrate your database: rake db:migrate
1282d2e8 »
2009-10-16 Initial commit
22
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
23 3. Add ancestry to your model
d43ece50 »
2009-10-16 Some last changes to the readme
24 - Add to app/models/[model].rb: acts_as_tree
1282d2e8 »
2009-10-16 Initial commit
25
26 Your model is now a tree!
27
41c344de »
2009-10-29 Updated readme
28 = Organising records into a tree
1282d2e8 »
2009-10-16 Initial commit
29
30 You can use the parent attribute to organise your records into a tree. If you have the id of the record you want to use as a parent and don't want to fetch it, you can also use parent_id. Like any virtual model attributes, parent and parent_id can be set using parent= and parent_id= on a record or by including them in the hash passed to new, create, create!, update_attributes and update_attributes!. For example:
31
95e67014 »
2009-10-18 Updated README
32 TreeNode.create! :name => 'Stinky', :parent => TreeNode.create!(:name => 'Squeeky')
1282d2e8 »
2009-10-16 Initial commit
33
48d00be9 »
2009-10-18 Updated readme
34 You can also create children through the children relation on a node:
35
95e67014 »
2009-10-18 Updated README
36 node.children.create :name => 'Stinky'
48d00be9 »
2009-10-18 Updated readme
37
41c344de »
2009-10-29 Updated readme
38 = Navigating your tree
1282d2e8 »
2009-10-16 Initial commit
39
40 To navigate an Ancestry model, use the following methods on any instance / record:
41
df705c35 »
2009-10-22 Version 1.1.0 done!
42 parent Returns the parent of the record, nil for a root node
43 parent_id Returns the id of the parent of the record, nil for a root node
44 root Returns the root of the tree the record is in, self for a root node
45 root_id Returns the id of the root of the tree the record is in
46 is_root? Returns true if the record is a root node, false otherwise
47 ancestor_ids Returns a list of ancestor ids, starting with the root id and ending with the parent id
48 ancestors Scopes the model on ancestors of the record
22c43f45 »
2009-10-29 Fixed to typos and changed date in gemspec
49 path_ids Returns a list the path ids, starting with the root id and ending with the node's own id
df705c35 »
2009-10-22 Version 1.1.0 done!
50 path Scopes model on path records of the record
51 children Scopes the model on children of the record
52 child_ids Returns a list of child ids
53 has_children? Returns true if the record has any children, false otherwise
54 is_childless? Returns true is the record has no childen, false otherwise
55 siblings Scopes the model on siblings of the record, the record itself is included
56 sibling_ids Returns a list of sibling ids
57 has_siblings? Returns true if the record's parent has more than one child
58 is_only_child? Returns true if the record is the only child of its parent
59 descendants Scopes the model on direct and indirect children of the record
60 descendant_ids Returns a list of a descendant ids
61 subtree Scopes the model on descendants and itself
62 subtree_ids Returns a list of all ids in the record's subtree
63 depth Return the depth of the node, root nodes are at depth 0
64
41c344de »
2009-10-29 Updated readme
65 = Options for acts_as_tree
df705c35 »
2009-10-22 Version 1.1.0 done!
66
e2a2ab68 »
2009-10-22 Fixed typo in README
67 The acts_as_tree methods supports the following options:
df705c35 »
2009-10-22 Version 1.1.0 done!
68
69 :ancestry_column Pass in a symbol to store ancestry in a different column
70 :orphan_strategy Instruct Ancestry what to do with children of a node that is destroyed:
71 :destroy All children are destroyed as well (default)
72 :rootify The children of the destroyed node become root nodes
73 :restrict An AncestryException is raised if any children exist
74 :cache_depth Cache the depth of each node in the 'ancestry_depth' column (default: false)
e3ea26a5 »
2009-10-29 - Version 1.1.2 (2009-10-29)
75 If you turn depth_caching on for an existing model:
76 - Migrate: add_column [table], :ancestry_depth, :default => 0
77 - Build cache: TreeNode.rebuild_depth_cache!
df705c35 »
2009-10-22 Version 1.1.0 done!
78 :depth_cache_column Pass in a symbol to store depth cache in a different column
1282d2e8 »
2009-10-16 Initial commit
79
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
80 = (Named) Scopes
1282d2e8 »
2009-10-16 Initial commit
81
82 Where possible, the navigation methods return scopes instead of records, this means additional ordering, conditions, limits, etc. can be applied and that the result can be either retrieved, counted or checked for existence. For example:
83
8d2cffbf »
2009-10-16 Fixed some problems in the README
84 node.children.exists?(:name => 'Mary')
85 node.subtree.all(:order => :name, :limit => 10).each do; ...; end
86 node.descendants.count
1282d2e8 »
2009-10-16 Initial commit
87
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
88 For convenience, a couple of named scopes are included at the class level:
1282d2e8 »
2009-10-16 Initial commit
89
df705c35 »
2009-10-22 Version 1.1.0 done!
90 roots Root nodes
91 ancestors_of(node) Ancestors of node, node can be either a record or an id
92 children_of(node) Children of node, node can be either a record or an id
93 descendants_of(node) Descendants of node, node can be either a record or an id
94 siblings_of(node) Siblings of node, node can be either a record or an id
1282d2e8 »
2009-10-16 Initial commit
95
22a19844 » Stefan Kroes
2009-10-18 - Added some tests for node creation through scopes
96 Thanks to some convenient rails magic, it is even possible to create nodes through the children and siblings scopes:
97
48d00be9 »
2009-10-18 Updated readme
98 node.children.create
99 node.siblings.create!
100 TestNode.children_of(node_id).new
101 TestNode.siblings_of(node_id).create
1282d2e8 »
2009-10-16 Initial commit
102
df705c35 »
2009-10-22 Version 1.1.0 done!
103 = Selecting nodes by depth
1282d2e8 »
2009-10-16 Initial commit
104
df705c35 »
2009-10-22 Version 1.1.0 done!
105 When depth caching is enabled (see acts_as_tree options), five more named scopes can be used to select nodes on their depth:
1282d2e8 »
2009-10-16 Initial commit
106
df705c35 »
2009-10-22 Version 1.1.0 done!
107 before_depth(depth) Return nodes that are less deep than depth (node.depth < depth)
108 to_depth(depth) Return nodes up to a certain depth (node.depth <= depth)
109 at_depth(depth) Return nodes that are at depth (node.depth == depth)
110 from_depth(depth) Return nodes starting from a certain depth (node.depth >= depth)
111 after_depth(depth) Return nodes that are deeper than depth (node.depth > depth)
112
113 The depth scopes are also available through calls to descendants, descendant_ids, subtree, subtree_ids, path and ancestors. In this case, depth values are interpreted relatively. Some examples:
114
115 node.subtree(:to_depth => 2) Subtree of node, to a depth of node.depth + 2 (self, children and grandchildren)
116 node.subtree.to_depth(5) Subtree of node to an absolute depth of 5
117 node.descendants(:at_depth => 2) Descendant of node, at depth node.depth + 2 (grandchildren)
118 node.descendants.at_depth(10) Descendants of node at an absolute depth of 10
119 node.ancestors.to_depth(3) The oldest 4 ancestors of node (its root and 3 more)
120 node.path(:from_depth => -2) The node's grandparent, parent and the node itself
121
122 node.ancestors(:from_depth => -6, :to_depth => -4)
123 node.path.from_depth(3).to_depth(4)
124 node.descendants(:from_depth => 2, :to_depth => 4)
125 node.subtree.from_depth(10).to_depth(12)
126
127 Please note that depth constraints cannot be passed to ancestor_ids and path_ids. The reason for this is that both these relations can be fetched directly from the ancestry column without performing a database query. It would require an entirely different method of applying the depth constraints which isn't worth the effort of implementing. You can use ancestors(depth_options).map(&:id) or ancestor_ids.slice(min_depth..max_depth) instead.
1282d2e8 »
2009-10-16 Initial commit
128
e3ea26a5 »
2009-10-29 - Version 1.1.2 (2009-10-29)
129 = STI support
130
131 Ancestry works fine with STI. Just create a STI inheritance hierarchy and build an Ancestry tree from the different classes/models. All Ancestry relations that where described above will return nodes of any model type. If you do only want nodes of a specific subclass you'll have to add a condition on type for that.
132
79671c47 »
2009-10-16 Started on rdoc readme
133 = Arrangement
1282d2e8 »
2009-10-16 Initial commit
134
135 Ancestry can arrange an entire subtree into nested hashes for easy navigation after retrieval from the database. TreeNode.arrange could for example return:
136
8d2cffbf »
2009-10-16 Fixed some problems in the README
137 { #<TreeNode id: 100018, name: "Stinky", ancestry: nil>
138 => { #<TreeNode id: 100019, name: "Crunchy", ancestry: "100018">
139 => { #<TreeNode id: 100020, name: "Squeeky", ancestry: "100018/100019">
140 => {}
141 }
1282d2e8 »
2009-10-16 Initial commit
142 }
143 }
144
145 The arrange method also works on a scoped class, for example:
146
8d2cffbf »
2009-10-16 Fixed some problems in the README
147 TreeNode.find_by_name('Crunchy').subtree.arrange
1282d2e8 »
2009-10-16 Initial commit
148
df705c35 »
2009-10-22 Version 1.1.0 done!
149 = Migrating from plugin that uses parent_id column
150
151 Most current tree plugins use a parent_id column (acts_as_tree, awesome_nested_set, better_nested_set, acts_as_nested_set). With ancestry its easy to migrate from any of these plugins, to do so, follow these steps:
152
153 1. Add ancestry column to your table
154 - Create migration: ./script/generate migration add_ancestry_to_[table] ancestry:string
155 - Add index to migration: add_index [table], :ancestry (UP) / remove_index [table], :ancestry (DOWN)
156 - Migrate your database: rake db:migrate
157
158 2. Remove old tree plugin or gem and add in Ancestry
159 - Remove plugin: rm -Rf vendor/plugins/[old plugin]
160 - Remove gem config line from environment.rb: config.gem [old gem]
161 - Add Ancestry to environment.rb: config.gem :ancestry
162 - See 'Installation' for more info on installing and configuring gems
163
164 3. Change your model
165 - Remove any macros required by old plugin/gem from app/models/[model].rb
166 - Add to app/models/[model].rb: acts_as_tree
167
168 4. Migrate database
169 - In './script.console': [model].build_ancestry_from_parent_ids!
170 - Make sure it worked ok: [model].check_ancestry_integrity!
171
172 5. Change your code
173 - Most tree calls will probably work fine with ancestry
174 - Others must be changed or proxied
175 - Check if all your data is intact and all tests pass
176
177 6. Drop parent_id column:
178 - Create migration: ./script/generate migration remove_parent_id_from_[table]
179 - Add to migration: remove_column [table], :parent_id (UP) / add_column [table], :parent_id, :integer (DOWN)
180 - Migrate your database: rake db:migrate
181
41c344de »
2009-10-29 Updated readme
182 = Integrity checking and restoration
1282d2e8 »
2009-10-16 Initial commit
183
df705c35 »
2009-10-22 Version 1.1.0 done!
184 I don't see any way Ancestry tree integrity could get compromised without explicitly setting cyclic parents or invalid ancestry and circumventing validation with update_attribute, if you do, please let me know.
185
186 Ancestry includes some methods for detecting integrity problems and restoring integrity just to be sure. To check integrity use: [Model].check_ancestry_integrity!. An AncestryIntegrityException will be raised if there are any problems. To restore integrity use: [Model].restore_ancestry_integrity!.
1282d2e8 »
2009-10-16 Initial commit
187
188 For example, from IRB:
189
8d2cffbf »
2009-10-16 Fixed some problems in the README
190 >> stinky = TreeNode.create :name => 'Stinky'
d43ece50 »
2009-10-16 Some last changes to the readme
191 $ #<TreeNode id: 1, name: "Stinky", ancestry: nil>
8d2cffbf »
2009-10-16 Fixed some problems in the README
192 >> squeeky = TreeNode.create :name => 'Squeeky', :parent => stinky
d43ece50 »
2009-10-16 Some last changes to the readme
193 $ #<TreeNode id: 2, name: "Squeeky", ancestry: "1">
8d2cffbf »
2009-10-16 Fixed some problems in the README
194 >> stinky.update_attribute :parent, squeeky
d43ece50 »
2009-10-16 Some last changes to the readme
195 $ true
8d2cffbf »
2009-10-16 Fixed some problems in the README
196 >> TreeNode.all
d43ece50 »
2009-10-16 Some last changes to the readme
197 $ [#<TreeNode id: 1, name: "Stinky", ancestry: "1/2">, #<TreeNode id: 2, name: "Squeeky", ancestry: "1/2/1">]
df705c35 »
2009-10-22 Version 1.1.0 done!
198 >> TreeNode.check_ancestry_integrity!
8d2cffbf »
2009-10-16 Fixed some problems in the README
199 !! Ancestry::AncestryIntegrityException: Conflicting parent id in node 1: 2 for node 1, expecting nil
df705c35 »
2009-10-22 Version 1.1.0 done!
200 >> TreeNode.restore_ancestry_integrity!
d43ece50 »
2009-10-16 Some last changes to the readme
201 $ [#<TreeNode id: 1, name: "Stinky", ancestry: 2>, #<TreeNode id: 2, name: "Squeeky", ancestry: nil>]
1282d2e8 »
2009-10-16 Initial commit
202
df705c35 »
2009-10-22 Version 1.1.0 done!
203 Additionally, if you think something is wrong with your depth cache:
204
205 >> TreeNode.rebuild_depth_cache!
1282d2e8 »
2009-10-16 Initial commit
206
df705c35 »
2009-10-22 Version 1.1.0 done!
207 = Tests
208
209 The Ancestry gem comes with a unit test suite consisting of about 1800 assertions in about 30 tests. It takes about 10 seconds to run on sqlite. To run it yourself, install Ancestry as a plugin into a Rails application, go to the ancestry folder and type 'rake'. The test suite is located in 'test/acts_as_tree_test.rb'.
1282d2e8 »
2009-10-16 Initial commit
210
79671c47 »
2009-10-16 Started on rdoc readme
211 = Internals
1282d2e8 »
2009-10-16 Initial commit
212
213 As can be seen in the previous section, Ancestry stores a path from the root to the parent for every node. This is a variation on the materialised path database pattern. It allows Ancestry to fetch any relation (siblings, descendants, etc.) in a single sql query without the complicated algorithms and incomprehensibility associated with left and right values. Additionally, any inserts, deletes and updates only affect nodes within the affected node's own subtree.
214
df705c35 »
2009-10-22 Version 1.1.0 done!
215 In the example above, the ancestry column is created as a string. This puts a limitation on the depth of the tree of about 40 or 50 levels, which I think may be enough for most users. To increase the maximum depth of the tree, increase the size of the string that is being used or change it to a text to remove the limitation entirely. Changing it to a text will however decrease performance because an index cannot be put on the column in that case.
216
217 The materialised path pattern requires Ancestry to use a 'like' condition in order to fetch descendants. This should not be particularly slow however since the the condition never starts with a wildcard which allows the DBMS to use the column index. If you have any data on performance with a large number of records, please drop me line.
218
219 = Version history
220
b9a102a4 »
2009-11-01 - Version 1.1.3 (2009-11-01)
221 The latest and recommended version of ancestry is 1.1.3. 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.
df705c35 »
2009-10-22 Version 1.1.0 done!
222
b9a102a4 »
2009-11-01 - Version 1.1.3 (2009-11-01)
223 - Version 1.1.3 (2009-11-01)
224 - Fixed a pretty bad bug where several operations took far too many queries
e3ea26a5 »
2009-10-29 - Version 1.1.2 (2009-10-29)
225 - Version 1.1.2 (2009-10-29)
226 - Added validation for depth cache column
227 - Added STI support (reported broken)
48441fb4 »
2009-10-28 - Version 1.1.1 (2009-10-28)
228 - Version 1.1.1 (2009-10-28)
229 - Fixed some parentheses warnings that where reported
230 - Fixed a reported issue with arrangement
231 - Fixed issues with ancestors and path order on postgres
232 - Added ordered_by_ancestry scope (needed to fix issues)
df705c35 »
2009-10-22 Version 1.1.0 done!
233 - Version 1.1.0 (2009-10-22)
234 - Depth caching (and cache rebuilding)
235 - Depth method for nodes
236 - Named scopes for selecting by depth
237 - Relative depth options for tree navigation methods:
238 - ancestors
239 - path
240 - descendants
241 - descendant_ids
242 - subtree
243 - subtree_ids
244 - Updated README
245 - Easy migration from existing plugins/gems
246 - acts_as_tree checks unknown options
247 - acts_as_tree checks that options are hash
248 - Added a bang (!) to the integrity functions
249 - Since these functions should only be used from ./script/console and not from your appliction, this change is not considered as breaking backwards compatibility and the major version wasn't bumped.
250 - Updated install script to point to documentation
251 - Removed rails specific init
252 - Removed uninstall script
253 - Version 1.0.0 (2009-10-16)
254 - Initial version
255 - Tree building
256 - Tree navigation
257 - Integrity checking / restoration
258 - Arrangement
259 - Orphan strategies
260 - Subtree movement
261 - Named scopes
262 - Validations
1282d2e8 »
2009-10-16 Initial commit
263
41c344de »
2009-10-29 Updated readme
264 = Future work
1282d2e8 »
2009-10-16 Initial commit
265
df705c35 »
2009-10-22 Version 1.1.0 done!
266 I will try to keep Ancestry up to date with changing versions of Rails and Ruby and also with any bug reports I might receive. I will implement new features on request as I see fit. One thing I definitely want to do soon is some proper performance testing.
1282d2e8 »
2009-10-16 Initial commit
267
41c344de »
2009-10-29 Updated readme
268 = Contact and copyright
1282d2e8 »
2009-10-16 Initial commit
269
41c344de »
2009-10-29 Updated readme
270 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.
271
272 Question? Contact me at s.a.kroes[at]gmail.com, make sure you read the documentation.
1282d2e8 »
2009-10-16 Initial commit
273
274 Copyright (c) 2009 Stefan Kroes, released under the MIT license
Something went wrong with that request. Please try again.