acts_as_ordered_tree 1.3.1 → 2.0.0.beta3

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