closure_tree 3.9.0 → 3.10.0
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/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
|