mongoid_orderable 6.0.1 → 6.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -1
- data/lib/mongoid/orderable/{engine.rb → handlers/base.rb} +42 -81
- data/lib/mongoid/orderable/handlers/document.rb +24 -0
- data/lib/mongoid/orderable/handlers/document_embedded.rb +14 -0
- data/lib/mongoid/orderable/handlers/document_transactional.rb +35 -0
- data/lib/mongoid/orderable/handlers/transaction.rb +71 -0
- data/lib/mongoid/orderable/mixins/callbacks.rb +21 -7
- data/lib/mongoid/orderable/version.rb +1 -1
- data/lib/mongoid_orderable.rb +5 -1
- data/spec/integration/concurrency_spec.rb +232 -0
- data/spec/integration/customized_spec.rb +31 -0
- data/spec/integration/embedded_spec.rb +41 -0
- data/spec/integration/foreign_key_spec.rb +33 -0
- data/spec/integration/inherited_spec.rb +54 -0
- data/spec/integration/multiple_fields_spec.rb +554 -0
- data/spec/integration/multiple_scoped_spec.rb +63 -0
- data/spec/integration/no_indexed_spec.rb +23 -0
- data/spec/integration/scoped_spec.rb +151 -0
- data/spec/integration/simple_spec.rb +184 -0
- data/spec/integration/string_scoped_spec.rb +28 -0
- data/spec/integration/zero_based_spec.rb +161 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/models.rb +122 -0
- metadata +33 -5
- data/spec/mongoid/orderable_spec.rb +0 -1486
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultipleScopedOrderable do
|
4
|
+
|
5
|
+
shared_examples_for 'multiple_scoped_orderable' do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
3.times do
|
9
|
+
Apple.create
|
10
|
+
Orange.create
|
11
|
+
end
|
12
|
+
MultipleScopedOrderable.create! apple_id: 1, orange_id: 1
|
13
|
+
MultipleScopedOrderable.create! apple_id: 2, orange_id: 1
|
14
|
+
MultipleScopedOrderable.create! apple_id: 2, orange_id: 2
|
15
|
+
MultipleScopedOrderable.create! apple_id: 1, orange_id: 3
|
16
|
+
MultipleScopedOrderable.create! apple_id: 1, orange_id: 1
|
17
|
+
MultipleScopedOrderable.create! apple_id: 3, orange_id: 3
|
18
|
+
MultipleScopedOrderable.create! apple_id: 2, orange_id: 3
|
19
|
+
MultipleScopedOrderable.create! apple_id: 3, orange_id: 2
|
20
|
+
MultipleScopedOrderable.create! apple_id: 1, orange_id: 3
|
21
|
+
end
|
22
|
+
|
23
|
+
def apple_positions
|
24
|
+
MultipleScopedOrderable.order_by([:apple_id, :asc], [:posa, :asc]).map(&:posa)
|
25
|
+
end
|
26
|
+
|
27
|
+
def orange_positions
|
28
|
+
MultipleScopedOrderable.order_by([:orange_id, :asc], [:poso, :asc]).map(&:poso)
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'default positions' do
|
32
|
+
it { expect(apple_positions).to eq([1, 2, 3, 4, 1, 2, 3, 1, 2]) }
|
33
|
+
it { expect(orange_positions).to eq([1, 2, 3, 1, 2, 1, 2, 3, 4]) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'change the scope of the apple' do
|
37
|
+
let(:record) { MultipleScopedOrderable.first }
|
38
|
+
before do
|
39
|
+
record.update_attribute(:apple_id, 2)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should properly set the apple positions' do
|
43
|
+
expect(apple_positions).to eq([1, 2, 3, 1, 2, 3, 4, 1, 2])
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should not affect the orange positions' do
|
47
|
+
expect(orange_positions).to eq([1, 2, 3, 1, 2, 1, 2, 3, 4])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with transactions' do
|
53
|
+
enable_transactions!
|
54
|
+
|
55
|
+
it_behaves_like 'multiple_scoped_orderable'
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'without transactions' do
|
59
|
+
disable_transactions!
|
60
|
+
|
61
|
+
it_behaves_like 'multiple_scoped_orderable'
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NoIndexOrderable do
|
4
|
+
|
5
|
+
shared_examples_for 'no_index_orderable' do
|
6
|
+
|
7
|
+
it 'should not have index on position field' do
|
8
|
+
expect(NoIndexOrderable.index_specifications.detect {|spec| spec.key == :position }).to be_nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with transactions' do
|
13
|
+
enable_transactions!
|
14
|
+
|
15
|
+
it_behaves_like 'no_index_orderable'
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'without transactions' do
|
19
|
+
disable_transactions!
|
20
|
+
|
21
|
+
it_behaves_like 'no_index_orderable'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScopedOrderable do
|
4
|
+
|
5
|
+
shared_examples_for 'scoped_orderable' do
|
6
|
+
|
7
|
+
def positions
|
8
|
+
ScopedOrderable.order_by([:group_id, :asc], [:position, :asc]).map(&:position)
|
9
|
+
end
|
10
|
+
|
11
|
+
before :each do
|
12
|
+
2.times { ScopedOrderable.create! group_id: 1 }
|
13
|
+
3.times { ScopedOrderable.create! group_id: 2 }
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should set proper position while creation' do
|
17
|
+
expect(positions).to eq([1, 2, 1, 2, 3])
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'removement' do
|
21
|
+
it 'top' do
|
22
|
+
ScopedOrderable.where(position: 1, group_id: 1).destroy
|
23
|
+
expect(positions).to eq([1, 1, 2, 3])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'bottom' do
|
27
|
+
ScopedOrderable.where(position: 3, group_id: 2).destroy
|
28
|
+
expect(positions).to eq([1, 2, 1, 2])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'middle' do
|
32
|
+
ScopedOrderable.where(position: 2, group_id: 2).destroy
|
33
|
+
expect(positions).to eq([1, 2, 1, 2])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'inserting' do
|
38
|
+
it 'top' do
|
39
|
+
newbie = ScopedOrderable.create! move_to: :top, group_id: 1
|
40
|
+
expect(positions).to eq([1, 2, 3, 1, 2, 3])
|
41
|
+
expect(newbie.position).to eq(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'bottom' do
|
45
|
+
newbie = ScopedOrderable.create! move_to: :bottom, group_id: 2
|
46
|
+
expect(positions).to eq([1, 2, 1, 2, 3, 4])
|
47
|
+
expect(newbie.position).to eq(4)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'middle' do
|
51
|
+
newbie = ScopedOrderable.create! move_to: 2, group_id: 2
|
52
|
+
expect(positions).to eq([1, 2, 1, 2, 3, 4])
|
53
|
+
expect(newbie.position).to eq(2)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'middle (with a numeric string)' do
|
57
|
+
newbie = ScopedOrderable.create! move_to: '2', group_id: 2
|
58
|
+
expect(positions).to eq([1, 2, 1, 2, 3, 4])
|
59
|
+
expect(newbie.position).to eq(2)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'middle (with a non-numeric string)' do
|
63
|
+
expect do
|
64
|
+
ScopedOrderable.create! move_to: 'two', group_id: 2
|
65
|
+
end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'index' do
|
70
|
+
it 'is not on position alone' do
|
71
|
+
expect(ScopedOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).to be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'is on compound fields' do
|
75
|
+
expect(ScopedOrderable.index_specifications.detect { |spec| spec.key == { group_id: 1, position: 1 } }).to_not be_nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'scope movement' do
|
80
|
+
let(:record) { ScopedOrderable.where(group_id: 2, position: 2).first }
|
81
|
+
|
82
|
+
it 'to a new scope group' do
|
83
|
+
record.update_attributes group_id: 3
|
84
|
+
expect(positions).to eq([1, 2, 1, 2, 1])
|
85
|
+
expect(record.position).to eq(1)
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when moving to an existing scope group' do
|
89
|
+
it 'without a position' do
|
90
|
+
record.update_attributes group_id: 1
|
91
|
+
expect(positions).to eq([1, 2, 3, 1, 2])
|
92
|
+
expect(record.reload.position).to eq(3)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'with symbol position' do
|
96
|
+
record.update_attributes group_id: 1, move_to: :top
|
97
|
+
expect(positions).to eq([1, 2, 3, 1, 2])
|
98
|
+
expect(record.reload.position).to eq(1)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'with point position' do
|
102
|
+
record.update_attributes group_id: 1, move_to: 2
|
103
|
+
expect(positions).to eq([1, 2, 3, 1, 2])
|
104
|
+
expect(record.reload.position).to eq(2)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'with point position (with a numeric string)' do
|
108
|
+
record.update_attributes group_id: 1, move_to: '2'
|
109
|
+
expect(positions).to eq([1, 2, 3, 1, 2])
|
110
|
+
expect(record.reload.position).to eq(2)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'with point position (with a non-numeric string)' do
|
114
|
+
expect do
|
115
|
+
record.update_attributes group_id: 1, move_to: 'two'
|
116
|
+
end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'utility methods' do
|
122
|
+
it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
|
123
|
+
record1 = ScopedOrderable.where(group_id: 1, position: 1).first
|
124
|
+
record2 = ScopedOrderable.where(group_id: 1, position: 2).first
|
125
|
+
record3 = ScopedOrderable.where(group_id: 2, position: 1).first
|
126
|
+
record4 = ScopedOrderable.where(group_id: 2, position: 2).first
|
127
|
+
record5 = ScopedOrderable.where(group_id: 2, position: 3).first
|
128
|
+
expect(record1.next_items.to_a).to eq([record2])
|
129
|
+
expect(record5.previous_items.to_a).to eq([record3, record4])
|
130
|
+
expect(record3.previous_items.to_a).to eq([])
|
131
|
+
expect(record3.next_items.to_a).to eq([record4, record5])
|
132
|
+
expect(record1.next_item).to eq(record2)
|
133
|
+
expect(record2.previous_item).to eq(record1)
|
134
|
+
expect(record1.previous_item).to eq(nil)
|
135
|
+
expect(record2.next_item).to eq(nil)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'with transactions' do
|
141
|
+
enable_transactions!
|
142
|
+
|
143
|
+
it_behaves_like 'scoped_orderable'
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'without transactions' do
|
147
|
+
disable_transactions!
|
148
|
+
|
149
|
+
it_behaves_like 'scoped_orderable'
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleOrderable do
|
4
|
+
|
5
|
+
shared_examples_for 'simple_orderable' do
|
6
|
+
|
7
|
+
def positions
|
8
|
+
SimpleOrderable.pluck(:position).sort
|
9
|
+
end
|
10
|
+
|
11
|
+
before :each do
|
12
|
+
5.times { SimpleOrderable.create! }
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have proper position field' do
|
16
|
+
expect(SimpleOrderable.fields.key?('position')).to be true
|
17
|
+
expect(SimpleOrderable.fields['position'].options[:type]).to eq(Integer)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have index on position field' do
|
21
|
+
expect(SimpleOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).not_to be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should have a orderable base of 1' do
|
25
|
+
expect(SimpleOrderable.create!.orderable_top).to eq(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should set proper position while creation' do
|
29
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'removement' do
|
33
|
+
it 'top' do
|
34
|
+
SimpleOrderable.where(position: 1).destroy
|
35
|
+
expect(positions).to eq([1, 2, 3, 4])
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'bottom' do
|
39
|
+
SimpleOrderable.where(position: 5).destroy
|
40
|
+
expect(positions).to eq([1, 2, 3, 4])
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'middle' do
|
44
|
+
SimpleOrderable.where(position: 3).destroy
|
45
|
+
expect(positions).to eq([1, 2, 3, 4])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'inserting' do
|
50
|
+
it 'top' do
|
51
|
+
newbie = SimpleOrderable.create! move_to: :top
|
52
|
+
expect(positions).to eq([1, 2, 3, 4, 5, 6])
|
53
|
+
expect(newbie.position).to eq(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'bottom' do
|
57
|
+
newbie = SimpleOrderable.create! move_to: :bottom
|
58
|
+
expect(positions).to eq([1, 2, 3, 4, 5, 6])
|
59
|
+
expect(newbie.position).to eq(6)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'middle' do
|
63
|
+
newbie = SimpleOrderable.create! move_to: 4
|
64
|
+
expect(positions).to eq([1, 2, 3, 4, 5, 6])
|
65
|
+
expect(newbie.position).to eq(4)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'middle (with a numeric string)' do
|
69
|
+
newbie = SimpleOrderable.create! move_to: '4'
|
70
|
+
expect(positions).to eq([1, 2, 3, 4, 5, 6])
|
71
|
+
expect(newbie.position).to eq(4)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'middle (with a non-numeric string)' do
|
75
|
+
expect do
|
76
|
+
SimpleOrderable.create! move_to: 'four'
|
77
|
+
end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'simultaneous create and update' do
|
81
|
+
newbie = SimpleOrderable.new
|
82
|
+
newbie.send(:orderable_before_update) { }
|
83
|
+
expect(newbie.position).to eq(6)
|
84
|
+
another = SimpleOrderable.create!
|
85
|
+
expect(another.position).to eq(6)
|
86
|
+
newbie.save!
|
87
|
+
expect(positions).to eq([1, 2, 3, 4, 5, 6, 7])
|
88
|
+
expect(newbie.position).to eq(7)
|
89
|
+
expect(another.position).to eq(6)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'parallel updates' do
|
93
|
+
newbie = SimpleOrderable.new
|
94
|
+
newbie.send(:orderable_before_update) { }
|
95
|
+
another = SimpleOrderable.create!
|
96
|
+
newbie.save!
|
97
|
+
expect(positions).to eq([1, 2, 3, 4, 5, 6, 7])
|
98
|
+
expect(newbie.position).to eq(7)
|
99
|
+
expect(another.position).to eq(6)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'movement' do
|
104
|
+
it 'higher from top' do
|
105
|
+
record = SimpleOrderable.where(position: 1).first
|
106
|
+
record.update_attributes move_to: :higher
|
107
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
108
|
+
expect(record.reload.position).to eq(1)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'higher from bottom' do
|
112
|
+
record = SimpleOrderable.where(position: 5).first
|
113
|
+
record.update_attributes move_to: :higher
|
114
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
115
|
+
expect(record.reload.position).to eq(4)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'higher from middle' do
|
119
|
+
record = SimpleOrderable.where(position: 3).first
|
120
|
+
record.update_attributes move_to: :higher
|
121
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
122
|
+
expect(record.reload.position).to eq(2)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'lower from top' do
|
126
|
+
record = SimpleOrderable.where(position: 1).first
|
127
|
+
record.update_attributes move_to: :lower
|
128
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
129
|
+
expect(record.reload.position).to eq(2)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'lower from bottom' do
|
133
|
+
record = SimpleOrderable.where(position: 5).first
|
134
|
+
record.update_attributes move_to: :lower
|
135
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
136
|
+
expect(record.reload.position).to eq(5)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'lower from middle' do
|
140
|
+
record = SimpleOrderable.where(position: 3).first
|
141
|
+
record.update_attributes move_to: :lower
|
142
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
143
|
+
expect(record.reload.position).to eq(4)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'does nothing if position not change' do
|
147
|
+
record = SimpleOrderable.where(position: 3).first
|
148
|
+
record.save
|
149
|
+
expect(positions).to eq([1, 2, 3, 4, 5])
|
150
|
+
expect(record.reload.position).to eq(3)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'utility methods' do
|
155
|
+
it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
|
156
|
+
record1 = SimpleOrderable.where(position: 1).first
|
157
|
+
record2 = SimpleOrderable.where(position: 2).first
|
158
|
+
record3 = SimpleOrderable.where(position: 3).first
|
159
|
+
record4 = SimpleOrderable.where(position: 4).first
|
160
|
+
record5 = SimpleOrderable.where(position: 5).first
|
161
|
+
expect(record1.next_items.to_a).to eq([record2, record3, record4, record5])
|
162
|
+
expect(record5.previous_items.to_a).to eq([record1, record2, record3, record4])
|
163
|
+
expect(record3.previous_items.to_a).to eq([record1, record2])
|
164
|
+
expect(record3.next_items.to_a).to eq([record4, record5])
|
165
|
+
expect(record1.next_item).to eq(record2)
|
166
|
+
expect(record2.previous_item).to eq(record1)
|
167
|
+
expect(record1.previous_item).to eq(nil)
|
168
|
+
expect(record5.next_item).to eq(nil)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'with transactions' do
|
174
|
+
enable_transactions!
|
175
|
+
|
176
|
+
it_behaves_like 'simple_orderable'
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'without transactions' do
|
180
|
+
disable_transactions!
|
181
|
+
|
182
|
+
it_behaves_like 'simple_orderable'
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StringScopedOrderable do
|
4
|
+
|
5
|
+
shared_examples_for 'string_scoped_orderable' do
|
6
|
+
|
7
|
+
it 'uses the foreign key of the relationship as scope' do
|
8
|
+
orderable1 = StringScopedOrderable.create!(some_scope: 1)
|
9
|
+
orderable2 = StringScopedOrderable.create!(some_scope: 1)
|
10
|
+
orderable3 = StringScopedOrderable.create!(some_scope: 2)
|
11
|
+
expect(orderable1.position).to eq 1
|
12
|
+
expect(orderable2.position).to eq 2
|
13
|
+
expect(orderable3.position).to eq 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with transactions' do
|
18
|
+
enable_transactions!
|
19
|
+
|
20
|
+
it_behaves_like 'string_scoped_orderable'
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'without transactions' do
|
24
|
+
disable_transactions!
|
25
|
+
|
26
|
+
it_behaves_like 'string_scoped_orderable'
|
27
|
+
end
|
28
|
+
end
|