closure_tree 4.2.0 → 4.2.1
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 +6 -14
- data/README.md +6 -0
- data/lib/closure_tree/hierarchy_maintenance.rb +1 -0
- data/lib/closure_tree/model.rb +17 -3
- data/lib/closure_tree/numeric_deterministic_ordering.rb +48 -16
- data/lib/closure_tree/numeric_order_support.rb +63 -0
- data/lib/closure_tree/support.rb +7 -3
- data/lib/closure_tree/support_attributes.rb +13 -10
- data/lib/closure_tree/support_flags.rb +1 -1
- data/lib/closure_tree/version.rb +1 -1
- data/spec/label_spec.rb +91 -101
- data/spec/spec_helper.rb +2 -0
- metadata +42 -29
- data/spec/fixtures/labels.yml +0 -55
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
[](http://travis-ci.org/mceachen/closure_tree)
|
9
9
|
[](http://rubygems.org/gems/closure_tree)
|
10
10
|
[](https://codeclimate.com/github/mceachen/closure_tree)
|
11
|
+
[](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
|
|
data/lib/closure_tree/model.rb
CHANGED
@@ -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(
|
64
|
-
fail "can't add self as sibling" if self ==
|
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?
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
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
|
data/lib/closure_tree/support.rb
CHANGED
@@ -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],
|
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,
|
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],
|
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 =
|
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
|
data/lib/closure_tree/version.rb
CHANGED
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
|
-
|
225
|
-
@
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
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.
|
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.
|
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.
|
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.
|
260
|
-
end
|
269
|
+
root.children.collect(&:sort_order).should == [0, 1, 2]
|
261
270
|
|
262
|
-
|
263
|
-
|
264
|
-
|
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.
|
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 "
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
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
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
-
|
328
|
-
|
329
|
-
|
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
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.
|
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-
|
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.
|
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
|
data/spec/fixtures/labels.yml
DELETED
@@ -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
|