lalala 4.0.0.dev.136 → 4.0.0.dev.141
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitmodules +0 -3
- data/lalala.gemspec +2 -6
- data/lib/lalala/version.rb +1 -1
- data/lib/lalala.rb +0 -1
- metadata +19 -53
- data/vendor/deps/closure_tree/.gitignore +0 -12
- data/vendor/deps/closure_tree/.travis.yml +0 -22
- data/vendor/deps/closure_tree/.yardopts +0 -3
- data/vendor/deps/closure_tree/Gemfile +0 -2
- data/vendor/deps/closure_tree/MIT-LICENSE +0 -19
- data/vendor/deps/closure_tree/README.md +0 -641
- data/vendor/deps/closure_tree/Rakefile +0 -26
- data/vendor/deps/closure_tree/ci/Gemfile.rails-3.0.x +0 -5
- data/vendor/deps/closure_tree/ci/Gemfile.rails-3.1.x +0 -4
- data/vendor/deps/closure_tree/ci/Gemfile.rails-3.2.x +0 -4
- data/vendor/deps/closure_tree/closure_tree.gemspec +0 -31
- data/vendor/deps/closure_tree/lib/closure_tree/acts_as_tree.rb +0 -55
- data/vendor/deps/closure_tree/lib/closure_tree/columns.rb +0 -123
- data/vendor/deps/closure_tree/lib/closure_tree/deterministic_ordering.rb +0 -49
- data/vendor/deps/closure_tree/lib/closure_tree/model.rb +0 -386
- data/vendor/deps/closure_tree/lib/closure_tree/numeric_deterministic_ordering.rb +0 -93
- data/vendor/deps/closure_tree/lib/closure_tree/version.rb +0 -3
- data/vendor/deps/closure_tree/lib/closure_tree/with_advisory_lock.rb +0 -18
- data/vendor/deps/closure_tree/lib/closure_tree.rb +0 -8
- data/vendor/deps/closure_tree/spec/cuisine_type_spec.rb +0 -30
- data/vendor/deps/closure_tree/spec/db/database.yml +0 -19
- data/vendor/deps/closure_tree/spec/db/schema.rb +0 -109
- data/vendor/deps/closure_tree/spec/fixtures/labels.yml +0 -55
- data/vendor/deps/closure_tree/spec/fixtures/tags.yml +0 -98
- data/vendor/deps/closure_tree/spec/hash_tree_spec.rb +0 -91
- data/vendor/deps/closure_tree/spec/label_spec.rb +0 -356
- data/vendor/deps/closure_tree/spec/namespace_type_spec.rb +0 -13
- data/vendor/deps/closure_tree/spec/parallel_prepend_sibling_spec.rb +0 -45
- data/vendor/deps/closure_tree/spec/parallel_spec.rb +0 -59
- data/vendor/deps/closure_tree/spec/spec_helper.rb +0 -57
- data/vendor/deps/closure_tree/spec/support/models.rb +0 -74
- data/vendor/deps/closure_tree/spec/tag_spec.rb +0 -469
- data/vendor/deps/closure_tree/spec/user_spec.rb +0 -136
- data/vendor/deps/closure_tree/tests.sh +0 -19
@@ -1,641 +0,0 @@
|
|
1
|
-
# Closure Tree [![Build Status](https://secure.travis-ci.org/mceachen/closure_tree.png?branch=master)](http://travis-ci.org/mceachen/closure_tree)
|
2
|
-
|
3
|
-
### Closure_tree lets your ActiveRecord models act as nodes in a [tree data structure](http://en.wikipedia.org/wiki/Tree_%28data_structure%29)
|
4
|
-
|
5
|
-
Common applications include modeling hierarchical data, like tags, page graphs in CMSes,
|
6
|
-
and tracking user referrals.
|
7
|
-
|
8
|
-
Mostly API-compatible with other popular nesting gems for Rails, like
|
9
|
-
[ancestry](https://github.com/stefankroes/ancestry),
|
10
|
-
[acts_as_tree](https://github.com/amerine/acts_as_tree) and
|
11
|
-
[awesome_nested_set](https://github.com/collectiveidea/awesome_nested_set/),
|
12
|
-
closure_tree has some great features:
|
13
|
-
|
14
|
-
* __Best-in-class select performance__:
|
15
|
-
* Fetch your whole ancestor lineage in 1 SELECT.
|
16
|
-
* Grab all your descendants in 1 SELECT.
|
17
|
-
* Get all your siblings in 1 SELECT.
|
18
|
-
* Fetch all [descendants as a nested hash](#nested-hashes) in 1 SELECT.
|
19
|
-
* [Find a node by ancestry path](#find_or_create_by_path) in 1 SELECT.
|
20
|
-
* __Best-in-class mutation performance__:
|
21
|
-
* 2 SQL INSERTs on node creation
|
22
|
-
* 3 SQL INSERT/UPDATEs on node reparenting
|
23
|
-
* Support for reparenting children (and all their progeny)
|
24
|
-
* Support for [concurrency](#concurrency) (using [with_advisory_lock](https://github/mceachen/with_advisory_lock))
|
25
|
-
* Support for polymorphism [STI](#sti) within the hierarchy
|
26
|
-
* ```find_or_create_by_path``` for [building out hierarchies quickly and conveniently](#find_or_create_by_path)
|
27
|
-
* Support for [deterministic ordering](#deterministic-ordering) of children
|
28
|
-
* Support for [preordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order) traversal of descendants
|
29
|
-
* Excellent [test coverage](#testing) in a variety of environments
|
30
|
-
|
31
|
-
See [Bill Karwin](http://karwin.blogspot.com/)'s excellent
|
32
|
-
[Models for hierarchical data presentation](http://www.slideshare.net/billkarwin/models-for-hierarchical-data)
|
33
|
-
for a description of different tree storage algorithms.
|
34
|
-
|
35
|
-
## Table of Contents
|
36
|
-
|
37
|
-
- [Installation](#installation)
|
38
|
-
- [Usage](#usage)
|
39
|
-
- [Accessing Data](#accessing-data)
|
40
|
-
- [Polymorphic hierarchies with STI](#polymorphic-hierarchies-with-sti)
|
41
|
-
- [Deterministic ordering](#deterministic-ordering)
|
42
|
-
- [Concurrency](#concurrency)
|
43
|
-
- [FAQ](#faq)
|
44
|
-
- [Testing](#testing)
|
45
|
-
- [Change log](#change-log)
|
46
|
-
|
47
|
-
## Installation
|
48
|
-
|
49
|
-
Note that closure_tree only supports Rails 3.0 and later, and has test coverage for MySQL, PostgreSQL, and SQLite.
|
50
|
-
|
51
|
-
1. Add this to your Gemfile: ```gem 'closure_tree'```
|
52
|
-
|
53
|
-
2. Run ```bundle install```
|
54
|
-
|
55
|
-
3. Add ```acts_as_tree``` to your hierarchical model(s). There are a number of [options](#available-options) you can pass in, too.
|
56
|
-
|
57
|
-
4. Add a migration to add a ```parent_id``` column to the model you want to act_as_tree.
|
58
|
-
You may want to also [add a column for deterministic ordering of children](#sort_order), but that's optional.
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
class AddParentIdToTag < ActiveRecord::Migration
|
62
|
-
def change
|
63
|
-
add_column :tag, :parent_id, :integer
|
64
|
-
end
|
65
|
-
end
|
66
|
-
```
|
67
|
-
|
68
|
-
Note that if the column is null, the tag will be considered a root node.
|
69
|
-
|
70
|
-
5. Add a database migration to store the hierarchy for your model. By
|
71
|
-
default the table name will be the model's table name, followed by
|
72
|
-
"_hierarchies". Note that by calling ```acts_as_tree```, a "virtual model" (in this case, ```TagHierarchy```)
|
73
|
-
will be added automatically, so you don't need to create it.
|
74
|
-
|
75
|
-
```ruby
|
76
|
-
class CreateTagHierarchies < ActiveRecord::Migration
|
77
|
-
def change
|
78
|
-
create_table :tag_hierarchies, :id => false do |t|
|
79
|
-
t.integer :ancestor_id, :null => false # ID of the parent/grandparent/great-grandparent/... tag
|
80
|
-
t.integer :descendant_id, :null => false # ID of the target tag
|
81
|
-
t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
|
82
|
-
end
|
83
|
-
|
84
|
-
# For "all progeny of…" selects:
|
85
|
-
add_index :tag_hierarchies, [:ancestor_id, :descendant_id], :unique => true
|
86
|
-
|
87
|
-
# For "all ancestors of…" selects
|
88
|
-
add_index :tag_hierarchies, [:descendant_id]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
```
|
92
|
-
|
93
|
-
6. Run ```rake db:migrate```
|
94
|
-
|
95
|
-
7. If you're migrating from another system where your model already has a
|
96
|
-
```parent_id``` column, run ```Tag.rebuild!``` and the
|
97
|
-
…_hierarchy table will be truncated and rebuilt.
|
98
|
-
|
99
|
-
If you're starting from scratch you don't need to call ```rebuild!```.
|
100
|
-
|
101
|
-
## Usage
|
102
|
-
|
103
|
-
### Creation
|
104
|
-
|
105
|
-
Create a root node:
|
106
|
-
|
107
|
-
```ruby
|
108
|
-
grandparent = Tag.create(:name => 'Grandparent')
|
109
|
-
```
|
110
|
-
|
111
|
-
Child nodes are created by appending to the children collection:
|
112
|
-
|
113
|
-
```ruby
|
114
|
-
parent = grandparent.children.create(:name => 'Parent')
|
115
|
-
```
|
116
|
-
|
117
|
-
Or by appending to the children collection:
|
118
|
-
|
119
|
-
```ruby
|
120
|
-
child2 = Tag.new(:name => 'Second Child')
|
121
|
-
parent.children << child2
|
122
|
-
```
|
123
|
-
|
124
|
-
Or by calling the "add_child" method:
|
125
|
-
|
126
|
-
```ruby
|
127
|
-
child3 = Tag.new(:name => 'Third Child')
|
128
|
-
parent.add_child child3
|
129
|
-
```
|
130
|
-
|
131
|
-
Then:
|
132
|
-
|
133
|
-
```ruby
|
134
|
-
grandparent.self_and_descendants.collect(&:name)
|
135
|
-
=> ["Grandparent", "Parent", "First Child", "Second Child", "Third Child"]
|
136
|
-
|
137
|
-
child1.ancestry_path
|
138
|
-
=> ["Grandparent", "Parent", "First Child"]
|
139
|
-
```
|
140
|
-
|
141
|
-
### find_or_create_by_path
|
142
|
-
|
143
|
-
We can do all the node creation and add_child calls with one method call:
|
144
|
-
|
145
|
-
```ruby
|
146
|
-
child = Tag.find_or_create_by_path(["grandparent", "parent", "child"])
|
147
|
-
```
|
148
|
-
|
149
|
-
You can ```find``` as well as ```find_or_create``` by "ancestry paths".
|
150
|
-
Ancestry paths may be built using any column in your model. The default
|
151
|
-
column is ```name```, which can be changed with the :name_column option
|
152
|
-
provided to ```acts_as_tree```.
|
153
|
-
|
154
|
-
Note that any other AR fields can be set with the second, optional ```attributes``` argument.
|
155
|
-
|
156
|
-
```ruby
|
157
|
-
child = Tag.find_or_create_by_path(%w{home chuck Photos"}, {:tag_type => "File"})
|
158
|
-
```
|
159
|
-
This will pass the attribute hash of ```{:name => "home", :tag_type => "File"}``` to
|
160
|
-
```Tag.find_or_create_by_name``` if the root directory doesn't exist (and
|
161
|
-
```{:name => "chuck", :tag_type => "File"}``` if the second-level tag doesn't exist, and so on).
|
162
|
-
|
163
|
-
### Moving nodes around the tree
|
164
|
-
|
165
|
-
Nodes can be moved around to other parents, and closure_tree moves the node's descendancy to the new parent for you:
|
166
|
-
|
167
|
-
```ruby
|
168
|
-
d = Tag.find_or_create_by_path %w(a b c d)
|
169
|
-
h = Tag.find_or_create_by_path %w(e f g h)
|
170
|
-
e = h.root
|
171
|
-
d.add_child(e) # "d.children << e" would work too, of course
|
172
|
-
h.ancestry_path
|
173
|
-
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
|
174
|
-
```
|
175
|
-
|
176
|
-
### Nested hashes
|
177
|
-
|
178
|
-
```hash_tree``` provides a method for rendering a subtree as an
|
179
|
-
ordered nested hash:
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
b = Tag.find_or_create_by_path %w(a b)
|
183
|
-
a = b.parent
|
184
|
-
b2 = Tag.find_or_create_by_path %w(a b2)
|
185
|
-
d1 = b.find_or_create_by_path %w(c1 d1)
|
186
|
-
c1 = d1.parent
|
187
|
-
d2 = b.find_or_create_by_path %w(c2 d2)
|
188
|
-
c2 = d2.parent
|
189
|
-
|
190
|
-
Tag.hash_tree
|
191
|
-
=> {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}
|
192
|
-
|
193
|
-
Tag.hash_tree(:limit_depth => 2)
|
194
|
-
=> {a => {b => {}, b2 => {}}}
|
195
|
-
|
196
|
-
b.hash_tree
|
197
|
-
=> {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}
|
198
|
-
|
199
|
-
b.hash_tree(:limit_depth => 2)
|
200
|
-
=> {b => {c1 => {}, c2 => {}}}
|
201
|
-
```
|
202
|
-
|
203
|
-
**If your tree is large (or might become so), use :limit_depth.**
|
204
|
-
|
205
|
-
Without this option, ```hash_tree``` will load the entire contents of that table into RAM. Your
|
206
|
-
server may not be happy trying to do this.
|
207
|
-
|
208
|
-
HT: [ancestry](https://github.com/stefankroes/ancestry#arrangement) and [elhoyos](https://github.com/mceachen/closure_tree/issues/11)
|
209
|
-
|
210
|
-
### <a id="options"></a>Available options
|
211
|
-
|
212
|
-
When you include ```acts_as_tree``` in your model, you can provide a hash to override the following defaults:
|
213
|
-
|
214
|
-
* ```:parent_column_name``` to override the column name of the parent foreign key in the model's table. This defaults to "parent_id".
|
215
|
-
* ```:hierarchy_table_name``` to override the hierarchy class name. This defaults to the singular name of the model + "Hierarchy", like ```TagHierarchy```.
|
216
|
-
* ```:hierarchy_table_name``` to override the hierarchy table name. This defaults to the singular name of the model + "_hierarchies", like ```tag_hierarchies```.
|
217
|
-
* ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nullify```.
|
218
|
-
* ```:nullify``` will simply set the parent column to null. Each child node will be considered a "root" node. This is the default.
|
219
|
-
* ```:delete_all``` will delete all descendant nodes (which circumvents the destroy hooks)
|
220
|
-
* ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)
|
221
|
-
* ```:name_column``` used by #```find_or_create_by_path```, #```find_by_path```, and ```ancestry_path``` instance methods. This is primarily useful if the model only has one required field (like a "tag").
|
222
|
-
* ```:order``` used to set up [deterministic ordering](#deterministic-ordering)
|
223
|
-
|
224
|
-
## Accessing Data
|
225
|
-
|
226
|
-
### Class methods
|
227
|
-
|
228
|
-
* ```Tag.root``` returns an arbitrary root node
|
229
|
-
* ```Tag.roots``` returns all root nodes
|
230
|
-
* ```Tag.leaves``` returns all leaf nodes
|
231
|
-
* ```Tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.
|
232
|
-
* ```Tag.find_by_path(path)``` returns the node whose name path is ```path```. See (#find_or_create_by_path).
|
233
|
-
* ```Tag.find_or_create_by_path(path)``` returns the node whose name path is ```path```, and will create the node if it doesn't exist already.See (#find_or_create_by_path).
|
234
|
-
* ```Tag.find_all_by_generation(generation_level)``` returns the descendant nodes who are ```generation_level``` away from a root. ```Tag.find_all_by_generation(0)``` is equivalent to ```Tag.roots```.
|
235
|
-
|
236
|
-
### Instance methods
|
237
|
-
|
238
|
-
* ```tag.root``` returns the root for this node
|
239
|
-
* ```tag.root?``` returns true if this is a root node
|
240
|
-
* ```tag.child?``` returns true if this is a child node. It has a parent.
|
241
|
-
* ```tag.leaf?``` returns true if this is a leaf node. It has no children.
|
242
|
-
* ```tag.leaves``` is scoped to all leaf nodes in self_and_descendants.
|
243
|
-
* ```tag.depth``` returns the depth, or "generation", for this node in the tree. A root node will have a value of 0.
|
244
|
-
* ```tag.parent``` returns the node's immediate parent. Root nodes will return nil.
|
245
|
-
* ```tag.children``` is a ```has_many``` of immediate children (just those nodes whose parent is the current node).
|
246
|
-
* ```tag.child_ids``` is an array of the IDs of the children.
|
247
|
-
* ```tag.ancestors``` is a ordered scope of [ parent, grandparent, great grandparent, … ]. Note that the size of this array will always equal ```tag.depth```.
|
248
|
-
* ```tag.ancestor_ids``` is an array of the IDs of the ancestors.
|
249
|
-
* ```tag.self_and_ancestors``` returns a scope containing self, parent, grandparent, great grandparent, etc.
|
250
|
-
* ```tag.siblings``` returns a scope containing all nodes with the same parent as ```tag```, excluding self.
|
251
|
-
* ```tag.sibling_ids``` returns an array of the IDs of the siblings.
|
252
|
-
* ```tag.self_and_siblings``` returns a scope containing all nodes with the same parent as ```tag```, including self.
|
253
|
-
* ```tag.descendants``` returns a scope of all children, childrens' children, etc., excluding self ordered by depth.
|
254
|
-
* ```tag.descendant_ids``` returns an array of the IDs of the descendants.
|
255
|
-
* ```tag.self_and_descendants``` returns a scope of all children, childrens' children, etc., including self, ordered by depth.
|
256
|
-
* ```tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.
|
257
|
-
* ```tag.find_by_path(path)``` returns the node whose name path *from ```tag```* is ```path```. See (#find_or_create_by_path).
|
258
|
-
* ```tag.find_or_create_by_path(path)``` returns the node whose name path *from ```tag```* is ```path```, and will create the node if it doesn't exist already.See (#find_or_create_by_path).
|
259
|
-
* ```tag.find_all_by_generation(generation_level)``` returns the descendant nodes who are ```generation_level``` away from ```tag```.
|
260
|
-
* ```tag.find_all_by_generation(0).to_a``` == ```[tag]```
|
261
|
-
* ```tag.find_all_by_generation(1)``` == ```tag.children```
|
262
|
-
* ```tag.find_all_by_generation(2)``` will return the tag's grandchildren, and so on.
|
263
|
-
* ```tag.destroy``` will destroy a node and do <em>something</em> to its children, which is determined by the ```:dependent``` option passed to ```acts_as_tree```.
|
264
|
-
|
265
|
-
## Polymorphic hierarchies with STI
|
266
|
-
|
267
|
-
Polymorphic models using single table inheritance (STI) are supported:
|
268
|
-
|
269
|
-
1. Create a db migration that adds a String ```type``` column to your model
|
270
|
-
2. Subclass the model class. You only need to add ```acts_as_tree``` to your base class:
|
271
|
-
|
272
|
-
```ruby
|
273
|
-
class Tag < ActiveRecord::Base
|
274
|
-
acts_as_tree
|
275
|
-
end
|
276
|
-
class WhenTag < Tag ; end
|
277
|
-
class WhereTag < Tag ; end
|
278
|
-
class WhatTag < Tag ; end
|
279
|
-
```
|
280
|
-
|
281
|
-
Please note that Rails (<= 3.2) doesn't handle polymorphic associations correctly if
|
282
|
-
you use the ```:type``` attribute, so **this doesn't work**:
|
283
|
-
|
284
|
-
```ruby
|
285
|
-
# BAD: ActiveRecord ignores the :type attribute:
|
286
|
-
root.children.create(:name => "child", :type => "WhenTag")
|
287
|
-
```
|
288
|
-
|
289
|
-
Instead, use either ```.add_child``` or ```children <<```:
|
290
|
-
|
291
|
-
```ruby
|
292
|
-
# GOOD!
|
293
|
-
a = Tag.create!(:name => "a")
|
294
|
-
b = WhenTag.new(:name => "b")
|
295
|
-
a.children << b
|
296
|
-
c = WhatTag.new(:name => "c")
|
297
|
-
b.add_child(c)
|
298
|
-
```
|
299
|
-
|
300
|
-
See [issue 43](https://github.com/mceachen/closure_tree/issues/43) for more information.
|
301
|
-
|
302
|
-
## Deterministic ordering
|
303
|
-
|
304
|
-
By default, children will be ordered by your database engine, which may not be what you want.
|
305
|
-
|
306
|
-
If you want to order children alphabetically, and your model has a ```name``` column, you'd do this:
|
307
|
-
|
308
|
-
```ruby
|
309
|
-
class Tag < ActiveRecord::Base
|
310
|
-
acts_as_tree :order => 'name'
|
311
|
-
end
|
312
|
-
```
|
313
|
-
|
314
|
-
If you want a specific order, add a new integer column to your model in a migration:
|
315
|
-
|
316
|
-
```ruby
|
317
|
-
t.integer :sort_order
|
318
|
-
```
|
319
|
-
|
320
|
-
and in your model:
|
321
|
-
|
322
|
-
```ruby
|
323
|
-
class OrderedTag < ActiveRecord::Base
|
324
|
-
acts_as_tree :order => 'sort_order'
|
325
|
-
end
|
326
|
-
```
|
327
|
-
|
328
|
-
When you enable ```order```, you'll also have the following new methods injected into your model:
|
329
|
-
|
330
|
-
* ```tag.siblings_before``` is a scope containing all nodes with the same parent as ```tag```,
|
331
|
-
whose sort order column is less than ```self```. These will be ordered properly, so the ```last```
|
332
|
-
element in scope will be the sibling immediately before ```self```
|
333
|
-
* ```tag.siblings_after``` is a scope containing all nodes with the same parent as ```tag```,
|
334
|
-
whose sort order column is more than ```self```. These will be ordered properly, so the ```first```
|
335
|
-
element in scope will be the sibling immediately "after" ```self```
|
336
|
-
|
337
|
-
If your ```order``` column is an integer attribute, you'll also have these:
|
338
|
-
|
339
|
-
* The class method ```#roots_and_descendants_preordered```, which returns all nodes in your tree,
|
340
|
-
[pre-ordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order).
|
341
|
-
|
342
|
-
* ```node1.self_and_descendants_preordered``` which will return descendants,
|
343
|
-
[pre-ordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order).
|
344
|
-
|
345
|
-
* ```node1.prepend_sibling(node2)``` which will
|
346
|
-
1. set ```node2``` to the same parent as ```node1```,
|
347
|
-
2. set ```node2```'s order column to 1 less than ```node1```'s value, and
|
348
|
-
3. decrement the order_column of all children of node1's parents whose order_column is <>>= node2's new value by 1.
|
349
|
-
|
350
|
-
* ```node1.append_sibling(node2)``` which will
|
351
|
-
1. set ```node2``` to the same parent as ```node1```,
|
352
|
-
2. set ```node2```'s order column to 1 more than ```node1```'s value, and
|
353
|
-
3. increment the order_column of all children of node1's parents whose order_column is >= node2's new value by 1.
|
354
|
-
|
355
|
-
```ruby
|
356
|
-
|
357
|
-
root = OrderedTag.create(:name => "root")
|
358
|
-
a = OrderedTag.create(:name => "a", :parent => "root")
|
359
|
-
b = OrderedTag.create(:name => "b")
|
360
|
-
c = OrderedTag.create(:name => "c")
|
361
|
-
|
362
|
-
# We have to call 'root.reload.children' because root won't be in sync with the database otherwise:
|
363
|
-
|
364
|
-
a.append_sibling(b)
|
365
|
-
root.reload.children.collect(&:name)
|
366
|
-
=> ["a", "b"]
|
367
|
-
|
368
|
-
a.prepend_sibling(b)
|
369
|
-
root.reload.children.collect(&:name)
|
370
|
-
=> ["b", "a"]
|
371
|
-
|
372
|
-
a.append_sibling(c)
|
373
|
-
root.reload.children.collect(&:name)
|
374
|
-
=> ["b", "a", "c"]
|
375
|
-
|
376
|
-
b.append_sibling(c)
|
377
|
-
root.reload.children.collect(&:name)
|
378
|
-
=> ["b", "c", "a"]
|
379
|
-
```
|
380
|
-
|
381
|
-
## Concurrency
|
382
|
-
|
383
|
-
Several methods, especially ```#rebuild``` and ```#find_or_create_by_path```, cannot run concurrently correctly.
|
384
|
-
```#find_or_create_by_path```, for example, may create duplicate nodes.
|
385
|
-
|
386
|
-
Database row-level locks work correctly with PostgreSQL, but MySQL's row-level locking is broken, and
|
387
|
-
erroneously reports deadlocks where there are none. To work around this, and have a consistent implementation
|
388
|
-
for both MySQL and PostgreSQL, [with_advisory_lock](https://github.com/mceachen/with_advisory_lock)
|
389
|
-
is used automatically to ensure correctness.
|
390
|
-
|
391
|
-
If you are already managing concurrency elsewhere in your application, and want to disable the use
|
392
|
-
of with_advisory_lock, pass ```:with_advisory_lock => false``` in the options hash:
|
393
|
-
|
394
|
-
```ruby
|
395
|
-
class Tag
|
396
|
-
acts_as_tree :with_advisory_lock => false
|
397
|
-
end
|
398
|
-
```
|
399
|
-
|
400
|
-
Note that you *will eventually have data corruption* if you disable advisory locks, write to your
|
401
|
-
database with multiple threads, and don't provide an alternative mutex.
|
402
|
-
|
403
|
-
|
404
|
-
## FAQ
|
405
|
-
|
406
|
-
### Does this gem support multiple parents?
|
407
|
-
|
408
|
-
No. This gem's API is based on the assumption that each node has either 0 or 1 parent.
|
409
|
-
|
410
|
-
The underlying closure tree structure will support multiple parents, but there would be many
|
411
|
-
breaking-API changes to support it. I'm open to suggestions and pull requests.
|
412
|
-
|
413
|
-
### How do I use this with test fixtures?
|
414
|
-
|
415
|
-
Test fixtures aren't going to be running your ```after_save``` hooks after inserting all your
|
416
|
-
fixture data, so you need to call ```.rebuild!``` before your test runs. There's an example in
|
417
|
-
the spec ```tag_spec.rb```:
|
418
|
-
|
419
|
-
```ruby
|
420
|
-
describe "Tag with fixtures" do
|
421
|
-
fixtures :tags
|
422
|
-
before :each do
|
423
|
-
Tag.rebuild! # <- required if you use fixtures
|
424
|
-
end
|
425
|
-
```
|
426
|
-
|
427
|
-
**However, if you're just starting with Rails, may I humbly suggest you adopt a factory library**,
|
428
|
-
rather than using fixtures? [Lots of people have written about this already](https://www.google.com/search?q=fixtures+versus+factories).
|
429
|
-
|
430
|
-
|
431
|
-
## Testing
|
432
|
-
|
433
|
-
Closure tree is [tested under every combination](http://travis-ci.org/#!/mceachen/closure_tree) of
|
434
|
-
|
435
|
-
* Ruby 1.8.7 and Ruby 1.9.3
|
436
|
-
* The latest Rails 3.0, 3.1, and 3.2 branches, and
|
437
|
-
* MySQL and PostgreSQL. SQLite works in a single-threaded environment.
|
438
|
-
|
439
|
-
Assuming you're using [rbenv](https://github.com/sstephenson/rbenv), you can use ```tests.sh``` to
|
440
|
-
run the test matrix locally.
|
441
|
-
|
442
|
-
Parallelism is not tested with Rails 3.0.x nor 3.1.x due to this
|
443
|
-
[known issue](https://github.com/rails/rails/issues/7538).
|
444
|
-
|
445
|
-
## Change log
|
446
|
-
|
447
|
-
### 3.10.0
|
448
|
-
|
449
|
-
* Added ```#roots_and_descendants_preordered```.
|
450
|
-
Thanks for the suggestion, [Leonel Galan](https://github.com/leonelgalan)!
|
451
|
-
|
452
|
-
### 3.9.0
|
453
|
-
|
454
|
-
* Added ```.child_ids```.
|
455
|
-
* Removed ```dependent => destroy``` on the descendant_hierarchy and ancestor_hierarchy collections
|
456
|
-
(they were a mistake).
|
457
|
-
* Clarified documentation for creation and child associations.
|
458
|
-
Because ```Tag.create!(:parent => ...)``` requires a ```.reload```, I removed it as an example.
|
459
|
-
|
460
|
-
All three of these improvements were suggested by Andrew Bromwich. Thanks!
|
461
|
-
|
462
|
-
### 3.8.2
|
463
|
-
|
464
|
-
* find_by_path uses 1 SELECT now. BOOM.
|
465
|
-
|
466
|
-
### 3.8.1
|
467
|
-
|
468
|
-
* Double-check locking for find_or_create_by_path
|
469
|
-
|
470
|
-
### 3.8.0
|
471
|
-
|
472
|
-
* Support for preordered descendants. This requires a numeric sort order column.
|
473
|
-
Resolves [feature request 38](https://github.com/mceachen/closure_tree/issues/38).
|
474
|
-
* Moved modules from ```acts_as_tree``` into separate files
|
475
|
-
|
476
|
-
### 3.7.3
|
477
|
-
|
478
|
-
Due to MySQL's inability to lock rows properly, I've switched to advisory_locks for
|
479
|
-
all write paths. This will prevent deadlocks, addressing
|
480
|
-
[issue 41](https://github.com/mceachen/closure_tree/issues/41).
|
481
|
-
|
482
|
-
### 3.7.2
|
483
|
-
|
484
|
-
* Support for UUID primary keys. Addresses
|
485
|
-
[issue 40](https://github.com/mceachen/closure_tree/issues/40). Thanks for the pull request,
|
486
|
-
[Julien](https://github.com/calexicoz)!
|
487
|
-
|
488
|
-
### 3.7.1
|
489
|
-
|
490
|
-
* Moved requires into ActiveSupport.on_load
|
491
|
-
* Added ```require 'with_advisory_lock'```
|
492
|
-
|
493
|
-
### 3.7.0
|
494
|
-
|
495
|
-
**Thread safety!**
|
496
|
-
* [Advisory locks](https://github.com/mceachen/with_advisory_lock) were
|
497
|
-
integrated with the class-level ```find_or_create_by_path``` and ```rebuild!```.
|
498
|
-
* Pessimistic locking is used by the instance-level ```find_or_create_by_path```.
|
499
|
-
|
500
|
-
### 3.6.9
|
501
|
-
|
502
|
-
* [Don Morrison](https://github.com/elskwid) massaged the [#hash_tree](#nested-hashes) query to
|
503
|
-
be more efficient, and found a bug in ```hash_tree```'s query that resulted in duplicate rows,
|
504
|
-
wasting time on the ruby side.
|
505
|
-
|
506
|
-
### 3.6.7
|
507
|
-
|
508
|
-
* Added workaround for ActiveRecord::Observer usage pre-db-creation. Addresses
|
509
|
-
[issue 32](https://github.com/mceachen/closure_tree/issues/32).
|
510
|
-
Thanks, [Don Morrison](https://github.com/elskwid)!
|
511
|
-
|
512
|
-
### 3.6.6
|
513
|
-
|
514
|
-
* Added support for Rails 4's [strong parameter](https://github.com/rails/strong_parameters).
|
515
|
-
Thanks, [James Miller](https://github.com/bensie)!
|
516
|
-
|
517
|
-
### 3.6.5
|
518
|
-
|
519
|
-
* Use ```quote_table_name``` instead of ```quote_column_name```. Addresses
|
520
|
-
[issue 29](https://github.com/mceachen/closure_tree/issues/29). Thanks,
|
521
|
-
[Marcello Barnaba](https://github.com/vjt)!
|
522
|
-
|
523
|
-
### 3.6.4
|
524
|
-
|
525
|
-
* Use ```.pluck``` when available for ```.ids_from```. Addresses
|
526
|
-
[issue 26](https://github.com/mceachen/closure_tree/issues/26). Thanks,
|
527
|
-
[Chris Sturgill](https://github.com/sturgill)!
|
528
|
-
|
529
|
-
### 3.6.3
|
530
|
-
|
531
|
-
* Fixed [issue 24](https://github.com/mceachen/closure_tree/issues/24), which optimized ```#hash_tree```
|
532
|
-
for roots. Thanks, [Saverio Trioni](https://github.com/rewritten)!
|
533
|
-
|
534
|
-
### 3.6.2
|
535
|
-
|
536
|
-
* Fixed [issue 23](https://github.com/mceachen/closure_tree/issues/23), which added support for ```#siblings```
|
537
|
-
when sort_order wasn't specified. Thanks, [Gary Greyling](https://github.com/garygreyling)!
|
538
|
-
|
539
|
-
### 3.6.1
|
540
|
-
|
541
|
-
* Fixed [issue 20](https://github.com/mceachen/closure_tree/issues/20), which affected
|
542
|
-
deterministic ordering when siblings where different STI classes. Thanks, [edwinramirez](https://github.com/edwinramirez)!
|
543
|
-
|
544
|
-
### 3.6.0
|
545
|
-
|
546
|
-
Added support for:
|
547
|
-
* ```:hierarchy_class_name``` as an option
|
548
|
-
* ActiveRecord::Base.table_name_prefix
|
549
|
-
* ActiveRecord::Base.table_name_suffix
|
550
|
-
|
551
|
-
This addresses [issue 21](https://github.com/mceachen/closure_tree/issues/21). Thanks, [Judd Blair](https://github.com/juddblair)!
|
552
|
-
|
553
|
-
### 3.5.2
|
554
|
-
|
555
|
-
* Added ```find_all_by_generation```
|
556
|
-
for [feature request 17](https://github.com/mceachen/closure_tree/issues/17).
|
557
|
-
|
558
|
-
### 3.4.2
|
559
|
-
|
560
|
-
* Fixed [issue 18](https://github.com/mceachen/closure_tree/issues/18), which affected
|
561
|
-
append_node/prepend_node ordering when the first node didn't have an explicit order_by value
|
562
|
-
|
563
|
-
### 3.4.1
|
564
|
-
|
565
|
-
* Reverted .gemspec mistake that changed add_development_dependency to add_runtime_dependency
|
566
|
-
|
567
|
-
### 3.4.0
|
568
|
-
|
569
|
-
Fixed [issue 15](https://github.com/mceachen/closure_tree/issues/15):
|
570
|
-
* "parent" is now attr_accessible, which adds support for constructor-provided parents.
|
571
|
-
* updated readme accordingly
|
572
|
-
|
573
|
-
### 3.3.2
|
574
|
-
|
575
|
-
* Merged calebphillips' patch for a more efficient leaves query
|
576
|
-
|
577
|
-
### 3.3.1
|
578
|
-
|
579
|
-
* Added support for partially-unsaved hierarchies [issue 13](https://github.com/mceachen/closure_tree/issues/13):
|
580
|
-
```
|
581
|
-
a = Tag.new(name: "a")
|
582
|
-
b = Tag.new(name: "b")
|
583
|
-
a.children << b
|
584
|
-
a.save
|
585
|
-
```
|
586
|
-
|
587
|
-
### 3.3.0
|
588
|
-
|
589
|
-
* Added [```hash_tree```](#nested-hashes).
|
590
|
-
|
591
|
-
### 3.2.1
|
592
|
-
|
593
|
-
* Added ```ancestor_ids```, ```descendant_ids```, and ```sibling_ids```
|
594
|
-
* Added example spec to solve [issue 9](https://github.com/mceachen/closure_tree/issues/9)
|
595
|
-
|
596
|
-
### 3.2.0
|
597
|
-
|
598
|
-
* Added support for deterministic ordering of nodes.
|
599
|
-
|
600
|
-
### 3.1.0
|
601
|
-
|
602
|
-
* Switched to using ```has_many :though``` rather than ```has_and_belongs_to_many```
|
603
|
-
|
604
|
-
### 3.0.4
|
605
|
-
|
606
|
-
* Merged [pull request](https://github.com/mceachen/closure_tree/pull/8) to fix ```.siblings``` and ```.self_and_siblings```
|
607
|
-
(Thanks, [eljojo](https://github.com/eljojo)!)
|
608
|
-
|
609
|
-
### 3.0.3
|
610
|
-
|
611
|
-
* Added support for ActiveRecord's whitelist_attributes
|
612
|
-
(Make sure you read [the Rails Security Guide](http://guides.rubyonrails.org/security.html), and
|
613
|
-
enable ```config.active_record.whitelist_attributes``` in your ```config/application.rb``` ASAP!)
|
614
|
-
|
615
|
-
### 3.0.2
|
616
|
-
|
617
|
-
* Fix for ancestry-loop detection (performed by a validation, not through raising an exception in before_save)
|
618
|
-
|
619
|
-
### 3.0.1
|
620
|
-
|
621
|
-
* Support 3.2.0's fickle deprecation of InstanceMethods (Thanks, [jheiss](https://github.com/mceachen/closure_tree/pull/5))!
|
622
|
-
|
623
|
-
### 3.0.0
|
624
|
-
|
625
|
-
* Support for polymorphic trees
|
626
|
-
* ```find_by_path``` and ```find_or_create_by_path``` signatures changed to support constructor attributes
|
627
|
-
* tested against Rails 3.1.3
|
628
|
-
|
629
|
-
### 2.0.0
|
630
|
-
|
631
|
-
* Had to increment the major version, as rebuild! will need to be called by prior consumers to support the new ```leaves``` class and instance methods.
|
632
|
-
* Tag deletion is supported now along with ```:dependent => :destroy``` and ```:dependent => :delete_all```
|
633
|
-
* Switched from default rails plugin directory structure to rspec
|
634
|
-
* Support for running specs under different database engines: ```export DB ; for DB in sqlite3 mysql postgresql ; do rake ; done```
|
635
|
-
|
636
|
-
## Thanks to
|
637
|
-
|
638
|
-
* https://github.com/collectiveidea/awesome_nested_set
|
639
|
-
* https://github.com/patshaughnessy/class_factory
|
640
|
-
* JetBrains, which provides an [open-source license](http://www.jetbrains.com/ruby/buy/buy.jsp#openSource) to
|
641
|
-
[RubyMine](http://www.jetbrains.com/ruby/features/) for the development of this project.
|
@@ -1,26 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'bundler/setup'
|
3
|
-
rescue LoadError
|
4
|
-
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
-
end
|
6
|
-
|
7
|
-
Bundler::GemHelper.install_tasks
|
8
|
-
|
9
|
-
require 'yard'
|
10
|
-
YARD::Rake::YardocTask.new do |t|
|
11
|
-
t.files = ['lib/**/*.rb', 'README.md']
|
12
|
-
end
|
13
|
-
|
14
|
-
require "rspec/core/rake_task"
|
15
|
-
RSpec::Core::RakeTask.new(:spec)
|
16
|
-
|
17
|
-
task :default => :spec
|
18
|
-
|
19
|
-
task :specs_with_db_ixes do
|
20
|
-
[["", ""], ["db_prefix_", ""], ["", "_db_suffix"], ["abc_", "_123"]].each do |prefix, suffix|
|
21
|
-
fail unless system("rake spec DB_PREFIX=#{prefix} DB_SUFFIX=#{suffix}")
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# Run the specs using all the different database engines:
|
26
|
-
# for DB in sqlite3 mysql postgresql ; do rake ; done
|