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,71 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree::Node::Traversals, :transactional do
6
+ shared_examples 'ActsAsOrderedTree::Node traversals' do |model, attrs = {}|
7
+ let(:root_1) { create model, attrs }
8
+ let(:child_1) { create model, attrs.merge(:parent => root_1) }
9
+ let(:grandchild_1) { create model, attrs.merge(:parent => child_1) }
10
+ let(:root_2) { create model, attrs }
11
+ let(:child_2) { create model, attrs.merge(:parent => root_2) }
12
+ let(:grandchild_2) { create model, attrs.merge(:parent => child_2) }
13
+
14
+ before { [root_1, child_1, grandchild_1].each(&:reload) }
15
+ before { [root_2, child_2, grandchild_2].each(&:reload) }
16
+
17
+ describe '#root' do
18
+ it { expect(root_1.root).to eq root_1 }
19
+ it { expect{root_1.root}.not_to query_database }
20
+ it { expect(child_1.root).to eq root_1 }
21
+ it { expect(grandchild_1.root).to eq root_1 }
22
+
23
+ it { expect(root_2.root).to eq root_2 }
24
+ it { expect{root_2.root}.not_to query_database }
25
+ it { expect(child_2.root).to eq root_2 }
26
+ it { expect(grandchild_2.root).to eq root_2 }
27
+ end
28
+
29
+ describe '#self_and_ancestors' do
30
+ it { expect(root_1.self_and_ancestors).to eq [root_1] }
31
+ it { expect{root_1.self_and_ancestors}.not_to query_database }
32
+ it { expect(child_1.self_and_ancestors).to eq [root_1, child_1] }
33
+ it { expect(grandchild_1.self_and_ancestors).to eq [root_1, child_1, grandchild_1] }
34
+
35
+ it { expect(child_1.self_and_ancestors).to respond_to :each_with_level }
36
+ it { expect(child_1.self_and_ancestors).to respond_to :each_without_orphans }
37
+ end
38
+
39
+ describe '#ancestors' do
40
+ it { expect(root_1.ancestors).to eq [] }
41
+ it { expect{root_1.ancestors}.not_to query_database }
42
+ it { expect(child_1.ancestors).to eq [root_1] }
43
+ it { expect(grandchild_1.ancestors).to eq [root_1, child_1] }
44
+
45
+ it { expect(child_1.ancestors).to respond_to :each_with_level }
46
+ it { expect(child_1.ancestors).to respond_to :each_without_orphans }
47
+ end
48
+
49
+ describe '#self_and_descendants' do
50
+ it { expect(root_1.self_and_descendants).to eq [root_1, child_1, grandchild_1] }
51
+ it { expect(child_1.self_and_descendants).to eq [child_1, grandchild_1] }
52
+ it { expect(grandchild_1.self_and_descendants).to eq [grandchild_1] }
53
+
54
+ it { expect(root_1.self_and_descendants).to respond_to :each_with_level }
55
+ it { expect(root_1.self_and_descendants).to respond_to :each_without_orphans }
56
+ end
57
+
58
+ describe '#descendants' do
59
+ it { expect(root_1.descendants).to eq [child_1, grandchild_1] }
60
+ it { expect(child_1.descendants).to eq [grandchild_1] }
61
+ it { expect(grandchild_1.descendants).to eq [] }
62
+
63
+ it { expect(root_1.descendants).to respond_to :each_with_level }
64
+ it { expect(root_1.descendants).to respond_to :each_without_orphans }
65
+ end
66
+ end
67
+
68
+ include_examples 'ActsAsOrderedTree::Node traversals', :default
69
+ include_examples 'ActsAsOrderedTree::Node traversals', :default_with_counter_cache
70
+ include_examples 'ActsAsOrderedTree::Node traversals', :scoped, :scope_type => 'a'
71
+ end
@@ -0,0 +1,98 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'acts_as_ordered_tree/persevering_transaction'
6
+
7
+ describe ActsAsOrderedTree::PerseveringTransaction, :non_transactional do
8
+ def create_transaction(connection = ActiveRecord::Base.connection)
9
+ described_class.new(connection)
10
+ end
11
+
12
+ describe 'Transaction state' do
13
+ def transaction
14
+ @transaction ||= create_transaction
15
+ end
16
+
17
+ it 'becomes committed only when real transaction ends' do
18
+ transaction.start do
19
+ nested_transaction = create_transaction
20
+
21
+ nested_transaction.start { }
22
+
23
+ expect(nested_transaction).not_to be_committed
24
+ expect(transaction).not_to be_committed
25
+ end
26
+
27
+ expect(transaction).to be_committed
28
+ end
29
+
30
+ it 'becomes rolledback when real transaction is rolledback' do
31
+ transaction.start do
32
+ raise ActiveRecord::Rollback
33
+ end
34
+
35
+ expect(transaction).to be_rolledback
36
+ end
37
+ end
38
+
39
+ describe 'After commit callbacks' do
40
+ it 'executes callbacks only when real transaction commits' do
41
+ executed = []
42
+
43
+ outer = create_transaction
44
+ outer.after_commit { executed << 1 }
45
+
46
+ outer.start do
47
+ inner = create_transaction
48
+ inner.after_commit { executed << 2 }
49
+
50
+ inner.start { }
51
+
52
+ expect(executed).to be_empty
53
+ end
54
+
55
+ expect(executed).to eq [1, 2]
56
+ end
57
+ end
58
+
59
+ describe 'Deadlock handling' do
60
+ def start_in_thread(&block)
61
+ Thread.start do
62
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
63
+ trans = create_transaction(connection)
64
+ trans.start(&block)
65
+ trans
66
+ end
67
+ end
68
+ end
69
+
70
+ let!(:resource1) { Default.create!(:name => 'resource 1') }
71
+ let!(:resource2) { Default.create!(:name => 'resource 2') }
72
+
73
+ # this test randomly fails on Rails 3.1
74
+ it 'Restarts transaction when deadlock occurred' do
75
+ threads = []
76
+
77
+ threads << start_in_thread do
78
+ resource1.lock!
79
+ sleep 0.1
80
+ resource2.lock!
81
+ sleep 0.1
82
+ end
83
+
84
+ threads << start_in_thread do
85
+ resource2.lock!
86
+ sleep 0.1
87
+ resource1.lock!
88
+ sleep 0.1
89
+ end
90
+
91
+ transactions = threads.map(&:value)
92
+
93
+ expect(transactions[0]).to be_committed
94
+ expect(transactions[1]).to be_committed
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,88 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree::Relation::Arrangeable, :transactional do
6
+ tree :factory => :default do
7
+ root {
8
+ child_1 {
9
+ grandchild_11
10
+ grandchild_12
11
+ }
12
+ child_2 {
13
+ grandchild_21
14
+ grandchild_22
15
+ }
16
+ }
17
+ end
18
+
19
+ specify '#descendants scope should be arrangeable' do
20
+ expect(root.descendants.arrange).to eq Hash[
21
+ child_1 => {
22
+ grandchild_11 => {},
23
+ grandchild_12 => {}
24
+ },
25
+ child_2 => {
26
+ grandchild_21 => {},
27
+ grandchild_22 => {}
28
+ }
29
+ ]
30
+ end
31
+
32
+ specify '#self_and_descendants should be arrangeable' do
33
+ expect(root.self_and_descendants.arrange).to eq Hash[
34
+ root => {
35
+ child_1 => {
36
+ grandchild_11 => {},
37
+ grandchild_12 => {}
38
+ },
39
+ child_2 => {
40
+ grandchild_21 => {},
41
+ grandchild_22 => {}
42
+ }
43
+ }
44
+ ]
45
+ end
46
+
47
+ specify '#ancestors should be arrangeable' do
48
+ expect(grandchild_11.ancestors.arrange).to eq Hash[
49
+ root => {
50
+ child_1 => {}
51
+ }
52
+ ]
53
+ end
54
+
55
+ specify '#self_and_ancestors should be arrangeable' do
56
+ expect(grandchild_11.self_and_ancestors.arrange).to eq Hash[
57
+ root => {
58
+ child_1 => {
59
+ grandchild_11 => {}
60
+ }
61
+ }
62
+ ]
63
+ end
64
+
65
+ it 'should not discard orphaned nodes by default' do
66
+ relation = root.descendants.where(root.class.arel_table[:id].not_eq(child_1.id))
67
+
68
+ expect(relation.arrange).to eq Hash[
69
+ grandchild_11 => {},
70
+ grandchild_12 => {},
71
+ child_2 => {
72
+ grandchild_21 => {},
73
+ grandchild_22 => {}
74
+ }
75
+ ]
76
+ end
77
+
78
+ it 'should discard orphans if option :discard passed' do
79
+ relation = root.descendants.where(root.class.arel_table[:id].not_eq(child_1.id))
80
+
81
+ expect(relation.arrange(:orphans => :discard)).to eq Hash[
82
+ child_2 => {
83
+ grandchild_21 => {},
84
+ grandchild_22 => {}
85
+ }
86
+ ]
87
+ end
88
+ end
@@ -0,0 +1,104 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'acts_as_ordered_tree/relation/iterable'
6
+
7
+ describe ActsAsOrderedTree::Relation::Iterable, :transactional do
8
+ shared_examples 'iterable' do |model|
9
+ tree :factory => model do
10
+ root_1 {
11
+ child_1 {
12
+ child_2
13
+ }
14
+ child_3 {
15
+ child_4
16
+ child_5
17
+ }
18
+ }
19
+ root_2 {
20
+ child_6
21
+ }
22
+ end
23
+
24
+ describe '#each_with_level' do
25
+ it 'iterates over collection and yields level' do
26
+ relation = current_tree.order(:id).extending(described_class)
27
+
28
+ expect { |b|
29
+ relation.each_with_level(&b)
30
+ }.to yield_successive_args [root_1, 0],
31
+ [child_1, 1],
32
+ [child_2, 2],
33
+ [child_3, 1],
34
+ [child_4, 2],
35
+ [child_5, 2],
36
+ [root_2, 0],
37
+ [child_6, 1]
38
+ end
39
+
40
+ it 'computes level relative to first selected node' do
41
+ expect { |b|
42
+ root_1.descendants.extending(described_class).each_with_level(&b)
43
+ }.to yield_successive_args [child_1, 1],
44
+ [child_2, 2],
45
+ [child_3, 1],
46
+ [child_4, 2],
47
+ [child_5, 2]
48
+ end
49
+ end
50
+
51
+ describe '#each_without_orphans' do
52
+ let(:relation) { current_tree.order(:id).extending(described_class) }
53
+
54
+ it 'iterates over collection' do
55
+ expect { |b|
56
+ relation.each_without_orphans(&b)
57
+ }.to yield_successive_args root_1,
58
+ child_1,
59
+ child_2,
60
+ child_3,
61
+ child_4,
62
+ child_5,
63
+ root_2,
64
+ child_6
65
+ end
66
+
67
+ it 'iterates over collection and discards orphans' do
68
+ expect { |b|
69
+ relation.where('id != ?', child_3.id).each_without_orphans(&b)
70
+ }.to yield_successive_args root_1,
71
+ child_1,
72
+ child_2,
73
+ root_2,
74
+ child_6
75
+ end
76
+
77
+ it 'iterates over collection and discards orphans' do
78
+ expect { |b|
79
+ relation.where('id != ?', root_2.id).each_without_orphans(&b)
80
+ }.to yield_successive_args root_1,
81
+ child_1,
82
+ child_2,
83
+ child_3,
84
+ child_4,
85
+ child_5
86
+ end
87
+
88
+ it 'iterates over collection and discards orphans' do
89
+ expect { |b|
90
+ relation.where('id != ?', root_1.id).each_without_orphans(&b)
91
+ }.to yield_successive_args root_2,
92
+ child_6
93
+ end
94
+ end
95
+ end
96
+
97
+ describe 'Model with cached level' do
98
+ it_behaves_like 'iterable', :default
99
+ end
100
+
101
+ describe 'Model without cached level' do
102
+ it_behaves_like 'iterable', :default_without_depth
103
+ end
104
+ end
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'acts_as_ordered_tree/relation/preloaded'
6
+
7
+ describe ActsAsOrderedTree::Relation::Preloaded, :transactional do
8
+ let!(:records) { create_list :default, 2 }
9
+
10
+ def relation
11
+ Default.where(nil).extending(described_class)
12
+ end
13
+
14
+ context 'when preloaded records were not set' do
15
+ it { expect(relation).to match_array records }
16
+ it { expect(relation.to_a).not_to be records }
17
+ it { expect{relation.to_a}.to query_database.once }
18
+ end
19
+
20
+ context 'when preloaded records were set directly' do
21
+ let(:preloaded) { relation.records(records) }
22
+
23
+ it { expect(preloaded).to eq records }
24
+
25
+ it { expect(preloaded.to_a).to be records }
26
+ it { expect{preloaded.to_a}.not_to query_database }
27
+
28
+ it { expect(preloaded.size).to eq 2 }
29
+ it { expect{preloaded.size}.not_to query_database }
30
+
31
+ context 'when preloaded relation was extended' do
32
+ let(:extended) { preloaded.extending(Module.new) }
33
+
34
+ it { expect(extended).to eq records }
35
+
36
+ it { expect(extended.to_a).to be records }
37
+ it { expect{extended.to_a}.not_to query_database }
38
+
39
+ it { expect(extended.size).to eq 2 }
40
+ it { expect{extended.size}.not_to query_database }
41
+ end
42
+
43
+ describe '#reverse_order' do
44
+ it { expect(preloaded.reverse_order).not_to be preloaded }
45
+ it { expect(preloaded.reverse_order.size).to eq 2 }
46
+ it { expect(preloaded.reverse_order).to eq records.reverse }
47
+ it { expect{preloaded.reverse_order.to_a}.not_to query_database }
48
+ end
49
+
50
+ describe '#reverse_order!' do
51
+ it { expect(preloaded.reverse_order!).to be preloaded }
52
+ it { expect(preloaded.reverse_order!.size).to eq 2 }
53
+ it { expect(preloaded.reverse_order!.to_a).to eq records.reverse }
54
+ it { expect{preloaded.reverse_order!.to_a}.not_to query_database }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,83 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree, 'Reorder via save', :transactional do
6
+ tree :factory => :default do
7
+ root {
8
+ child_1
9
+ child_2
10
+ child_3
11
+ }
12
+ end
13
+
14
+ def reorder(node, position)
15
+ name = "category #{rand(100..1000)}"
16
+ node.position = position
17
+ node.name = name
18
+ expect { node.save! }.not_to raise_error
19
+ expect(node.name).to eq name
20
+ end
21
+
22
+ def assert_order(*nodes)
23
+ nodes.each_with_index do |node, index|
24
+ expect(node.reload.position).to eq index + 1
25
+ end
26
+ end
27
+
28
+ context 'when I change position to lower' do
29
+ before { reorder child_2, 1 }
30
+
31
+ it 'moves node up' do
32
+ assert_order child_2, child_1, child_3
33
+ end
34
+ end
35
+
36
+ context 'when I change position to lower' do
37
+ before { reorder child_2, 3 }
38
+
39
+ it 'moves node down' do
40
+ assert_order child_1, child_3, child_2
41
+ end
42
+ end
43
+
44
+ context 'when I move highest node lower' do
45
+ before { reorder child_1, 3 }
46
+
47
+ it 'moves node down' do
48
+ assert_order child_2, child_3, child_1
49
+ end
50
+ end
51
+
52
+ context 'when I move lowest node upper' do
53
+ before { reorder child_3, 1 }
54
+
55
+ it 'moves node down' do
56
+ assert_order child_3, child_1, child_2
57
+ end
58
+ end
59
+
60
+ context 'when I move to very high position' do
61
+ before { reorder child_1, 5 }
62
+
63
+ it 'moves node to bottom' do
64
+ assert_order child_2, child_3, child_1
65
+ end
66
+ end
67
+
68
+ context 'when I move to zero position' do
69
+ before { reorder child_2, 0 }
70
+
71
+ it 'moves it to top' do
72
+ assert_order child_2, child_1, child_3
73
+ end
74
+ end
75
+
76
+ context 'when I move to same position' do
77
+ before { reorder child_2, 2 }
78
+
79
+ specify 'order remains the same' do
80
+ assert_order child_1, child_2, child_3
81
+ end
82
+ end
83
+ end