closure_tree 3.9.0 → 3.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -0
- data/lib/closure_tree/deterministic_ordering.rb +25 -15
- data/lib/closure_tree/numeric_deterministic_ordering.rb +28 -0
- data/lib/closure_tree/version.rb +1 -1
- data/spec/label_spec.rb +36 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA512:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e94fc2a743703667fdfdc150ad5d24ea7799956355191476d15856f81213154f4f2cc8a0981ca978ad80278ccc1201b38ccd9143e1ec00e1ffa243a238ad1d8a
|
4
|
+
data.tar.gz: b5c221f82dbf36750c90a834632b550baa0b2e7d82bc3d9f41621716958e0ed26eb29be8ab480800ff5509b0dafd207dfef558f2079189a5b4f1be8b9eea6809
|
5
5
|
SHA1:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f2021ccd1462a82b2735a5b28219c4d870a69a3
|
7
|
+
data.tar.gz: e8559d6b2aeed3aa0dffb07998b3655972ad9b8b
|
data/README.md
CHANGED
@@ -278,6 +278,27 @@ class WhereTag < Tag ; end
|
|
278
278
|
class WhatTag < Tag ; end
|
279
279
|
```
|
280
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
|
+
|
281
302
|
## Deterministic ordering
|
282
303
|
|
283
304
|
By default, children will be ordered by your database engine, which may not be what you want.
|
@@ -315,6 +336,9 @@ When you enable ```order```, you'll also have the following new methods injected
|
|
315
336
|
|
316
337
|
If your ```order``` column is an integer attribute, you'll also have these:
|
317
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
|
+
|
318
342
|
* ```node1.self_and_descendants_preordered``` which will return descendants,
|
319
343
|
[pre-ordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order).
|
320
344
|
|
@@ -420,6 +444,11 @@ Parallelism is not tested with Rails 3.0.x nor 3.1.x due to this
|
|
420
444
|
|
421
445
|
## Change log
|
422
446
|
|
447
|
+
### 3.10.0
|
448
|
+
|
449
|
+
* Added ```#roots_and_descendants_preordered```.
|
450
|
+
Thanks for the suggestion, [Leonel Galan](https://github.com/leonelgalan)!
|
451
|
+
|
423
452
|
### 3.9.0
|
424
453
|
|
425
454
|
* Added ```.child_ids```.
|
@@ -1,17 +1,33 @@
|
|
1
1
|
module ClosureTree
|
2
2
|
module DeterministicOrdering
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassAndInstanceMethods
|
6
|
+
def order_column
|
7
|
+
o = order_option
|
8
|
+
o.split(' ', 2).first if o
|
9
|
+
end
|
10
|
+
|
11
|
+
def require_order_column
|
12
|
+
raise ":order value, '#{order_option}', isn't a column" if order_column.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def order_column_sym
|
16
|
+
require_order_column
|
17
|
+
order_column.to_sym
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
|
20
|
+
def quoted_order_column(include_table_name = true)
|
21
|
+
require_order_column
|
22
|
+
prefix = include_table_name ? "#{quoted_table_name}." : ""
|
23
|
+
"#{prefix}#{connection.quote_column_name(order_column)}"
|
24
|
+
end
|
10
25
|
end
|
11
26
|
|
12
|
-
|
13
|
-
|
14
|
-
|
27
|
+
include ClassAndInstanceMethods
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
include ClassAndInstanceMethods
|
15
31
|
end
|
16
32
|
|
17
33
|
def order_value
|
@@ -22,12 +38,6 @@ module ClosureTree
|
|
22
38
|
write_attribute(order_column_sym, new_order_value)
|
23
39
|
end
|
24
40
|
|
25
|
-
def quoted_order_column(include_table_name = true)
|
26
|
-
require_order_column
|
27
|
-
prefix = include_table_name ? "#{quoted_table_name}." : ""
|
28
|
-
"#{prefix}#{connection.quote_column_name(order_column)}"
|
29
|
-
end
|
30
|
-
|
31
41
|
def siblings_before
|
32
42
|
siblings.where(["#{quoted_order_column} < ?", order_value])
|
33
43
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# This module is only included if the order column is an integer.
|
2
2
|
module ClosureTree
|
3
3
|
module DeterministicNumericOrdering
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
4
6
|
def self_and_descendants_preordered
|
5
7
|
# TODO: raise NotImplementedError if sort_order is not numeric and not null?
|
6
8
|
h = connection.select_one(<<-SQL)
|
@@ -24,6 +26,32 @@ module ClosureTree
|
|
24
26
|
self_and_descendants.joins(join_sql).group("#{quoted_table_name}.id").reorder(order_by)
|
25
27
|
end
|
26
28
|
|
29
|
+
module ClassMethods
|
30
|
+
def roots_and_descendants_preordered
|
31
|
+
h = connection.select_one(<<-SQL)
|
32
|
+
SELECT
|
33
|
+
count(*) as total_descendants,
|
34
|
+
max(generations) as max_depth
|
35
|
+
FROM #{quoted_hierarchy_table_name}
|
36
|
+
SQL
|
37
|
+
join_sql = <<-SQL
|
38
|
+
JOIN #{quoted_hierarchy_table_name} anc_hier
|
39
|
+
ON anc_hier.descendant_id = #{quoted_table_name}.id
|
40
|
+
JOIN #{quoted_table_name} anc
|
41
|
+
ON anc.id = anc_hier.ancestor_id
|
42
|
+
JOIN (
|
43
|
+
SELECT descendant_id, max(generations) AS max_depth
|
44
|
+
FROM #{quoted_hierarchy_table_name}
|
45
|
+
GROUP BY 1
|
46
|
+
) AS depths ON depths.descendant_id = anc.id
|
47
|
+
SQL
|
48
|
+
node_score = "(1 + anc.#{quoted_order_column(false)}) * " +
|
49
|
+
"power(#{h['total_descendants']}, #{h['max_depth'].to_i + 1} - depths.max_depth)"
|
50
|
+
order_by = "sum(#{node_score})"
|
51
|
+
joins(join_sql).group("#{quoted_table_name}.id").reorder(order_by)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
27
55
|
def append_sibling(sibling_node, use_update_all = true)
|
28
56
|
add_sibling(sibling_node, use_update_all, true)
|
29
57
|
end
|
data/lib/closure_tree/version.rb
CHANGED
data/spec/label_spec.rb
CHANGED
@@ -19,7 +19,7 @@ def create_label_tree
|
|
19
19
|
Label.update_all("sort_order = id")
|
20
20
|
end
|
21
21
|
|
22
|
-
def create_preorder_tree
|
22
|
+
def create_preorder_tree(suffix = "")
|
23
23
|
%w(
|
24
24
|
a/l/n/r
|
25
25
|
a/l/n/q
|
@@ -30,16 +30,18 @@ def create_preorder_tree
|
|
30
30
|
a/b/c/d/g
|
31
31
|
a/b/c/d/f
|
32
32
|
a/b/c/d/e
|
33
|
-
).shuffle.each { |ea| Label.find_or_create_by_path(ea.split
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
ea.
|
33
|
+
).shuffle.each { |ea| Label.find_or_create_by_path(ea.split('/').collect { |ea| "#{ea}#{suffix}" }) }
|
34
|
+
|
35
|
+
Label.roots.each_with_index do |root, root_idx|
|
36
|
+
root.order_value = root_idx
|
37
|
+
root.save!
|
38
|
+
root.self_and_descendants.each do |ea|
|
39
|
+
ea.children.to_a.sort_by(&:name).each_with_index do |ea, idx|
|
40
|
+
ea.order_value = idx
|
40
41
|
ea.save!
|
41
42
|
end
|
42
43
|
end
|
44
|
+
end
|
43
45
|
end
|
44
46
|
|
45
47
|
describe Label do
|
@@ -113,7 +115,24 @@ describe Label do
|
|
113
115
|
end
|
114
116
|
end
|
115
117
|
end
|
118
|
+
|
119
|
+
it "supports children << and add_child" do
|
120
|
+
a = EventLabel.create!(:name => "a")
|
121
|
+
b = DateLabel.new(:name => "b")
|
122
|
+
a.children << b
|
123
|
+
c = Label.new(:name => "c")
|
124
|
+
b.add_child(c)
|
125
|
+
|
126
|
+
a.self_and_descendants.collect do |ea|
|
127
|
+
ea.class
|
128
|
+
end.should == [EventLabel, DateLabel, Label]
|
129
|
+
|
130
|
+
a.self_and_descendants.collect do |ea|
|
131
|
+
ea.name
|
132
|
+
end.should == %w(a b c)
|
133
|
+
end
|
116
134
|
end
|
135
|
+
|
117
136
|
context "find_all_by_generation" do
|
118
137
|
before :all do
|
119
138
|
delete_all_labels
|
@@ -318,13 +337,20 @@ describe Label do
|
|
318
337
|
end
|
319
338
|
end
|
320
339
|
|
321
|
-
context "
|
340
|
+
context "preorder" do
|
322
341
|
it "returns descendants in proper order" do
|
323
342
|
delete_all_labels
|
324
343
|
create_preorder_tree
|
325
344
|
a = Label.root
|
326
345
|
a.name.should == "a"
|
327
|
-
|
346
|
+
expected = ('a'..'r').to_a
|
347
|
+
a.self_and_descendants_preordered.collect { |ea| ea.name }.should == expected
|
348
|
+
Label.roots_and_descendants_preordered.collect { |ea| ea.name }.should == expected
|
349
|
+
create_preorder_tree("1")
|
350
|
+
# Should be no change:
|
351
|
+
a.reload.self_and_descendants_preordered.collect { |ea| ea.name }.should == expected
|
352
|
+
expected += ('a'..'r').collect { |ea| "#{ea}1" }
|
353
|
+
Label.roots_and_descendants_preordered.collect { |ea| ea.name }.should == expected
|
328
354
|
end
|
329
355
|
end unless ENV["DB"] == "sqlite"
|
330
356
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: closure_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-13 00:00:00 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|