positionable 1.0.1
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.
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENCE +20 -0
- data/README.rdoc +117 -0
- data/Rakefile +1 -0
- data/lib/positionable.rb +314 -0
- data/lib/positionable/version.rb +3 -0
- data/positionable.gemspec +28 -0
- data/spec/factories.rb +53 -0
- data/spec/lib/positionable_spec.rb +596 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/matchers/contiguity_matcher.rb +26 -0
- data/spec/support/models.rb +41 -0
- data/spec/support/schema.rb +33 -0
- metadata +134 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "positionable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "positionable"
|
7
|
+
s.version = Positionable::VERSION
|
8
|
+
s.authors = ["Philippe Guégan"]
|
9
|
+
s.email = ["philippe.guegan@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/pguegan/positionable"
|
11
|
+
s.summary = %q(A gem for positionning your ActiveRecord models.)
|
12
|
+
s.description = %q(This extension provides contiguous positionning capabilities to you ActiveRecord models.)
|
13
|
+
|
14
|
+
s.rubyforge_project = "positionable"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
22
|
+
s.add_development_dependency "rspec", "~> 2.3"
|
23
|
+
s.add_development_dependency "sqlite3-ruby"
|
24
|
+
s.add_development_dependency "simplecov"
|
25
|
+
s.add_development_dependency "factory_girl"
|
26
|
+
|
27
|
+
s.add_dependency "activerecord", "~> 3.1"
|
28
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
|
3
|
+
factory :folder do
|
4
|
+
sequence(:title) { |n| "Folder #{n}" }
|
5
|
+
|
6
|
+
factory :folder_with_documents do
|
7
|
+
after_create do |folder|
|
8
|
+
folder.documents = FactoryGirl.create_list(:document, 5, :folder => folder)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
factory :document do
|
14
|
+
folder
|
15
|
+
sequence(:title) { |n| "Document #{n}" }
|
16
|
+
end
|
17
|
+
|
18
|
+
factory :default_item do
|
19
|
+
sequence(:title) { |n| "Default Item #{n}" }
|
20
|
+
end
|
21
|
+
|
22
|
+
factory :starting_at_one_item do
|
23
|
+
sequence(:title) { |n| "Starting At One Item #{n}" }
|
24
|
+
end
|
25
|
+
|
26
|
+
factory :asc_item do
|
27
|
+
sequence(:title) { |n| "Asc Item #{n}" }
|
28
|
+
end
|
29
|
+
|
30
|
+
factory :desc_item do
|
31
|
+
sequence(:title) { |n| "Desc Item #{n}" }
|
32
|
+
end
|
33
|
+
|
34
|
+
factory :group do
|
35
|
+
sequence(:title) { |n| "Group #{n}" }
|
36
|
+
|
37
|
+
factory :group_with_complex_items do
|
38
|
+
after_create do |group|
|
39
|
+
group.complex_items = FactoryGirl.create_list(:complex_item, 5, :group => group)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
factory :complex_item do
|
45
|
+
group
|
46
|
+
sequence(:title) { |n| "Complex Item #{n}" }
|
47
|
+
end
|
48
|
+
|
49
|
+
factory :stuff do
|
50
|
+
sequence(:title) { |n| "Stuff #{n}" }
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,596 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Positionable do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Document.delete_all
|
7
|
+
Folder.delete_all
|
8
|
+
Item.delete_all
|
9
|
+
Dummy.delete_all
|
10
|
+
end
|
11
|
+
|
12
|
+
context "ActiveRecord extension" do
|
13
|
+
|
14
|
+
it "does not extend non positionable models" do
|
15
|
+
dummy = Dummy.new
|
16
|
+
dummy.respond_to?(:previous).should be_false
|
17
|
+
dummy.respond_to?(:next).should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "extends positionable models" do
|
21
|
+
item = DefaultItem.new
|
22
|
+
item.respond_to?(:previous).should be_true
|
23
|
+
item.respond_to?(:next).should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "prepends the table name in SQL 'order by' clause" do
|
27
|
+
sql = DefaultItem.where("1 = 1").to_sql
|
28
|
+
table = DefaultItem.table_name
|
29
|
+
sql.should include("ORDER BY \"#{table}\".\"position\"")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context "ordering" do
|
35
|
+
|
36
|
+
it "orders records by their position by default" do
|
37
|
+
shuffle_positions = (0..9).to_a.shuffle
|
38
|
+
shuffle_positions.each do |position|
|
39
|
+
item = Factory.create(:default_item)
|
40
|
+
item.update_column(:position, position)
|
41
|
+
end
|
42
|
+
DefaultItem.all.should be_contiguous
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context "contiguous positionning" do
|
48
|
+
|
49
|
+
let!(:items) { FactoryGirl.create_list(:default_item, 10) }
|
50
|
+
let(:middle) { items[items.size / 2] }
|
51
|
+
|
52
|
+
it "makes the position to start at zero by default" do
|
53
|
+
items.first.position.should == 0
|
54
|
+
end
|
55
|
+
|
56
|
+
it "increments position by one after creation" do
|
57
|
+
item = Factory.create(:default_item)
|
58
|
+
item.position.should == items.last.position + 1
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does not exist a previous for the first record" do
|
62
|
+
items.first.previous.should be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "gives the previous record according to its position" do
|
66
|
+
items[1..(items.size - 1)].each_with_index do |item, index|
|
67
|
+
item.previous.should == items[index]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "gives all the previous records according to their positions" do
|
72
|
+
middle.all_previous.size.should == middle.position
|
73
|
+
middle.all_previous.each_with_index do |previous, index|
|
74
|
+
previous.should == items[index]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "does not exist a next for the last record" do
|
79
|
+
items.last.next.should be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it "gives the next record according to its position" do
|
83
|
+
items[0..(items.size - 2)].each_with_index do |item, index|
|
84
|
+
item.next.should == items[index + 1]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "gives all the next records according to their positions" do
|
89
|
+
middle.all_next.size.should == items.size - middle.position - 1
|
90
|
+
middle.all_next.each_with_index do |neXt, index|
|
91
|
+
neXt.should == items[middle.position + index + 1]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "caracterizes the first record" do
|
96
|
+
items.first.first?.should be_true
|
97
|
+
items.but_first.each do |item|
|
98
|
+
item.first?.should be_false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "caracterizes the last record" do
|
103
|
+
items.but_last.each do |item|
|
104
|
+
item.last?.should be_false
|
105
|
+
end
|
106
|
+
items.last.last?.should be_true
|
107
|
+
end
|
108
|
+
|
109
|
+
it "decrements positions of next sibblings after deletion" do
|
110
|
+
position = items.size / 2
|
111
|
+
middle.destroy
|
112
|
+
items.before(position).should be_contiguous
|
113
|
+
items.after(position).should be_contiguous.starting_at(position)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "does not up the first record" do
|
117
|
+
item = items.first
|
118
|
+
item.position.should == 0 # Meta!
|
119
|
+
item.up!
|
120
|
+
item.position.should == 0
|
121
|
+
end
|
122
|
+
|
123
|
+
it "does not down the last record" do
|
124
|
+
item = items.last
|
125
|
+
item.position.should == items.size - 1 # Meta!
|
126
|
+
item.down!
|
127
|
+
item.position.should == items.size - 1
|
128
|
+
end
|
129
|
+
|
130
|
+
it "reorders the records positions after upping" do
|
131
|
+
position = middle.position
|
132
|
+
previous = middle.previous
|
133
|
+
neXt = middle.next
|
134
|
+
previous.position.should == position - 1 # Meta!
|
135
|
+
neXt.position.should == position + 1 # Meta!
|
136
|
+
middle.up!
|
137
|
+
previous.reload.position.should == position
|
138
|
+
middle.position.should == position - 1
|
139
|
+
neXt.reload.position.should == position + 1
|
140
|
+
end
|
141
|
+
|
142
|
+
it "reorders the records positions after downing" do
|
143
|
+
position = middle.position
|
144
|
+
previous = middle.previous
|
145
|
+
neXt = middle.next
|
146
|
+
previous.position.should == position - 1 # Meta!
|
147
|
+
neXt.position.should == position + 1 # Meta!
|
148
|
+
middle.down!
|
149
|
+
previous.reload.position.should == position - 1
|
150
|
+
middle.position.should == position + 1
|
151
|
+
neXt.reload.position.should == position
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "moving" do
|
155
|
+
|
156
|
+
context "mass-assignement" do
|
157
|
+
|
158
|
+
it "reorders records when position is updated" do
|
159
|
+
old_position = middle.position
|
160
|
+
new_position = old_position + 3
|
161
|
+
middle.update_attributes({ :position => new_position })
|
162
|
+
(0..(old_position - 1)).each do |position|
|
163
|
+
items[position].reload.position.should == position
|
164
|
+
end
|
165
|
+
middle.position.should == new_position
|
166
|
+
((old_position + 1)..new_position).each do |position|
|
167
|
+
items[position].reload.position.should == position - 1
|
168
|
+
end
|
169
|
+
((new_position + 1)..(items.count - 1)).each do |position|
|
170
|
+
items[position].reload.position.should == position
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it "does not reorder anything when position is updated but out of range" do
|
175
|
+
middle.update_attributes({ :position => items.count + 10 })
|
176
|
+
items.should be_contiguous
|
177
|
+
end
|
178
|
+
|
179
|
+
it "does not reorder anything when position is updated but before start" do
|
180
|
+
middle.update_attributes({ :position => -1 })
|
181
|
+
items.should be_contiguous
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
it "also moves the previous records when moving to a lower position" do
|
187
|
+
old_position = middle.position
|
188
|
+
new_position = old_position - 3
|
189
|
+
middle.move_to new_position
|
190
|
+
(0..(new_position - 1)).each do |position|
|
191
|
+
items[position].reload.position.should == position
|
192
|
+
end
|
193
|
+
middle.position.should == new_position
|
194
|
+
(new_position..(old_position - 1)).each do |position|
|
195
|
+
items[position].reload.position.should == position + 1
|
196
|
+
end
|
197
|
+
((old_position + 1)..(items.count - 1)).each do |position|
|
198
|
+
items[position].reload.position.should == position
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it "also moves the next records when moving to a higher position" do
|
203
|
+
old_position = middle.position
|
204
|
+
new_position = old_position + 3
|
205
|
+
middle.move_to new_position
|
206
|
+
(0..(old_position - 1)).each do |position|
|
207
|
+
items[position].reload.position.should == position
|
208
|
+
end
|
209
|
+
middle.position.should == new_position
|
210
|
+
((old_position + 1)..new_position).each do |position|
|
211
|
+
items[position].reload.position.should == position - 1
|
212
|
+
end
|
213
|
+
((new_position + 1)..(items.count - 1)).each do |position|
|
214
|
+
items[position].reload.position.should == position
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
it "does not move anything if new position is before start position" do
|
219
|
+
lambda {
|
220
|
+
middle.move_to -1
|
221
|
+
}.should_not change(middle, :position)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "does not move anything if new position is out of range" do
|
225
|
+
lambda {
|
226
|
+
middle.move_to items.count + 10
|
227
|
+
}.should_not change(middle, :position)
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
describe "range" do
|
235
|
+
|
236
|
+
let!(:items) { FactoryGirl.create_list(:default_item, 10) }
|
237
|
+
|
238
|
+
it "gives the range position of a new record" do
|
239
|
+
item = Factory.build(:default_item)
|
240
|
+
item.range.should == (0..items.count)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "gives the range position of an existing record" do
|
244
|
+
items.sample.range.should == (0..(items.count - 1))
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
context "scoping" do
|
250
|
+
|
251
|
+
let!(:folders) { FactoryGirl.create_list(:folder_with_documents, 5) }
|
252
|
+
|
253
|
+
it "orders records by their position by default" do
|
254
|
+
folders.each do |folder|
|
255
|
+
documents = folder.documents
|
256
|
+
shuffled_positions = (0..(documents.size - 1)).to_a.shuffle
|
257
|
+
documents.each_with_index do |document, index|
|
258
|
+
document.update_column(:position, shuffled_positions[index])
|
259
|
+
end
|
260
|
+
documents = folder.reload.documents
|
261
|
+
documents.should be_contiguous
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
it "makes the position to start at zero for each folder" do
|
266
|
+
folders.each do |folder|
|
267
|
+
folder.documents.first.position.should == 0
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
it "increments position by one after creation inside a folder" do
|
272
|
+
folders.each do |folder|
|
273
|
+
last_position = folder.documents.last.position
|
274
|
+
document = Factory.create(:document, :folder => folder)
|
275
|
+
document.position.should == last_position + 1
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
it "does not exist a previous for the first record of each folder" do
|
280
|
+
folders.each do |folder|
|
281
|
+
folder.documents.first.previous.should be_nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
it "gives the previous record of the folder according to its position" do
|
286
|
+
folders.each do |folder|
|
287
|
+
folder.documents.but_first.each_with_index do |document, index|
|
288
|
+
document.previous.should == folder.documents[index]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
it "gives all the previous records of the folder according to their positions" do
|
294
|
+
folders.each do |folder|
|
295
|
+
documents = folder.documents
|
296
|
+
middle = documents[documents.size / 2]
|
297
|
+
middle.all_previous.size.should == middle.position
|
298
|
+
middle.all_previous.each_with_index do |previous, index|
|
299
|
+
previous.should == documents[index]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it "does not exist a next for the last record of the folder" do
|
305
|
+
folders.each do |folder|
|
306
|
+
folder.documents.last.next.should be_nil
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
it "gives the next record of the folder according to its position" do
|
311
|
+
folders.each do |folder|
|
312
|
+
documents = folder.documents
|
313
|
+
documents.but_last.each_with_index do |document, index|
|
314
|
+
document.next.should == documents[index + 1]
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
it "gives all the next records of the folder according to their positions" do
|
320
|
+
folders.each do |folder|
|
321
|
+
documents = folder.documents
|
322
|
+
middle = documents[documents.size / 2]
|
323
|
+
middle.all_next.count.should == documents.count - middle.position - 1
|
324
|
+
middle.all_next.each_with_index do |neXt, index|
|
325
|
+
neXt.should == documents[middle.position + index + 1]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
it "caracterizes the first record of the folder" do
|
331
|
+
folders.each do |folder|
|
332
|
+
documents = folder.documents
|
333
|
+
documents.first.first?.should be_true
|
334
|
+
documents.but_first.each do |document|
|
335
|
+
document.first?.should be_false
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
it "caracterizes the last record of the folder" do
|
341
|
+
folders.each do |folder|
|
342
|
+
documents = folder.documents
|
343
|
+
documents.but_last.each do |document|
|
344
|
+
document.last?.should be_false
|
345
|
+
end
|
346
|
+
documents.last.last?.should be_true
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
it "decrements positions of next sibblings of the folder after deletion" do
|
351
|
+
folders.each do |folder|
|
352
|
+
documents = folder.documents
|
353
|
+
middle = documents.size / 2
|
354
|
+
documents[middle].destroy
|
355
|
+
documents.before(middle).should be_contiguous
|
356
|
+
documents.after(middle).should be_contiguous.starting_at(middle)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
it "does not up the first record of the folder" do
|
361
|
+
folders.each do |folder|
|
362
|
+
document = folder.documents.first
|
363
|
+
document.position.should == 0 # Meta!
|
364
|
+
document.up!
|
365
|
+
document.position.should == 0
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
it "does not down the last record of the folder" do
|
370
|
+
folders.each do |folder|
|
371
|
+
document = folder.documents.last
|
372
|
+
document.position.should == folder.documents.size - 1 # Meta!
|
373
|
+
document.down!
|
374
|
+
document.position.should == folder.documents.size - 1
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
it "reorders the records positions after upping" do
|
379
|
+
folders.each do |folder|
|
380
|
+
documents = folder.documents
|
381
|
+
middle = documents[documents.size / 2]
|
382
|
+
position = middle.position
|
383
|
+
previous = middle.previous
|
384
|
+
neXt = middle.next
|
385
|
+
previous.position.should == position - 1 # Meta!
|
386
|
+
neXt.position.should == position + 1 # Meta!
|
387
|
+
middle.up!
|
388
|
+
previous.reload.position.should == position
|
389
|
+
middle.position.should == position - 1
|
390
|
+
neXt.reload.position.should == position + 1
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
it "reorders the records positions after downing" do
|
395
|
+
folders.each do |folder|
|
396
|
+
documents = folder.documents
|
397
|
+
middle = documents[documents.size / 2]
|
398
|
+
position = middle.position
|
399
|
+
previous = middle.previous
|
400
|
+
neXt = middle.next
|
401
|
+
previous.position.should == position - 1 # Meta!
|
402
|
+
neXt.position.should == position + 1 # Meta!
|
403
|
+
middle.down!
|
404
|
+
previous.reload.position.should == position - 1
|
405
|
+
middle.position.should == position + 1
|
406
|
+
neXt.reload.position.should == position
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
context "changing scope" do
|
411
|
+
|
412
|
+
let!(:old_folder) { folders.first }
|
413
|
+
# Last document is a special case when changing scope, so it is avoided
|
414
|
+
let!(:document) { old_folder.documents.but_last.sample }
|
415
|
+
# A new folder containing a different count of documents than the old folder
|
416
|
+
let!(:new_folder) { Factory.create(:folder) }
|
417
|
+
let!(:new_documents) { FactoryGirl.create_list(:document, old_folder.documents.count + 1, :folder => new_folder) }
|
418
|
+
|
419
|
+
it "moves to bottom position when scope has changed but position is out of range" do
|
420
|
+
document.update_attributes( {:folder_id => new_folder.id, :position => new_documents.count + 10 } )
|
421
|
+
document.position.should == new_folder.documents.count - 1
|
422
|
+
document.last?.should be_true
|
423
|
+
end
|
424
|
+
|
425
|
+
it "keeps position when scope has changed but position belongs to range" do
|
426
|
+
position = document.position
|
427
|
+
document.update_attributes( {:folder_id => new_folder.id} )
|
428
|
+
document.position.should == position # Position unchanged
|
429
|
+
new_folder.reload.documents.should be_contiguous
|
430
|
+
end
|
431
|
+
|
432
|
+
it "reorders records of previous scope" do
|
433
|
+
document.update_attributes( {:folder_id => new_folder.id} )
|
434
|
+
old_folder.reload.documents.should be_contiguous
|
435
|
+
end
|
436
|
+
|
437
|
+
end
|
438
|
+
|
439
|
+
describe "range" do
|
440
|
+
|
441
|
+
context "new record" do
|
442
|
+
|
443
|
+
it "gives a range only if the scope is specified" do
|
444
|
+
lambda {
|
445
|
+
Document.new.range
|
446
|
+
}.should raise_error(Positionable::RangeWithoutScopeError)
|
447
|
+
end
|
448
|
+
|
449
|
+
it "gives the range within a scope" do
|
450
|
+
folder = Factory.create(:folder_with_documents)
|
451
|
+
document = Document.new
|
452
|
+
document.range(folder).should == (0..(folder.documents.count + 1))
|
453
|
+
end
|
454
|
+
|
455
|
+
it "gives the range within its own scope by default" do
|
456
|
+
document = Factory.build(:document)
|
457
|
+
folder = document.folder
|
458
|
+
document.range.should == (0..(folder.documents.count + 1))
|
459
|
+
end
|
460
|
+
|
461
|
+
it "gives the range within another scope" do
|
462
|
+
document = Factory.build(:document)
|
463
|
+
folder = Factory.create(:folder_with_documents)
|
464
|
+
document.folder.should_not == folder # Meta!
|
465
|
+
document.range(folder).should == (0..(folder.documents.count + 1))
|
466
|
+
end
|
467
|
+
|
468
|
+
end
|
469
|
+
|
470
|
+
context "existing record" do
|
471
|
+
|
472
|
+
it "gives the range within its own scope" do
|
473
|
+
folder = Factory.create(:folder_with_documents)
|
474
|
+
document = folder.documents.sample
|
475
|
+
document.range(folder).should == (0..folder.documents.count)
|
476
|
+
end
|
477
|
+
|
478
|
+
it "gives the range within another scope" do
|
479
|
+
document = Factory.create(:document)
|
480
|
+
folder = Factory.create(:folder_with_documents)
|
481
|
+
document.folder.should_not == folder # Meta!
|
482
|
+
document.range(folder).should == (0..(folder.documents.count + 1))
|
483
|
+
end
|
484
|
+
|
485
|
+
end
|
486
|
+
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
|
491
|
+
context "start position" do
|
492
|
+
|
493
|
+
let(:start) { 1 }
|
494
|
+
|
495
|
+
it "starts at the given position" do
|
496
|
+
item = Factory.create(:starting_at_one_item)
|
497
|
+
item.position.should == start
|
498
|
+
end
|
499
|
+
|
500
|
+
it "increments by one the given start position" do
|
501
|
+
items = FactoryGirl.create_list(:starting_at_one_item, 5)
|
502
|
+
item = Factory.create(:starting_at_one_item)
|
503
|
+
item.position.should == items.size + start
|
504
|
+
end
|
505
|
+
|
506
|
+
it "caracterizes the first record according the start position" do
|
507
|
+
items = FactoryGirl.create_list(:starting_at_one_item, 5)
|
508
|
+
items.first.first?.should be_true
|
509
|
+
items.but_first.each do |item|
|
510
|
+
item.first?.should be_false
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
it "caracterizes the last record according the start position" do
|
515
|
+
items = FactoryGirl.create_list(:starting_at_one_item, 5)
|
516
|
+
items.but_last.each do |item|
|
517
|
+
item.last?.should be_false
|
518
|
+
end
|
519
|
+
items.last.last?.should be_true
|
520
|
+
end
|
521
|
+
|
522
|
+
describe "moving" do
|
523
|
+
|
524
|
+
it "does not move anything if new position is before start position" do
|
525
|
+
items = FactoryGirl.create_list(:starting_at_one_item, 5)
|
526
|
+
item = items.sample
|
527
|
+
lambda {
|
528
|
+
item.move_to start - 1
|
529
|
+
}.should_not change(item, :position)
|
530
|
+
end
|
531
|
+
|
532
|
+
end
|
533
|
+
|
534
|
+
describe "range" do
|
535
|
+
|
536
|
+
it "staggers range with start position" do
|
537
|
+
items = FactoryGirl.create_list(:starting_at_one_item, 5)
|
538
|
+
items.sample.range.should == (start..(items.count + start - 1))
|
539
|
+
end
|
540
|
+
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
544
|
+
|
545
|
+
context "insertion order" do
|
546
|
+
|
547
|
+
describe "asc" do
|
548
|
+
|
549
|
+
it "appends at the last position" do
|
550
|
+
items = FactoryGirl.create_list(:asc_item, 5)
|
551
|
+
item = Factory.create(:asc_item)
|
552
|
+
item.position.should == items.size
|
553
|
+
end
|
554
|
+
|
555
|
+
it "orders items by ascending position" do
|
556
|
+
FactoryGirl.create_list(:asc_item, 5)
|
557
|
+
AscItem.all.each_with_index do |item, index|
|
558
|
+
item.position.should == index
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
end
|
563
|
+
|
564
|
+
describe "desc" do
|
565
|
+
|
566
|
+
it "appends at the last position" do
|
567
|
+
items = FactoryGirl.create_list(:desc_item, 5)
|
568
|
+
item = Factory.create(:desc_item)
|
569
|
+
item.position.should == items.size
|
570
|
+
end
|
571
|
+
|
572
|
+
it "orders items by descending position" do
|
573
|
+
items = FactoryGirl.create_list(:desc_item, 5)
|
574
|
+
DescItem.all.reverse.should be_contiguous
|
575
|
+
end
|
576
|
+
|
577
|
+
end
|
578
|
+
|
579
|
+
end
|
580
|
+
|
581
|
+
context "mixing options" do
|
582
|
+
|
583
|
+
let!(:groups) { FactoryGirl.create_list(:group_with_complex_items, 5) }
|
584
|
+
let(:start) { 1 } # Check configuration in support/models.rb
|
585
|
+
|
586
|
+
it "manages complex items" do
|
587
|
+
# All options are tested here (grouping, descending ordering and start position at 1)
|
588
|
+
groups.each do |group|
|
589
|
+
size = group.complex_items.size
|
590
|
+
group.complex_items.reverse.should be_contiguous.starting_at(start)
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
end
|
595
|
+
|
596
|
+
end
|