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,31 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree, ':counter_cache option', :transactional do
6
+ describe 'Class without counter cache, #children.size' do
7
+ tree :factory => :default do
8
+ root {
9
+ child_1
10
+ child_2
11
+ }
12
+ end
13
+
14
+ it { expect(root.children.size).to eq 2 }
15
+ it { expect{root.children.size}.to query_database.once }
16
+ end
17
+
18
+ describe 'Class with counter cache, #children.size' do
19
+ tree :factory => :default_with_counter_cache do
20
+ root {
21
+ child_1
22
+ child_2
23
+ }
24
+ end
25
+
26
+ before { root.reload }
27
+
28
+ it { expect(root.children.size).to eq 2 }
29
+ it { expect{root.children.size}.not_to query_database }
30
+ end
31
+ end
@@ -0,0 +1,110 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree, 'Create node', :transactional do
6
+ shared_examples 'create ordered tree node' do |model = :default|
7
+ let(:record) { build model }
8
+
9
+ before { record.parent = parent }
10
+
11
+ context 'when position is nil' do
12
+ before { record.position = nil }
13
+
14
+ it 'does not change node parent' do
15
+ expect{record.save}.not_to change(record, :parent)
16
+ end
17
+
18
+ it 'puts record to position = 1 when there are no siblings' do
19
+ expect{record.save}.to change(record, :position).from(nil).to(1)
20
+ end
21
+
22
+ it 'puts record to bottom position when there are some siblings' do
23
+ create model, :parent => parent
24
+
25
+ expect{record.save}.to change(record, :position).from(nil).to(2)
26
+ end
27
+
28
+ it 'calculates depth column' do
29
+ if record.ordered_tree.columns.depth?
30
+ expect{record.save}.to change(record, :depth).from(nil).to(parent ? parent.depth + 1 : 0)
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'when position != nil' do
36
+ before { record.position = 3 }
37
+
38
+ it 'changes position to 1 if siblings is empty' do
39
+ expect{record.save}.to change(record, :position).from(3).to(1)
40
+ end
41
+
42
+ it 'changes position to highest if there are too few siblings' do
43
+ create model, :parent => parent
44
+
45
+ expect{record.save}.to change(record, :position).from(3).to(2)
46
+ end
47
+
48
+ it 'increments position of lower siblings on insert' do
49
+ first = create model, :parent => parent
50
+ second = create model, :parent => parent
51
+ third = create model, :parent => parent
52
+
53
+ expect(first.reload.position).to eq 1
54
+ expect(second.reload.position).to eq 2
55
+ expect(third.reload.position).to eq 3
56
+
57
+ expect{record.save and third.reload}.to change(third, :position).from(3).to(4)
58
+
59
+ expect(first.reload.position).to eq 1
60
+ expect(second.reload.position).to eq 2
61
+ expect(record.reload.position).to eq 3
62
+ end
63
+
64
+ it 'calculates depth column' do
65
+ if record.ordered_tree.columns.depth?
66
+ expect{record.save}.to change(record, :depth).from(nil).to(parent ? parent.depth + 1 : 0)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'when parent is nil' do
73
+ include_examples 'create ordered tree node' do
74
+ let(:parent) { nil }
75
+ end
76
+ end
77
+
78
+ context 'when parent exists' do
79
+ include_examples 'create ordered tree node' do
80
+ let(:parent) { create :default }
81
+ end
82
+ end
83
+
84
+ context 'when parent exists (scoped)' do
85
+ include_examples 'create ordered tree node', :scoped do
86
+ let(:type) { 'scope' }
87
+ let(:parent) { create :scoped, :scope_type => type }
88
+
89
+ it 'copies scope columns values from parent node' do
90
+ expect{record.save}.to change(record, :scope_type).to(parent.scope_type)
91
+ end
92
+ end
93
+ end
94
+
95
+ describe 'when counter_cache exists' do
96
+ include_examples 'create ordered tree node', :default_with_counter_cache do
97
+ let(:parent) { create :default_with_counter_cache }
98
+
99
+ it 'sets counter_cache to 0 for new record' do
100
+ record.save
101
+
102
+ expect(record.categories_count).to eq 0
103
+ end
104
+
105
+ it 'increments counter_cache of parent' do
106
+ expect{record.save and parent.reload}.to change(parent, :categories_count).by(1)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree, 'Destroy node', :transactional do
6
+ shared_examples 'destroy ordered tree node' do |model = :default, attrs = {}|
7
+ tree :factory => model, :attributes => attrs do
8
+ root {
9
+ child_1 {
10
+ grandchild_1 {
11
+ grandchild_2
12
+ }
13
+ }
14
+ child_2
15
+ child_3
16
+ }
17
+ end
18
+
19
+ def assert_destroyed(record)
20
+ expect(record.class).not_to exist(record.id)
21
+ end
22
+
23
+ it 'destroys descendants' do
24
+ child_1.destroy
25
+
26
+ assert_destroyed(grandchild_1)
27
+ assert_destroyed(grandchild_2)
28
+ end
29
+
30
+ it 'decrements lower siblings positions' do
31
+ child_1.destroy
32
+
33
+ [child_2, child_3].each(&:reload)
34
+
35
+ expect(child_2.position).to eq 1
36
+ expect(child_3.position).to eq 2
37
+ end
38
+ end
39
+
40
+ context 'Default model' do
41
+ include_examples 'destroy ordered tree node', :default
42
+ end
43
+
44
+ context 'Scoped model' do
45
+ include_examples 'destroy ordered tree node', :scoped, :scope_type => 't'
46
+ end
47
+
48
+ context 'Model with counter cache' do
49
+ include_examples 'destroy ordered tree node', :default_with_counter_cache
50
+
51
+ before { root.reload }
52
+
53
+ it 'decrements parent children counter' do
54
+ expect{child_1.destroy and root.reload}.to change(root, :categories_count).from(3).to(2)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,176 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree, 'inheritance without STI', :transactional do
6
+ class BaseCategory < ActiveRecord::Base
7
+ self.table_name = 'categories'
8
+
9
+ acts_as_ordered_tree
10
+ end
11
+
12
+ class ConcreteCategory < BaseCategory
13
+ end
14
+
15
+ class ConcreteCategoryWithScope < BaseCategory
16
+ default_scope { where(arel_table[:name].matches('* %')) }
17
+ end
18
+
19
+ let!(:root) { BaseCategory.create(:name => '* root') }
20
+ let!(:child_1) { BaseCategory.create(:name => 'child 1', :parent => root) }
21
+ let!(:child_2) { BaseCategory.create(:name => 'child 2', :parent => child_1) }
22
+ let!(:child_3) { BaseCategory.create(:name => 'child 3', :parent => child_1) }
23
+ let!(:child_4) { BaseCategory.create(:name => '* child 4', :parent => root) }
24
+ let!(:child_5) { BaseCategory.create(:name => '* child 5', :parent => child_4) }
25
+ let!(:child_6) { BaseCategory.create(:name => 'child 6', :parent => child_4) }
26
+
27
+ matcher :be_of do |klass|
28
+ match do |relation|
29
+ expect(relation.map(&:class).uniq).to eq [klass]
30
+ end
31
+ end
32
+
33
+ shared_examples 'Inheritance test' do |klass|
34
+ describe "#{klass.name}#children" do
35
+ let(:root_node) { root.becomes(klass) }
36
+
37
+ it { expect(root_node).to be_a klass }
38
+ it { expect(root_node.children).to be_of klass }
39
+ end
40
+
41
+ describe "#{klass.name}#parent" do
42
+ let(:node) { child_5.becomes(klass) }
43
+
44
+ it { expect(node.parent).to be_an_instance_of klass }
45
+ end
46
+
47
+ describe "#{klass.name}#root" do
48
+ let(:root_node) { root.becomes(klass) }
49
+ let(:node) { child_5.becomes(klass) }
50
+
51
+ it { expect(node.root).to eq root_node }
52
+ it { expect(node.root).to be_an_instance_of klass }
53
+ end
54
+
55
+ describe "#{klass.name}#descendants" do
56
+ let(:node) { child_4.becomes(klass) }
57
+
58
+ it { expect(node.descendants).to be_of klass }
59
+ end
60
+
61
+ describe "#{klass.name}#ancestors" do
62
+ let(:node) { child_5.becomes(klass) }
63
+
64
+ it { expect(node.ancestors).to be_of klass }
65
+ end
66
+
67
+ describe "#{klass.name} tree validators" do
68
+ it 'calls validators only once' do
69
+ expect_any_instance_of(ActsAsOrderedTree::Validators::CyclicReferenceValidator).to receive(:validate).once
70
+
71
+ child_1.becomes(klass).save
72
+ end
73
+ end
74
+ end
75
+
76
+ include_examples 'Inheritance test', BaseCategory
77
+ include_examples 'Inheritance test', ConcreteCategory
78
+ include_examples 'Inheritance test', ConcreteCategoryWithScope do
79
+ let(:klass) { ConcreteCategoryWithScope }
80
+
81
+ describe 'ConcreteCategoryWithScope#children' do
82
+ let(:node) { klass.find(child_4.id) }
83
+
84
+ it { expect(node).to be_a klass }
85
+ it { expect(node.children).to be_of klass }
86
+
87
+ it 'applies class default scope to #children' do
88
+ expect(node.children.size).to eq 1
89
+ end
90
+ end
91
+
92
+ describe 'ConcreteCategoryWithScope#parent' do
93
+ let(:orphaned) { child_2.becomes(klass) }
94
+ let(:out_of_scope_with_proper_parent) { child_1.becomes(klass) }
95
+
96
+ it { expect(orphaned.parent).to be_nil }
97
+ it { expect(out_of_scope_with_proper_parent.parent).to eq root.becomes(klass) }
98
+ end
99
+
100
+ describe 'ConcreteCategoryWithScope#descendants' do
101
+ let(:root_node) { klass.find(root.id) }
102
+
103
+ it { expect(root_node.descendants).to be_of klass }
104
+ it { expect(root_node.descendants.map(&:id)).to eq [child_4.id, child_5.id] }
105
+ end
106
+ end
107
+ end
108
+
109
+ describe ActsAsOrderedTree, 'inheritance with STI', :transactional do
110
+ class StiRoot < StiExample
111
+ end
112
+
113
+ class StiExample1 < StiExample
114
+ end
115
+
116
+ class StiExample2 < StiExample
117
+ end
118
+
119
+ # build tree
120
+ let!(:root) { StiRoot.create(:name => 'root') }
121
+ let!(:child_1) { StiExample1.create(:name => 'child 1', :parent => root) }
122
+ let!(:child_2) { StiExample1.create(:name => 'child 2', :parent => child_1) }
123
+ let!(:child_3) { StiExample1.create(:name => 'child 3', :parent => child_1) }
124
+ let!(:child_4) { StiExample2.create(:name => 'child 4', :parent => root) }
125
+ let!(:child_5) { StiExample2.create(:name => 'child 5', :parent => child_4) }
126
+ let!(:child_6) { StiExample2.create(:name => 'child 6', :parent => child_4) }
127
+
128
+ before { [root, child_1, child_2, child_3, child_4, child_5, child_6].each(&:reload) }
129
+
130
+ describe '#children' do
131
+ it { expect(root.children).to eq [child_1, child_4] }
132
+ end
133
+
134
+ describe '#parent' do
135
+ it { expect(child_1.parent).to eq root }
136
+ end
137
+
138
+ describe '#descendants' do
139
+ it { expect(root.descendants).to eq [child_1, child_2, child_3, child_4, child_5, child_6] }
140
+ end
141
+
142
+ describe '#ancestors' do
143
+ it { expect(child_5.ancestors).to eq [root, child_4] }
144
+ end
145
+
146
+ describe '#root' do
147
+ it { expect(child_5.root).to eq root }
148
+ end
149
+
150
+ describe '#left_sibling' do
151
+ it { expect(child_4.left_sibling).to eq child_1 }
152
+ end
153
+
154
+ describe '#right_sibling' do
155
+ it { expect(child_1.right_sibling).to eq child_4 }
156
+ end
157
+
158
+ describe 'predicates' do
159
+ it { expect(root).to be_is_ancestor_of(child_1) }
160
+ it { expect(root).to be_is_or_is_ancestor_of(child_1) }
161
+ it { expect(child_1).to be_is_descendant_of(root) }
162
+ it { expect(child_1).to be_is_or_is_descendant_of(root) }
163
+ end
164
+
165
+ describe 'node reload' do
166
+ it { expect(child_1.ordered_tree_node.reload).to eq child_1 }
167
+ end
168
+
169
+ describe 'node moving' do
170
+ before { child_4.move_to_child_of(child_1) }
171
+
172
+ it { expect(child_4.parent).to eq child_1 }
173
+ it { expect(child_1.children).to include child_4 }
174
+ it { expect(child_1.descendants).to include child_4 }
175
+ end
176
+ end
data/spec/move_spec.rb ADDED
@@ -0,0 +1,94 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ActsAsOrderedTree, 'Movement via save', :transactional do
6
+ tree :factory => :default_with_counter_cache do
7
+ root {
8
+ child_1 {
9
+ child_2
10
+ child_3
11
+ }
12
+ child_4 {
13
+ child_5
14
+ }
15
+ }
16
+ end
17
+
18
+ def move(node, new_parent, new_position)
19
+ name = "category #{rand(100..1000)}"
20
+ node.parent = new_parent
21
+ node.position = new_position
22
+ node.name = name
23
+
24
+ node.save
25
+
26
+ expect(node.reload.parent).to eq new_parent
27
+ expect(node.position).to eq new_position
28
+ expect(node.name).to eq name
29
+ end
30
+
31
+ context 'when child 2 moved under child 4 to position 1' do
32
+ before { move child_2, child_4, 1 }
33
+
34
+ expect_tree_to_match {
35
+ root {
36
+ child_1 :categories_count => 1 do
37
+ child_3 :position => 1
38
+ end
39
+ child_4 :categories_count => 2 do
40
+ child_2 :position => 1
41
+ child_5 :position => 2
42
+ end
43
+ }
44
+ }
45
+ end
46
+
47
+ context 'when child 2 moved under child 4 to position 2' do
48
+ before { move child_2, child_4, 2 }
49
+
50
+ expect_tree_to_match {
51
+ root {
52
+ child_1 :categories_count => 1 do
53
+ child_3 :position => 1
54
+ end
55
+ child_4 :categories_count => 2 do
56
+ child_5 :position => 1
57
+ child_2 :position => 2
58
+ end
59
+ }
60
+ }
61
+ end
62
+
63
+ context 'when level changed' do
64
+ before { move child_1, child_4, 1 }
65
+
66
+ expect_tree_to_match {
67
+ root {
68
+ child_4 :categories_count => 2 do
69
+ child_1 :position => 1, :categories_count => 2, :depth => 2 do
70
+ child_2 :position => 1, :depth => 3
71
+ child_3 :position => 2, :depth => 3
72
+ end
73
+ child_5 :position => 2, :depth => 2
74
+ end
75
+ }
76
+ }
77
+ end
78
+
79
+ context 'when node moved to root' do
80
+ before { move child_1, nil, 1 }
81
+
82
+ expect_tree_to_match {
83
+ child_1 :position => 1, :depth => 0 do
84
+ child_2 :position => 1, :depth => 1
85
+ child_3 :position => 2, :depth => 1
86
+ end
87
+ root :position => 2, :depth => 0 do
88
+ child_4 :depth => 1 do
89
+ child_5 :depth => 2
90
+ end
91
+ end
92
+ }
93
+ end
94
+ end