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 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