lalala 4.0.0.dev.136 → 4.0.0.dev.141
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.
- 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 [](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
|