closure_tree 4.2.0 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MWJmNzNmNzk4OGM2YmJlYWIzYjdkNTE2Yzk2YjZlNWM1ZDgwMTlhYg==
5
- data.tar.gz: !binary |-
6
- YjcwMmY4MTcwMTRlZmJmODAwOGViNGMyNzU4NGQ5MGY4ZDU1MjQ5Mg==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- NGRlYjk1MDU4NzdiYzQzNzIzZGQxZTZjMjc1NDA4NzQ0YTAwYTExYTZlM2E4
10
- YWM0OTY4NzQ2ZmI4NjA2NDc4MWNjN2RlODIwNTY0ZWMwZGJmNzUwNjk0ZmE3
11
- M2NlYmU1MTk5MzViN2ExZmIwYjBmYTU5N2JhMGVmOGE5N2U3NmQ=
12
- data.tar.gz: !binary |-
13
- NmE0NGFjNmZiMDAwM2U5MTc3Y2E3OWIyMGFiNTEzYWI3MTA1MmRlYWI5MzQw
14
- OGMxNGQwODZmY2ZiNzQwZDhmNGUzYTEwNjNkMTZlOTNjZTE5ZTA2NGNhNTg5
15
- MTJjMjgxODk1NzdmOWQ1NzhhODA1MWZhYjNlYzUyOGUxNmVlYTk=
2
+ SHA1:
3
+ metadata.gz: 78c02789c9a49942f41eea1ce0ce205d38f1df9a
4
+ data.tar.gz: dcad9031af038ee30c9ec61a0f60941c028536f2
5
+ SHA512:
6
+ metadata.gz: 31034ae207d81917f3e60d69312c80561d21382123be62fb0b753db933f420be6007b5d69438c37b2ecd9800a38310ee184c3f9b570312d7d27cc0adc03971ab
7
+ data.tar.gz: 2068b5ecf26b41e96cb339a0e14427e6e6baa0527180dce781bd5f10d179662ac8b45b5f9232d5a2413fbf43c72f3a7ee42a0f2f745f95f2851711827b8a1bb3
data/README.md CHANGED
@@ -8,6 +8,7 @@ and tracking user referrals.
8
8
  [![Build Status](https://secure.travis-ci.org/mceachen/closure_tree.png?branch=master)](http://travis-ci.org/mceachen/closure_tree)
9
9
  [![Gem Version](https://badge.fury.io/rb/closure_tree.png)](http://rubygems.org/gems/closure_tree)
10
10
  [![Code Climate](https://codeclimate.com/github/mceachen/closure_tree.png)](https://codeclimate.com/github/mceachen/closure_tree)
11
+ [![Coverage Status](https://coveralls.io/repos/mceachen/closure_tree/badge.png?branch=master)](https://coveralls.io/r/mceachen/closure_tree?branch=master)
11
12
 
12
13
  Substantially more efficient than
13
14
  [ancestry](https://github.com/stefankroes/ancestry) and
@@ -471,6 +472,10 @@ Parallelism is not tested with Rails 3.0.x nor 3.1.x due to this
471
472
 
472
473
  ## Change log
473
474
 
475
+ ### 4.2.1
476
+
477
+ * Deleting from NumericDeterministicOrdering doesn't create sort order gaps anymore.
478
+
474
479
  ### 4.2.0
475
480
 
476
481
  * Added ```with_ancestor(*ancestors)```. Thanks for the idea, [Matt](https://github.com/mgornick)!
@@ -479,6 +484,7 @@ Parallelism is not tested with Rails 3.0.x nor 3.1.x due to this
479
484
  Thanks for the help, [Judd Blair](https://github.com/juddblair)!
480
485
  **Please note that this changes prior behavior—test your code with this new version!**
481
486
  * ```ct_advisory_lock``` was moved into the ```_ct``` support class, to reduce model method pollution
487
+ * Moved a bunch of code into more focused piles of module mixins
482
488
 
483
489
  ### 4.1.0
484
490
 
@@ -52,6 +52,7 @@ module ClosureTree
52
52
  SQL
53
53
  end
54
54
  children.each { |c| c.rebuild! }
55
+ _ct_reorder_children if _ct.order_is_numeric?
55
56
  end
56
57
  end
57
58
 
@@ -33,9 +33,6 @@ module ClosureTree
33
33
  :foreign_key => "ancestor_id",
34
34
  :order => order_by_generations)
35
35
 
36
- # TODO: FIXME: this collection currently ignores sort_order
37
- # (because the quoted_table_named would need to be joined in to get to the order column)
38
-
39
36
  has_many :self_and_descendants, *_ct.has_many_with_order_option(
40
37
  :through => :descendant_hierarchies,
41
38
  :source => :descendant,
@@ -127,8 +124,25 @@ module ClosureTree
127
124
  read_attribute(_ct.parent_column_sym)
128
125
  end
129
126
 
127
+ def _ct_quoted_parent_id
128
+ _ct.quoted_value(_ct_parent_id)
129
+ end
130
+
130
131
  def _ct_id
131
132
  read_attribute(_ct.model_class.primary_key)
132
133
  end
134
+
135
+ def _ct_quoted_id
136
+ _ct.quoted_value(_ct_id)
137
+ end
138
+
139
+ def _ct_update_column(column, value)
140
+ if respond_to?(:update_column)
141
+ update_column(column, value)
142
+ else
143
+ # This will run callbacks, but it's better than failing outright:
144
+ update_attribute(column, value)
145
+ end
146
+ end
133
147
  end
134
148
  end
@@ -1,8 +1,26 @@
1
+ require 'active_support/concern'
2
+
1
3
  # This module is only included if the order column is an integer.
2
4
  module ClosureTree
3
5
  module NumericDeterministicOrdering
4
6
  extend ActiveSupport::Concern
5
7
 
8
+ included do
9
+ after_destroy :_ct_reorder_after_destroy
10
+ end
11
+
12
+ def _ct_reorder_after_destroy
13
+ _ct_reorder_siblings
14
+ end
15
+
16
+ def _ct_reorder_siblings(minimum_sort_order_value = nil, delta = 0)
17
+ _ct.reorder_with_parent_id(_ct_parent_id, minimum_sort_order_value, delta)
18
+ end
19
+
20
+ def _ct_reorder_children(minimum_sort_order_value = nil, delta = 0)
21
+ _ct.reorder_with_parent_id(_ct_id, minimum_sort_order_value, delta)
22
+ end
23
+
6
24
  def self_and_descendants_preordered
7
25
  # TODO: raise NotImplementedError if sort_order is not numeric and not null?
8
26
  h = _ct.connection.select_one(<<-SQL)
@@ -60,27 +78,41 @@ module ClosureTree
60
78
  add_sibling(sibling_node, false)
61
79
  end
62
80
 
63
- def add_sibling(sibling_node, add_after = true)
64
- fail "can't add self as sibling" if self == sibling_node
81
+ def add_sibling(sibling, add_after = true)
82
+ fail "can't add self as sibling" if self == sibling
65
83
  _ct.with_advisory_lock do
66
- if self.order_value.nil? || siblings_before.without(sibling_node).empty?
67
- update_attribute(:order_value, 0)
84
+ if self.order_value.nil?
85
+ # ergh, we don't know where we stand within the siblings, so establish that first:
86
+ _ct_reorder_siblings
87
+ reload # < because self.order_value changed
68
88
  end
69
- sibling_node.parent = self.parent
70
- starting_order_value = self.order_value.to_i
71
- to_reorder = siblings_after.without(sibling_node).to_a
72
- if add_after
73
- to_reorder.unshift(sibling_node)
74
- else
75
- to_reorder.unshift(self)
76
- sibling_node.update_attribute(:order_value, starting_order_value)
89
+ prior_sibling_parent = sibling.parent
90
+ if prior_sibling_parent == self.parent
91
+ # We have to adjust the prior siblings by moving sibling out of the way:
92
+ sibling._ct_update_column(_ct.parent_column_sym, nil)
93
+ if sibling.order_value && sibling.order_value < self.order_value
94
+ _ct_reorder_siblings(sibling.order_value, 0)
95
+ reload # < because self.order_value changed
96
+ end
77
97
  end
78
-
79
- to_reorder.each_with_index do |ea, idx|
80
- ea.update_attribute(:order_value, starting_order_value + idx + 1)
98
+ _ct_move_new_sibling(sibling, add_after)
99
+ if prior_sibling_parent && prior_sibling_parent != self.parent
100
+ prior_sibling_parent._ct_reorder_children
81
101
  end
82
- sibling_node.reload # because the parent may have changed.
102
+ sibling
103
+ end
104
+ end
105
+
106
+ def _ct_move_new_sibling(sibling, add_after)
107
+ _ct_reorder_siblings(self.order_value + 1, 1)
108
+ if add_after
109
+ sibling.order_value = self.order_value + 1
110
+ else
111
+ sibling.order_value = self.order_value
112
+ self.order_value += 1
113
+ self.save!
83
114
  end
115
+ parent.add_child(sibling) # <- this causes sibling to be saved.
84
116
  end
85
117
  end
86
118
  end
@@ -0,0 +1,63 @@
1
+ module ClosureTree
2
+ module NumericOrderSupport
3
+
4
+ def self.adapter_for_connection(connection)
5
+ case connection.adapter_name.downcase.to_sym
6
+ when :postgresql
7
+ ::ClosureTree::NumericOrderSupport::PostgreSQLAdapter
8
+ when :mysql, :mysql2
9
+ ::ClosureTree::NumericOrderSupport::MysqlAdapter
10
+ else
11
+ ::ClosureTree::NumericOrderSupport::GenericAdapter
12
+ end
13
+ end
14
+
15
+ module MysqlAdapter
16
+ def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil, delta = 0)
17
+ min_where = if minimum_sort_order_value
18
+ "AND #{quoted_order_column} >= #{minimum_sort_order_value}"
19
+ else
20
+ ""
21
+ end
22
+ connection.execute "SET @i = 0"
23
+ connection.execute <<-SQL
24
+ UPDATE #{quoted_table_name}
25
+ SET #{quoted_order_column} = (@i := @i + 1) + #{minimum_sort_order_value.to_i + delta - 1}
26
+ WHERE #{quoted_parent_column_name} = #{quoted_value(parent_id)} #{min_where}
27
+ ORDER BY #{order_by}
28
+ SQL
29
+ end
30
+ end
31
+
32
+ module PostgreSQLAdapter
33
+ def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil, delta = 0)
34
+ min_where = if minimum_sort_order_value
35
+ "AND #{quoted_order_column} >= #{minimum_sort_order_value}"
36
+ else
37
+ ""
38
+ end
39
+ connection.execute <<-SQL
40
+ UPDATE #{quoted_table_name}
41
+ SET #{quoted_order_column(false)} = t.seq + #{minimum_sort_order_value.to_i + delta - 1}
42
+ FROM (
43
+ SELECT #{quoted_id_column_name} AS id, row_number() OVER(ORDER BY #{order_by}) AS seq
44
+ FROM #{quoted_table_name}
45
+ WHERE #{quoted_parent_column_name} = #{quoted_value(parent_id)} #{min_where}) AS t
46
+ WHERE #{quoted_table_name}.#{quoted_id_column_name} = t.id
47
+ SQL
48
+ end
49
+ end
50
+
51
+ module GenericAdapter
52
+ def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil, delta = 0)
53
+ scope = model_class.where(parent_column_sym => parent_id)
54
+ if minimum_sort_order_value
55
+ scope = scope.where("#{quoted_order_column} >= #{minimum_sort_order_value}")
56
+ end
57
+ scope.each_with_index do |ea, idx|
58
+ ea.update_attribute(order_column_sym, idx + minimum_sort_order_value.to_i + delta)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,6 @@
1
1
  require 'closure_tree/support_flags'
2
2
  require 'closure_tree/support_attributes'
3
+ require 'closure_tree/numeric_order_support'
3
4
 
4
5
  module ClosureTree
5
6
  class Support
@@ -19,6 +20,9 @@ module ClosureTree
19
20
  :with_advisory_lock => true
20
21
  }.merge(options)
21
22
  raise IllegalArgumentException, "name_column can't be 'path'" if options[:name_column] == 'path'
23
+ if order_is_numeric?
24
+ extend NumericOrderSupport.adapter_for_connection(connection)
25
+ end
22
26
  end
23
27
 
24
28
  def hierarchy_class_for_model
@@ -58,13 +62,13 @@ module ClosureTree
58
62
 
59
63
  def with_order_option(opts)
60
64
  if order_option?
61
- opts[:order] = [opts[:order], options[:order]].compact.join(",")
65
+ opts[:order] = [opts[:order], order_by].compact.join(",")
62
66
  end
63
67
  opts
64
68
  end
65
69
 
66
70
  def scope_with_order(scope, additional_order_by = nil)
67
- order_option? ? scope.order(*([additional_order_by, options[:order]].compact)) : scope
71
+ order_option? ? scope.order(*([additional_order_by, order_by].compact)) : scope
68
72
  end
69
73
 
70
74
  # lambda-ize the order, but don't apply the default order_option
@@ -78,7 +82,7 @@ module ClosureTree
78
82
 
79
83
  def has_many_with_order_option(opts)
80
84
  if ActiveRecord::VERSION::MAJOR > 3
81
- order_options = [opts[:order], options[:order]].compact
85
+ order_options = [opts[:order], order_by].compact
82
86
  [lambda { order(order_options) }, opts.except(:order)]
83
87
  else
84
88
  [with_order_option(opts)]
@@ -1,6 +1,10 @@
1
+ require 'forwardable'
1
2
  module ClosureTree
2
3
  module SupportAttributes
3
4
 
5
+ extend Forwardable
6
+ def_delegators :model_class, :connection, :transaction, :table_name
7
+
4
8
  # This is the "topmost" class. This will only potentially not be ct_class if you are using STI.
5
9
  def base_class
6
10
  options[:base_class]
@@ -10,18 +14,14 @@ module ClosureTree
10
14
  @attribute_names ||= model_class.new.attributes.keys - model_class.protected_attributes.to_a
11
15
  end
12
16
 
13
- def connection
14
- model_class.connection
15
- end
16
-
17
- def table_name
18
- model_class.table_name
19
- end
20
-
21
17
  def quoted_table_name
22
18
  connection.quote_table_name(table_name)
23
19
  end
24
20
 
21
+ def quoted_value(value)
22
+ value.is_a?(Numeric) ? value : quote(value)
23
+ end
24
+
25
25
  def hierarchy_class_name
26
26
  options[:hierarchy_class_name] || model_class.to_s + "Hierarchy"
27
27
  end
@@ -69,8 +69,12 @@ module ClosureTree
69
69
  connection.quote_column_name name_column
70
70
  end
71
71
 
72
+ def order_by
73
+ options[:order]
74
+ end
75
+
72
76
  def order_column
73
- o = options[:order]
77
+ o = order_by
74
78
  if o.nil?
75
79
  nil
76
80
  elsif o.is_a?(String)
@@ -94,6 +98,5 @@ module ClosureTree
94
98
  prefix = include_table_name ? "#{quoted_table_name}." : ""
95
99
  "#{prefix}#{connection.quote_column_name(order_column)}"
96
100
  end
97
-
98
101
  end
99
102
  end
@@ -15,7 +15,7 @@ module ClosureTree
15
15
  end
16
16
 
17
17
  def order_option?
18
- !options[:order].nil?
18
+ order_by.present?
19
19
  end
20
20
 
21
21
  def order_is_numeric?
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = Gem::Version.new("4.2.0") unless defined?(::ClosureTree::VERSION)
2
+ VERSION = Gem::Version.new("4.2.1") unless defined?(::ClosureTree::VERSION)
3
3
  end
data/spec/label_spec.rb CHANGED
@@ -216,23 +216,29 @@ describe Label do
216
216
  @a = EventLabel.new(:name => "a")
217
217
  @b = DirectoryLabel.new(:name => "b")
218
218
  @c = DateLabel.new(:name => "c")
219
+ @d = Label.new(:name => "d")
219
220
  @parent.children << @a
220
221
  @a.append_sibling(@b)
221
222
  @b.append_sibling(@c)
223
+ @c.append_sibling(@d)
222
224
  end
223
225
 
224
- it "when inserted before" do
225
- @b.append_sibling(@a)
226
- # Have to reload because the sort_order will have changed out from under the references:
227
- @b.reload.sort_order.should be < @a.reload.sort_order
228
- @a.reload.sort_order.should be < @c.reload.sort_order
226
+ def children_name_and_order
227
+ @parent.reload.children.map { |ea| [ea.name, ea.sort_order] }
228
+ end
229
+
230
+ it "sort_orders properly" do
231
+ children_name_and_order.should == [['a', 0], ['b', 1], ['c', 2], ['d', 3]]
229
232
  end
230
233
 
231
234
  it "when inserted before" do
232
235
  @b.append_sibling(@a)
233
- # Have to reload because the sort_order will have changed out from under the references:
234
- @b.reload.sort_order.should be < @a.reload.sort_order
235
- @a.reload.sort_order.should be < @c.reload.sort_order
236
+ children_name_and_order.should == [['b', 0], ['a', 1], ['c', 2], ['d', 3]]
237
+ end
238
+
239
+ it "when inserted after" do
240
+ @a.append_sibling(@c)
241
+ children_name_and_order.should == [['a', 0], ['c', 1], ['b', 2], ['d', 3]]
236
242
  end
237
243
  end
238
244
 
@@ -243,116 +249,100 @@ describe Label do
243
249
  c = Label.create(:name => "c")
244
250
 
245
251
  a.append_sibling(b)
252
+ a.self_and_siblings.collect(&:name).should == %w(a b)
246
253
  root.reload.children.collect(&:name).should == %w(a b)
247
- root.reload.children.collect(&:sort_order).should == [0, 1]
254
+ root.children.collect(&:sort_order).should == [0, 1]
248
255
 
249
256
  a.prepend_sibling(b)
257
+ a.self_and_siblings.collect(&:name).should == %w(b a)
250
258
  root.reload.children.collect(&:name).should == %w(b a)
251
- root.reload.children.collect(&:sort_order).should == [0, 1]
259
+ root.children.collect(&:sort_order).should == [0, 1]
252
260
 
253
261
  a.append_sibling(c)
262
+ a.self_and_siblings.collect(&:name).should == %w(b a c)
254
263
  root.reload.children.collect(&:name).should == %w(b a c)
255
- root.reload.children.collect(&:sort_order).should == [0, 1, 2]
264
+ root.children.collect(&:sort_order).should == [0, 1, 2]
256
265
 
257
- b.append_sibling(c)
266
+ # We need to reload b because it was updated by a.append_sibling(c)
267
+ b.reload.append_sibling(c)
258
268
  root.reload.children.collect(&:name).should == %w(b c a)
259
- root.reload.children.collect(&:sort_order).should == [0, 1, 2]
260
- end
269
+ root.children.collect(&:sort_order).should == [0, 1, 2]
261
270
 
262
- context "Deterministic siblings sort with custom integer column" do
263
- delete_all_labels
264
- fixtures :labels
271
+ # We need to reload a because it was updated by b.append_sibling(c)
272
+ d = a.reload.append_sibling(Label.new(:name => "d"))
273
+ d.self_and_siblings.collect(&:name).should == %w(b c a d)
274
+ d.self_and_siblings.collect(&:sort_order).should == [0, 1, 2, 3]
275
+ end
265
276
 
277
+ context "#add_sibling" do
266
278
  before :each do
267
- Label.rebuild!
268
- end
269
-
270
- it "orders siblings_before and siblings_after correctly" do
271
- labels(:c16).self_and_siblings.to_a.should == [labels(:c16), labels(:c17), labels(:c18), labels(:c19)]
272
- labels(:c16).siblings_before.to_a.should == []
273
- labels(:c16).siblings_after.to_a.should == [labels(:c17), labels(:c18), labels(:c19)]
274
- end
275
-
276
- it "should prepend a node as a sibling of another node" do
277
- labels(:c16).prepend_sibling(labels(:c17))
278
- labels(:c16).self_and_siblings.to_a.should == [labels(:c17), labels(:c16), labels(:c18), labels(:c19)]
279
- labels(:c19).prepend_sibling(labels(:c16))
280
- labels(:c16).self_and_siblings.to_a.should == [labels(:c17), labels(:c18), labels(:c16), labels(:c19)]
281
- labels(:c16).siblings_before.to_a.should == [labels(:c17), labels(:c18)]
282
- labels(:c16).siblings_after.to_a.should == [labels(:c19)]
283
- end
284
-
285
- it "should prepend a node as a sibling of another node (!update_all)" do
286
- labels(:c16).prepend_sibling(labels(:c17))
287
- labels(:c16).self_and_siblings.to_a.should == [labels(:c17), labels(:c16), labels(:c18), labels(:c19)]
288
- labels(:c19).reload.prepend_sibling(labels(:c16).reload)
289
- labels(:c16).self_and_siblings.to_a.should == [labels(:c17), labels(:c18), labels(:c16), labels(:c19)]
290
- labels(:c16).siblings_before.to_a.should == [labels(:c17), labels(:c18)]
291
- labels(:c16).siblings_after.to_a.should == [labels(:c19)]
279
+ Label.delete_all
292
280
  end
293
281
 
294
- it "appends a node as a sibling of another node" do
295
- labels(:c19).append_sibling(labels(:c17))
296
- labels(:c16).self_and_siblings.to_a.should == [labels(:c16), labels(:c18), labels(:c19), labels(:c17)]
297
- labels(:c16).append_sibling(labels(:c19))
298
- labels(:c16).self_and_siblings.to_a.should == [labels(:c16), labels(:c19), labels(:c18), labels(:c17)]
299
- labels(:c16).siblings_before.to_a.should == []
300
- labels(:c16).siblings_after.to_a.should == labels(:c16).siblings.to_a
301
- end
302
-
303
- it "should move a node before another node (update_all)" do
304
- labels(:c2).ancestry_path.should == %w{a1 b2 c2}
305
- labels(:b2).prepend_sibling(labels(:c2))
306
- labels(:c2).ancestry_path.should == %w{a1 c2}
307
- labels(:c2).self_and_siblings.to_a.should == [labels(:b1), labels(:c2), labels(:b2)]
308
- labels(:c2).siblings_before.to_a.should == [labels(:b1)]
309
- labels(:c2).siblings_after.to_a.should == [labels(:b2)]
310
- labels(:b1).siblings_after.to_a.should == [labels(:c2), labels(:b2)]
311
- end
312
-
313
- it "should move a node after another node (update_all)" do
314
- labels(:c2).ancestry_path.should == %w{a1 b2 c2}
315
- labels(:b2).append_sibling(labels(:c2))
316
- labels(:c2).ancestry_path.should == %w{a1 c2}
317
- labels(:c2).self_and_siblings.to_a.should == [labels(:b1), labels(:b2), labels(:c2)]
282
+ it "should move a node before another node which has an uninitialized sort_order" do
283
+ f = Label.find_or_create_by_path %w(a b c d e fa)
284
+ f0 = f.prepend_sibling(Label.new(:name => "fb")) # < not alpha sort, so name shouldn't matter
285
+ f0.ancestry_path.should == %w(a b c d e fb)
286
+ f.siblings_before.to_a.should == [f0]
287
+ f0.siblings_before.should be_empty
288
+ f0.siblings_after.should == [f]
289
+ f.siblings_after.should be_empty
290
+ f0.self_and_siblings.should == [f0, f]
291
+ f.self_and_siblings.should == [f0, f]
292
+ end
293
+
294
+ it "should move a node to another tree" do
295
+ f1 = Label.find_or_create_by_path %w(a1 b1 c1 d1 e1 f1)
296
+ f2 = Label.find_or_create_by_path %w(a2 b2 c2 d2 e2 f2)
297
+ f1.add_sibling(f2)
298
+ f2.ancestry_path.should == %w(a1 b1 c1 d1 e1 f2)
299
+ f1.parent.children.should == [f1, f2]
300
+ end
301
+
302
+ it "should reorder old-parent siblings when a node moves to another tree" do
303
+ f1 = Label.find_or_create_by_path %w(a1 b1 c1 d1 e1 f1)
304
+ f2 = Label.find_or_create_by_path %w(a2 b2 c2 d2 e2 f2)
305
+ f3 = f2.prepend_sibling(Label.new(:name => "f3"))
306
+ f4 = f2.append_sibling(Label.new(:name => "f4"))
307
+ f1.add_sibling(f2)
308
+ f1.self_and_siblings.collect(&:sort_order).should == [0, 1]
309
+ f3.self_and_siblings.collect(&:sort_order).should == [0, 1]
310
+ f1.self_and_siblings.collect(&:name).should == %w(f1 f2)
311
+ f3.self_and_siblings.collect(&:name).should == %w(f3 f4)
318
312
  end
313
+ end
319
314
 
320
- it "should move a node before another node" do
321
- labels(:c2).ancestry_path.should == %w{a1 b2 c2}
322
- labels(:b2).prepend_sibling(labels(:c2))
323
- labels(:c2).ancestry_path.should == %w{a1 c2}
324
- labels(:c2).self_and_siblings.to_a.should == [labels(:b1), labels(:c2), labels(:b2)]
315
+ context "destructive reordering" do
316
+ before :each do
317
+ Label.delete_all
318
+ # to make sure sort_order isn't affected by additional nodes:
319
+ create_preorder_tree
320
+ @root = Label.create(:name => "root")
321
+ @a = @root.children.create!(:name => "a")
322
+ @b = @a.append_sibling(Label.new(:name => "b"))
323
+ @c = @b.append_sibling(Label.new(:name => "c"))
324
+ end
325
+ context "doesn't create sort order gaps from" do
326
+ it "from head" do
327
+ @a.destroy
328
+ @root.reload.children.should == [@b, @c]
329
+ @root.children.map { |ea| ea.sort_order }.should == [0, 1]
330
+ end
331
+ it "from mid" do
332
+ @b.destroy
333
+ @root.reload.children.should == [@a, @c]
334
+ @root.children.map { |ea| ea.sort_order }.should == [0, 1]
335
+ end
336
+ it "from tail" do
337
+ @c.destroy
338
+ @root.reload.children.should == [@a, @b]
339
+ @root.children.map { |ea| ea.sort_order }.should == [0, 1]
340
+ end
325
341
  end
326
-
327
- it "should move a node before another node which has an uninitialized sort_order" do
328
- labels(:f3).ancestry_path.should == %w{f3}
329
- labels(:e2).children << labels(:f3)
330
- labels(:f3).reload.ancestry_path.should == %w{a1 b2 c2 d2 e2 f3}
331
- labels(:f3).self_and_siblings.to_a.should == [labels(:f3)]
332
- labels(:f3).prepend_sibling labels(:f4)
333
- labels(:f3).siblings_before.to_a.should == [labels(:f4)]
334
- labels(:f3).self_and_siblings.to_a.should == [labels(:f4), labels(:f3)]
335
- end
336
-
337
- it "should move a node after another node which has an uninitialized sort_order" do
338
- labels(:f3).ancestry_path.should == %w{f3}
339
- labels(:e2).children << labels(:f3)
340
- labels(:f3).reload.ancestry_path.should == %w{a1 b2 c2 d2 e2 f3}
341
- labels(:f3).self_and_siblings.to_a.should == [labels(:f3)]
342
- labels(:f3).append_sibling labels(:f4)
343
- labels(:f3).siblings_after.to_a.should == [labels(:f4)]
344
- labels(:f3).self_and_siblings.to_a.should == [labels(:f3), labels(:f4)]
345
- end
346
-
347
- it "should move a node after another node" do
348
- labels(:c2).ancestry_path.should == %w{a1 b2 c2}
349
- labels(:b2).append_sibling(labels(:c2))
350
- labels(:c2).ancestry_path.should == %w{a1 c2}
351
- labels(:c2).self_and_siblings.to_a.should == [labels(:b1), labels(:b2), labels(:c2)]
352
- labels(:c2).append_sibling(labels(:e2))
353
- labels(:e2).self_and_siblings.to_a.should == [labels(:b1), labels(:b2), labels(:c2), labels(:e2)]
354
- labels(:a1).self_and_descendants.collect(&:name).should == %w(a1 b1 b2 c2 e2 d2 c1-six c1-seven c1-eight c1-nine)
355
- labels(:a1).leaves.collect(&:name).should =~ %w(b2 e2 d2 c1-six c1-seven c1-eight c1-nine)
342
+ it "shouldn't fail if all children are destroyed" do
343
+ roots = Label.roots.to_a
344
+ roots.each { |ea| ea.children.destroy_all }
345
+ Label.all.to_a.should =~ roots
356
346
  end
357
347
  end
358
348
 
data/spec/spec_helper.rb CHANGED
@@ -12,6 +12,8 @@ require 'rspec/rails/adapters'
12
12
  require 'rspec/rails/fixture_support'
13
13
  require 'closure_tree'
14
14
  require 'tmpdir'
15
+ require 'coveralls'
16
+ Coveralls.wear!
15
17
 
16
18
  #log = Logger.new(STDOUT)
17
19
  #log.sev_threshold = Logger::DEBUG
metadata CHANGED
@@ -1,167 +1,181 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 4.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-10 00:00:00.000000000 Z
11
+ date: 2013-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: 3.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: with_advisory_lock
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: 0.0.6
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.0.6
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: yard
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ! '>='
73
+ - - '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ! '>='
80
+ - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: fuubar
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ! '>='
87
+ - - '>='
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ! '>='
94
+ - - '>='
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec-rails
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ! '>='
101
+ - - '>='
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ! '>='
108
+ - - '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: mysql2
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ! '>='
115
+ - - '>='
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ! '>='
122
+ - - '>='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: pg
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ! '>='
129
+ - - '>='
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ! '>='
136
+ - - '>='
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: sqlite3
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ! '>='
143
+ - - '>='
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ! '>='
150
+ - - '>='
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: uuidtools
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - ! '>='
157
+ - - '>='
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - ! '>='
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: coveralls
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - '>='
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
167
181
  description: Easily and efficiently make your ActiveRecord model support hierarchies
@@ -179,6 +193,7 @@ files:
179
193
  - lib/closure_tree/hierarchy_maintenance.rb
180
194
  - lib/closure_tree/model.rb
181
195
  - lib/closure_tree/numeric_deterministic_ordering.rb
196
+ - lib/closure_tree/numeric_order_support.rb
182
197
  - lib/closure_tree/support.rb
183
198
  - lib/closure_tree/support_attributes.rb
184
199
  - lib/closure_tree/support_flags.rb
@@ -190,7 +205,6 @@ files:
190
205
  - spec/cuisine_type_spec.rb
191
206
  - spec/db/database.yml
192
207
  - spec/db/schema.rb
193
- - spec/fixtures/labels.yml
194
208
  - spec/fixtures/tags.yml
195
209
  - spec/label_spec.rb
196
210
  - spec/namespace_type_spec.rb
@@ -212,17 +226,17 @@ require_paths:
212
226
  - lib
213
227
  required_ruby_version: !ruby/object:Gem::Requirement
214
228
  requirements:
215
- - - ! '>='
229
+ - - '>='
216
230
  - !ruby/object:Gem::Version
217
231
  version: '0'
218
232
  required_rubygems_version: !ruby/object:Gem::Requirement
219
233
  requirements:
220
- - - ! '>='
234
+ - - '>='
221
235
  - !ruby/object:Gem::Version
222
236
  version: '0'
223
237
  requirements: []
224
238
  rubyforge_project:
225
- rubygems_version: 2.0.3
239
+ rubygems_version: 2.0.2
226
240
  signing_key:
227
241
  specification_version: 4
228
242
  summary: Easily and efficiently make your ActiveRecord model support hierarchies
@@ -230,7 +244,6 @@ test_files:
230
244
  - spec/cuisine_type_spec.rb
231
245
  - spec/db/database.yml
232
246
  - spec/db/schema.rb
233
- - spec/fixtures/labels.yml
234
247
  - spec/fixtures/tags.yml
235
248
  - spec/label_spec.rb
236
249
  - spec/namespace_type_spec.rb
@@ -1,55 +0,0 @@
1
- a1:
2
- name: a1
3
- sort_order: 1
4
-
5
- b1:
6
- name: b1
7
- parent: a1
8
- sort_order: 1
9
-
10
- b2:
11
- name: b2
12
- parent: a1
13
- sort_order: 2
14
-
15
- # Note that the names are not alphabetically ordered:
16
- c16:
17
- name: c1-six
18
- parent: b1
19
- sort_order: 6
20
-
21
- c17:
22
- name: c1-seven
23
- parent: b1
24
- sort_order: 7
25
-
26
- c18:
27
- name: c1-eight
28
- parent: b1
29
- sort_order: 8
30
-
31
- c19:
32
- name: c1-nine
33
- parent: b1
34
- sort_order: 9
35
-
36
- c2:
37
- name: c2
38
- parent: b2
39
- sort_order: 1
40
-
41
- d2:
42
- name: d2
43
- parent: c2
44
- sort_order: 1
45
-
46
- e2:
47
- name: e2
48
- parent: d2
49
- sort_order: 1
50
-
51
- f3:
52
- name: f3
53
-
54
- f4:
55
- name: f4