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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA512:
3
- metadata.gz: 4eba6a7034297fd15958c27428782adc3a1b36abc1f0723039583418c33eb08377e3b5cfa744aae985cdd2b1b2cbd91138486e2a0591c0ff2f9c1fcb9893361d
4
- data.tar.gz: 8f8f339d462f846bfb59c260756d94ad8328a9bea1f4bf9ad2d9d00317f05efd7f8d099eee2b1c1a8e661ccef827fcbb46cb9f5a22f9e0f816bd5b8118fea9c4
3
+ metadata.gz: e94fc2a743703667fdfdc150ad5d24ea7799956355191476d15856f81213154f4f2cc8a0981ca978ad80278ccc1201b38ccd9143e1ec00e1ffa243a238ad1d8a
4
+ data.tar.gz: b5c221f82dbf36750c90a834632b550baa0b2e7d82bc3d9f41621716958e0ed26eb29be8ab480800ff5509b0dafd207dfef558f2079189a5b4f1be8b9eea6809
5
5
  SHA1:
6
- metadata.gz: f04e325774ddd75fddfaeb47753095903350aed8
7
- data.tar.gz: 66ed5c26efe5cac30d3bb3e3d1d128ae67d4e184
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
- def order_column
4
- o = order_option
5
- o.split(' ', 2).first if o
6
- end
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
- def require_order_column
9
- raise ":order value, '#{order_option}', isn't a column" if order_column.nil?
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
- def order_column_sym
13
- require_order_column
14
- order_column.to_sym
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
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "3.9.0" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = "3.10.0" unless defined?(::ClosureTree::VERSION)
3
3
  end
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
- a = Label.find_by_path(["a"])
35
- a.order_value = 0
36
- a.save!
37
- a.self_and_descendants.each do |ea|
38
- ea.children.to_a.sort_by(&:name).each_with_index do |ea, idx|
39
- ea.order_value = idx
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 ".self_and_descendants_preordered" do
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
- a.self_and_descendants_preordered.collect { |ea| ea.name }.should == ('a'..'r').to_a
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.9.0
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-06 00:00:00 Z
12
+ date: 2013-03-13 00:00:00 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord