acts_as_ordered_tree 1.3.1 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/lib/acts_as_ordered_tree.rb +22 -100
  3. data/lib/acts_as_ordered_tree/adapters.rb +17 -0
  4. data/lib/acts_as_ordered_tree/adapters/abstract.rb +23 -0
  5. data/lib/acts_as_ordered_tree/adapters/postgresql.rb +150 -0
  6. data/lib/acts_as_ordered_tree/adapters/recursive.rb +157 -0
  7. data/lib/acts_as_ordered_tree/compatibility.rb +22 -0
  8. data/lib/acts_as_ordered_tree/compatibility/active_record/association_scope.rb +9 -0
  9. data/lib/acts_as_ordered_tree/compatibility/active_record/default_scoped.rb +19 -0
  10. data/lib/acts_as_ordered_tree/compatibility/active_record/null_relation.rb +71 -0
  11. data/lib/acts_as_ordered_tree/compatibility/features.rb +153 -0
  12. data/lib/acts_as_ordered_tree/deprecate.rb +24 -0
  13. data/lib/acts_as_ordered_tree/hooks.rb +38 -0
  14. data/lib/acts_as_ordered_tree/hooks/update.rb +86 -0
  15. data/lib/acts_as_ordered_tree/instance_methods.rb +92 -453
  16. data/lib/acts_as_ordered_tree/iterators/arranger.rb +35 -0
  17. data/lib/acts_as_ordered_tree/iterators/level_calculator.rb +52 -0
  18. data/lib/acts_as_ordered_tree/iterators/orphans_pruner.rb +58 -0
  19. data/lib/acts_as_ordered_tree/node.rb +78 -0
  20. data/lib/acts_as_ordered_tree/node/attributes.rb +48 -0
  21. data/lib/acts_as_ordered_tree/node/movement.rb +62 -0
  22. data/lib/acts_as_ordered_tree/node/movements.rb +111 -0
  23. data/lib/acts_as_ordered_tree/node/predicates.rb +98 -0
  24. data/lib/acts_as_ordered_tree/node/reloading.rb +49 -0
  25. data/lib/acts_as_ordered_tree/node/siblings.rb +139 -0
  26. data/lib/acts_as_ordered_tree/node/traversals.rb +53 -0
  27. data/lib/acts_as_ordered_tree/persevering_transaction.rb +93 -0
  28. data/lib/acts_as_ordered_tree/position.rb +143 -0
  29. data/lib/acts_as_ordered_tree/relation/arrangeable.rb +33 -0
  30. data/lib/acts_as_ordered_tree/relation/iterable.rb +41 -0
  31. data/lib/acts_as_ordered_tree/relation/preloaded.rb +46 -11
  32. data/lib/acts_as_ordered_tree/transaction/base.rb +57 -0
  33. data/lib/acts_as_ordered_tree/transaction/callbacks.rb +67 -0
  34. data/lib/acts_as_ordered_tree/transaction/create.rb +68 -0
  35. data/lib/acts_as_ordered_tree/transaction/destroy.rb +34 -0
  36. data/lib/acts_as_ordered_tree/transaction/dsl.rb +214 -0
  37. data/lib/acts_as_ordered_tree/transaction/factory.rb +67 -0
  38. data/lib/acts_as_ordered_tree/transaction/move.rb +70 -0
  39. data/lib/acts_as_ordered_tree/transaction/passthrough.rb +12 -0
  40. data/lib/acts_as_ordered_tree/transaction/reorder.rb +42 -0
  41. data/lib/acts_as_ordered_tree/transaction/save.rb +64 -0
  42. data/lib/acts_as_ordered_tree/transaction/update.rb +78 -0
  43. data/lib/acts_as_ordered_tree/tree.rb +148 -0
  44. data/lib/acts_as_ordered_tree/tree/association.rb +20 -0
  45. data/lib/acts_as_ordered_tree/tree/callbacks.rb +57 -0
  46. data/lib/acts_as_ordered_tree/tree/children_association.rb +120 -0
  47. data/lib/acts_as_ordered_tree/tree/columns.rb +102 -0
  48. data/lib/acts_as_ordered_tree/tree/deprecated_columns_accessors.rb +24 -0
  49. data/lib/acts_as_ordered_tree/tree/parent_association.rb +31 -0
  50. data/lib/acts_as_ordered_tree/tree/perseverance.rb +19 -0
  51. data/lib/acts_as_ordered_tree/tree/scopes.rb +56 -0
  52. data/lib/acts_as_ordered_tree/validators.rb +1 -1
  53. data/lib/acts_as_ordered_tree/version.rb +1 -1
  54. data/spec/acts_as_ordered_tree_spec.rb +80 -909
  55. data/spec/adapters/postgresql_spec.rb +14 -0
  56. data/spec/adapters/recursive_spec.rb +12 -0
  57. data/spec/adapters/shared.rb +272 -0
  58. data/spec/callbacks_spec.rb +177 -0
  59. data/spec/counter_cache_spec.rb +31 -0
  60. data/spec/create_spec.rb +110 -0
  61. data/spec/destroy_spec.rb +57 -0
  62. data/spec/inheritance_spec.rb +176 -0
  63. data/spec/move_spec.rb +94 -0
  64. data/spec/node/movements/concurrent_movements_spec.rb +354 -0
  65. data/spec/node/movements/move_higher_spec.rb +46 -0
  66. data/spec/node/movements/move_lower_spec.rb +46 -0
  67. data/spec/node/movements/move_to_child_of_spec.rb +147 -0
  68. data/spec/node/movements/move_to_child_with_index_spec.rb +124 -0
  69. data/spec/node/movements/move_to_child_with_position_spec.rb +85 -0
  70. data/spec/node/movements/move_to_left_of_spec.rb +120 -0
  71. data/spec/node/movements/move_to_right_of_spec.rb +120 -0
  72. data/spec/node/movements/move_to_root_spec.rb +67 -0
  73. data/spec/node/predicates_spec.rb +211 -0
  74. data/spec/node/reloading_spec.rb +42 -0
  75. data/spec/node/siblings_spec.rb +193 -0
  76. data/spec/node/traversals_spec.rb +71 -0
  77. data/spec/persevering_transaction_spec.rb +98 -0
  78. data/spec/relation/arrangeable_spec.rb +88 -0
  79. data/spec/relation/iterable_spec.rb +104 -0
  80. data/spec/relation/preloaded_spec.rb +57 -0
  81. data/spec/reorder_spec.rb +83 -0
  82. data/spec/spec_helper.rb +30 -38
  83. data/spec/support/db/boot.rb +22 -0
  84. data/spec/{db → support/db}/config.travis.yml +2 -0
  85. data/spec/{db → support/db}/config.yml +1 -0
  86. data/spec/{db → support/db}/schema.rb +9 -0
  87. data/spec/support/factories.rb +2 -2
  88. data/spec/support/matchers.rb +67 -58
  89. data/spec/support/models.rb +6 -14
  90. data/spec/support/tree_factory.rb +315 -0
  91. data/spec/tree/children_association_spec.rb +72 -0
  92. data/spec/tree/columns_spec.rb +65 -0
  93. data/spec/tree/scopes_spec.rb +39 -0
  94. metadata +161 -43
  95. data/lib/acts_as_ordered_tree/adapters/postgresql_adapter.rb +0 -104
  96. data/lib/acts_as_ordered_tree/arrangeable.rb +0 -80
  97. data/lib/acts_as_ordered_tree/class_methods.rb +0 -72
  98. data/lib/acts_as_ordered_tree/relation/base.rb +0 -26
  99. data/lib/acts_as_ordered_tree/tenacious_transaction.rb +0 -30
  100. data/spec/concurrency_support_spec.rb +0 -156
@@ -0,0 +1,354 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree::Node::Movements, :non_transactional, :unless => ENV['DB'] == 'sqlite3' do
6
+ class ConcurrentTasks < Array
7
+ def task(&block)
8
+ push block
9
+ end
10
+
11
+ def spawn(suite)
12
+ map do |task|
13
+ thread { suite.instance_eval(&task) }
14
+ end.each(&:join)
15
+ end
16
+
17
+ private
18
+ def thread(&block)
19
+ Thread.start do
20
+ ActiveRecord::Base.connection_pool.with_connection(&block)
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.concurrent(&block)
26
+ tasks = ConcurrentTasks.new
27
+ tasks.instance_eval(&block)
28
+
29
+ before do
30
+ tasks.spawn(self)
31
+ end
32
+ end
33
+
34
+ shared_examples 'Concurrency support' do |factory, attrs = {}|
35
+ context 'create root nodes in empty tree simultaneously' do
36
+ let(:current_tree) { FactoryGirl.factory_by_name(factory).build_class }
37
+
38
+ concurrent do
39
+ 3.times { task { create factory, attrs } }
40
+ end
41
+
42
+ expect_tree_to_match {
43
+ any
44
+ any
45
+ any
46
+ }
47
+ end
48
+
49
+ context 'add root nodes to existing tree simultaneously' do
50
+ tree :factory => factory, :attributes => attrs do
51
+ root
52
+ end
53
+
54
+ concurrent do
55
+ 3.times { task { create factory, attrs } }
56
+ end
57
+
58
+ expect_tree_to_match {
59
+ root
60
+ any
61
+ any
62
+ any
63
+ }
64
+ end
65
+
66
+ context 'create nodes on the same level simultaneously' do
67
+ tree :factory => factory do
68
+ root
69
+ end
70
+
71
+ concurrent do
72
+ 3.times do
73
+ task { create factory, :parent => root }
74
+ end
75
+ end
76
+
77
+ expect_tree_to_match {
78
+ root {
79
+ any
80
+ any
81
+ any
82
+ }
83
+ }
84
+ end
85
+
86
+ context 'move same node simultaneously' do
87
+ tree :factory => factory, :attributes => attrs do
88
+ node_1
89
+ node_2
90
+ node_3
91
+ node_4
92
+ end
93
+
94
+ # node itself isn't thread safe
95
+ def moved_node
96
+ current_tree.find(node_2.id)
97
+ end
98
+
99
+ concurrent do
100
+ task { moved_node.move_higher }
101
+ task { moved_node.move_lower }
102
+ task { moved_node.move_to_right_of(node_4) }
103
+ end
104
+
105
+ expect_tree_to_match {
106
+ any
107
+ any
108
+ any
109
+ any
110
+ }
111
+ end
112
+
113
+ context 'move nodes to same parent simultaneously' do
114
+ tree :factory => factory, :attributes => attrs do
115
+ root
116
+ node_1
117
+ node_2
118
+ node_3
119
+ end
120
+
121
+ concurrent do
122
+ task { node_1.move_to_child_of(root) }
123
+ task { node_2.move_to_child_of(root) }
124
+ task { node_3.move_to_child_of(root) }
125
+ end
126
+
127
+ expect_tree_to_match {
128
+ root {
129
+ any
130
+ any
131
+ any
132
+ }
133
+ }
134
+ end
135
+
136
+ context 'move nodes to left of same root node simultaneously' do
137
+ tree :factory => factory, :attributes => attrs do
138
+ root_1
139
+ root_2 {
140
+ node_1
141
+ node_2
142
+ node_3
143
+ }
144
+ end
145
+
146
+ concurrent do
147
+ task { node_1.move_to_left_of(root_2) }
148
+ task { node_2.move_to_left_of(root_2) }
149
+ task { node_3.move_to_left_of(root_2) }
150
+ end
151
+
152
+ expect_tree_to_match {
153
+ root_1
154
+ any
155
+ any
156
+ any
157
+ root_2
158
+ }
159
+ end
160
+
161
+ context 'move nodes to left of same non-root node simultaneously' do
162
+ tree :factory => factory do
163
+ root {
164
+ child_1
165
+ child_2 {
166
+ node_1
167
+ node_2
168
+ node_3
169
+ }
170
+ }
171
+ end
172
+
173
+ concurrent do
174
+ task { node_1.move_to_left_of(child_2) }
175
+ task { node_2.move_to_left_of(child_2) }
176
+ task { node_3.move_to_left_of(child_2) }
177
+ end
178
+
179
+ expect_tree_to_match {
180
+ root {
181
+ child_1
182
+ any
183
+ any
184
+ any
185
+ child_2
186
+ }
187
+ }
188
+ end
189
+
190
+ context 'move node to right of same root node simultaneously' do
191
+ tree :factory => factory, :attributes => attrs do
192
+ root_1 {
193
+ node_1
194
+ node_2
195
+ node_3
196
+ }
197
+ root_2
198
+ end
199
+
200
+ concurrent do
201
+ task { node_1.move_to_right_of(root_1) }
202
+ task { node_2.move_to_right_of(root_1) }
203
+ task { node_3.move_to_right_of(root_1) }
204
+ end
205
+
206
+ expect_tree_to_match {
207
+ root_1
208
+ any
209
+ any
210
+ any
211
+ root_2
212
+ }
213
+ end
214
+
215
+ context 'move nodes to right of same non-root node simultaneously' do
216
+ tree :factory => factory do
217
+ root {
218
+ child_1 {
219
+ node_1
220
+ node_2
221
+ node_3
222
+ }
223
+ child_2
224
+ }
225
+ end
226
+
227
+ concurrent do
228
+ task { node_1.move_to_right_of(child_1) }
229
+ task { node_2.move_to_right_of(child_1) }
230
+ task { node_3.move_to_right_of(child_1) }
231
+ end
232
+
233
+ expect_tree_to_match {
234
+ root {
235
+ child_1
236
+ any
237
+ any
238
+ any
239
+ child_2
240
+ }
241
+ }
242
+ end
243
+
244
+ context 'move nodes to root simultaneously' do
245
+ tree :factory => factory do
246
+ root {
247
+ node_1
248
+ node_2
249
+ node_3
250
+ }
251
+ end
252
+
253
+ concurrent do
254
+ task { node_1.move_to_root }
255
+ task { node_2.move_to_root }
256
+ task { node_3.move_to_root }
257
+ end
258
+
259
+ expect_tree_to_match {
260
+ root
261
+ any
262
+ any
263
+ any
264
+ }
265
+ end
266
+
267
+ context 'move nodes left simultaneously' do
268
+ tree :factory => factory do
269
+ root {
270
+ node_1
271
+ node_2
272
+ node_3
273
+ node_4
274
+ }
275
+ end
276
+
277
+ concurrent do
278
+ task { node_2.move_left }
279
+ task { node_3.move_left }
280
+ end
281
+
282
+ expect_tree_to_match {
283
+ root {
284
+ any
285
+ any
286
+ any
287
+ node_4
288
+ }
289
+ }
290
+ end
291
+
292
+ context 'move nodes right simultaneously' do
293
+ tree :factory => factory do
294
+ root {
295
+ node_1
296
+ node_2
297
+ node_3
298
+ node_4
299
+ }
300
+ end
301
+
302
+ concurrent do
303
+ task { node_2.move_right }
304
+ task { node_3.move_right }
305
+ end
306
+
307
+ expect_tree_to_match {
308
+ root {
309
+ node_1
310
+ any
311
+ any
312
+ any
313
+ }
314
+ }
315
+ end
316
+
317
+ context 'swap nodes between different branches simultaneously' do
318
+ tree :factory => factory do
319
+ root {
320
+ child_1 {
321
+ swap_1
322
+ other_1
323
+ }
324
+ child_2 {
325
+ other_2
326
+ swap_2
327
+ }
328
+ }
329
+ end
330
+
331
+ concurrent do
332
+ task { swap_1.move_to_child_with_position(child_2, 2) }
333
+ task { swap_2.move_to_child_with_position(child_1, 1) }
334
+ end
335
+
336
+ expect_tree_to_match {
337
+ root {
338
+ child_1 {
339
+ swap_2
340
+ other_1
341
+ }
342
+ child_2 {
343
+ other_2
344
+ swap_1
345
+ }
346
+ }
347
+ }
348
+ end
349
+ end
350
+
351
+ include_examples 'Concurrency support', :default
352
+ include_examples 'Concurrency support', :default_with_counter_cache
353
+ include_examples 'Concurrency support', :scoped, :scope_type => 's'
354
+ end
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree::Node::Movements, '#move_higher', :transactional do
6
+ shared_examples '#move_higher' do |factory, attrs = {}|
7
+ describe "#move_higher #{factory}" do
8
+ tree :factory => factory, :attributes => attrs do
9
+ node_1
10
+ node_2
11
+ node_3
12
+ end
13
+
14
+ context 'trying to move highest node up' do
15
+ before { node_1.move_higher }
16
+
17
+ expect_tree_to_match {
18
+ node_1
19
+ node_2
20
+ node_3
21
+ }
22
+ end
23
+
24
+ context 'trying to move node with position > 1' do
25
+ before { node_2.move_higher }
26
+
27
+ expect_tree_to_match {
28
+ node_2
29
+ node_1
30
+ node_3
31
+ }
32
+ end
33
+
34
+ context 'when attribute, not related to tree changed' do
35
+ before { @old_name = node_3.name }
36
+ before { node_3.name = 'new name' }
37
+
38
+ it { expect{node_3.move_higher}.to change(node_3, :name).to(@old_name) }
39
+ end
40
+ end
41
+ end
42
+
43
+ include_examples '#move_higher', :default
44
+ include_examples '#move_higher', :default_with_counter_cache
45
+ include_examples '#move_higher', :scoped, :scope_type => 's'
46
+ end
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree::Node::Movements, '#move_lower', :transactional do
6
+ shared_examples '#move_lower' do |factory, attrs = {}|
7
+ describe "#move_lower #{factory}" do
8
+ tree :factory => factory, :attributes => attrs do
9
+ node_1
10
+ node_2
11
+ node_3
12
+ end
13
+
14
+ context 'trying to lowest node down' do
15
+ before { node_3.move_lower }
16
+
17
+ expect_tree_to_match {
18
+ node_1
19
+ node_2
20
+ node_3
21
+ }
22
+ end
23
+
24
+ context 'trying to move node with not lowest position' do
25
+ before { node_2.move_lower }
26
+
27
+ expect_tree_to_match {
28
+ node_1
29
+ node_3
30
+ node_2
31
+ }
32
+ end
33
+
34
+ context 'when attribute, not related to tree changed' do
35
+ before { @old_name = node_2.name }
36
+ before { node_2.name = 'new name' }
37
+
38
+ it { expect{node_2.move_lower}.to change(node_2, :name).to(@old_name) }
39
+ end
40
+ end
41
+ end
42
+
43
+ include_examples '#move_lower', :default
44
+ include_examples '#move_lower', :default_with_counter_cache
45
+ include_examples '#move_lower', :scoped, :scope_type => 's'
46
+ end
@@ -0,0 +1,147 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree::Node::Movements, '#move_to_child_of', :transactional do
6
+ shared_examples '#move_to_child_of' do |factory|
7
+ describe "#move_to_child_of #{factory}" do
8
+ tree :factory => factory do
9
+ root {
10
+ child_1
11
+ child_2
12
+ child_3 {
13
+ child_4
14
+ }
15
+ }
16
+ end
17
+
18
+ context 'when AR object given' do
19
+ it 'moves node' do
20
+ expect {
21
+ child_3.move_to_child_of(child_1)
22
+ }.to change(child_3, :parent).from(root).to(child_1)
23
+ end
24
+
25
+ it 'does not change attributes unrelated to tree' do
26
+ old, child_4.name = child_4.name, 'new name'
27
+
28
+ expect {
29
+ child_4.move_to_child_of(root)
30
+ }.to change(child_4, :name).to(old)
31
+ end
32
+
33
+ context 'moving to child of self' do
34
+ it { expect(child_3.move_to_child_of(child_3)).to be false }
35
+
36
+ it 'does not move node' do
37
+ expect {
38
+ child_3.move_to_child_of(child_3)
39
+ }.not_to change(child_3, :reload)
40
+ end
41
+
42
+ it 'invalidates node' do
43
+ expect {
44
+ child_3.move_to_child_of(child_3)
45
+ }.to change(child_3, :valid?).from(true).to(false)
46
+ end
47
+ end
48
+
49
+ context 'moving to child of current parent' do
50
+ it 'does not move node' do
51
+ expect {
52
+ child_2.move_to_child_of(root)
53
+ }.not_to change(child_2, :reload)
54
+ end
55
+ end
56
+
57
+ context 'moving to child of descendant' do
58
+ it { expect(root.move_to_child_of(child_1)).to be false }
59
+
60
+ it 'does node move node' do
61
+ expect {
62
+ root.move_to_child_of(child_1)
63
+ }.not_to change(root, :reload)
64
+ end
65
+
66
+ it 'invalidates node' do
67
+ expect {
68
+ root.move_to_child_of(child_1)
69
+ }.to change(root, :valid?).from(true).to(false)
70
+ end
71
+ end
72
+
73
+ context 'moving node deeper' do
74
+ before { child_3.move_to_child_of(child_2) }
75
+
76
+ expect_tree_to_match {
77
+ root {
78
+ child_1
79
+ child_2 {
80
+ child_3 {
81
+ child_4
82
+ }
83
+ }
84
+ }
85
+ }
86
+ end
87
+
88
+ context 'moving node upper' do
89
+ before { child_4.move_to_child_of(root) }
90
+
91
+ expect_tree_to_match {
92
+ root {
93
+ child_1
94
+ child_2
95
+ child_3
96
+ child_4
97
+ }
98
+ }
99
+ end
100
+ end
101
+
102
+ context 'when ID given' do
103
+ it 'moves node' do
104
+ expect {
105
+ child_3.move_to_child_of(child_1.id)
106
+ }.to change(child_3, :parent).from(root).to(child_1)
107
+ end
108
+
109
+ it 'does not move node if parent was not changed' do
110
+ expect {
111
+ child_2.move_to_child_of(root.id)
112
+ }.not_to change(child_2, :reload)
113
+ end
114
+
115
+ context 'moving to non-existent ID' do
116
+ before { allow(child_3).to receive(:valid?).and_return(false) }
117
+
118
+ it { expect(child_3.move_to_child_of(-1)).to be false }
119
+
120
+ it 'does not move node' do
121
+ expect {
122
+ child_3.move_to_child_of(-1)
123
+ }.not_to change(child_3, :reload)
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'when nil given' do
129
+ before { child_2.move_to_child_of(nil) }
130
+
131
+ expect_tree_to_match {
132
+ root {
133
+ child_1
134
+ child_3 {
135
+ child_4
136
+ }
137
+ }
138
+ child_2
139
+ }
140
+ end
141
+ end
142
+ end
143
+
144
+ include_examples '#move_to_child_of', :default
145
+ include_examples '#move_to_child_of', :default_with_counter_cache
146
+ include_examples '#move_to_child_of', :scoped
147
+ end