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