sortifiable 0.1.2 → 0.2.0
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/CHANGELOG +7 -0
- data/lib/sortifiable.rb +156 -36
- data/lib/sortifiable/version.rb +1 -1
- data/test/sortifiable_test.rb +30 -0
- metadata +5 -5
data/CHANGELOG
CHANGED
data/lib/sortifiable.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Sortifiable
|
|
|
70
70
|
options[:class] = self
|
|
71
71
|
|
|
72
72
|
include InstanceMethods
|
|
73
|
-
before_create :
|
|
73
|
+
before_create :add_to_list
|
|
74
74
|
before_destroy :decrement_position_on_lower_items, :if => :in_list?
|
|
75
75
|
|
|
76
76
|
self.acts_as_list_options = options
|
|
@@ -85,9 +85,18 @@ module Sortifiable
|
|
|
85
85
|
module InstanceMethods
|
|
86
86
|
# Add the item to the end of the list
|
|
87
87
|
def add_to_list
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
if in_list?
|
|
89
|
+
move_to_bottom
|
|
90
|
+
else
|
|
91
|
+
list_class.transaction do
|
|
92
|
+
ids = lock_list!
|
|
93
|
+
last_position = ids.size
|
|
94
|
+
if persisted?
|
|
95
|
+
update_position(last_position + 1)
|
|
96
|
+
else
|
|
97
|
+
send("#{position_column}=".to_sym, last_position + 1)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
91
100
|
end
|
|
92
101
|
end
|
|
93
102
|
|
|
@@ -98,7 +107,7 @@ module Sortifiable
|
|
|
98
107
|
|
|
99
108
|
# Decrease the position of this item without adjusting the rest of the list.
|
|
100
109
|
def decrement_position
|
|
101
|
-
in_list? &&
|
|
110
|
+
in_list? && update_position(current_position - 1)
|
|
102
111
|
end
|
|
103
112
|
|
|
104
113
|
# Return +true+ if this object is the first in the list.
|
|
@@ -131,23 +140,44 @@ module Sortifiable
|
|
|
131
140
|
|
|
132
141
|
# Increase the position of this item without adjusting the rest of the list.
|
|
133
142
|
def increment_position
|
|
134
|
-
in_list? &&
|
|
143
|
+
in_list? && update_position(current_position + 1)
|
|
135
144
|
end
|
|
136
145
|
|
|
137
146
|
# Insert the item at the given position (defaults to the top position of 1).
|
|
138
147
|
def insert_at(position = 1)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
list_class.transaction do
|
|
149
|
+
ids = lock_list!
|
|
150
|
+
position = [[1, position].max, ids.size].min
|
|
151
|
+
|
|
152
|
+
if persisted?
|
|
153
|
+
current_position = ids.index(id) + 1
|
|
154
|
+
|
|
155
|
+
sql = <<-SQL
|
|
156
|
+
#{quoted_position_column} = CASE
|
|
157
|
+
WHEN #{quoted_position_column} = #{current_position} THEN #{position}
|
|
158
|
+
WHEN #{quoted_position_column} > #{current_position}
|
|
159
|
+
AND #{quoted_position_column} < #{position} THEN #{quoted_position_column} - 1
|
|
160
|
+
WHEN #{quoted_position_column} < #{current_position}
|
|
161
|
+
AND #{quoted_position_column} >= #{position} THEN #{quoted_position_column} + 1
|
|
162
|
+
ELSE #{quoted_position_column}
|
|
163
|
+
END
|
|
164
|
+
SQL
|
|
165
|
+
|
|
166
|
+
list_scope.update_all(sql)
|
|
167
|
+
update_position(position)
|
|
168
|
+
else
|
|
169
|
+
save!
|
|
170
|
+
|
|
171
|
+
sql = <<-SQL
|
|
172
|
+
#{quoted_position_column} = CASE
|
|
173
|
+
WHEN #{quoted_position_column} >= #{position} THEN #{quoted_position_column} + 1
|
|
174
|
+
ELSE #{quoted_position_column}
|
|
175
|
+
END
|
|
176
|
+
SQL
|
|
177
|
+
|
|
178
|
+
list_scope.update_all(sql)
|
|
179
|
+
update_position(position)
|
|
148
180
|
end
|
|
149
|
-
else
|
|
150
|
-
false
|
|
151
181
|
end
|
|
152
182
|
end
|
|
153
183
|
|
|
@@ -188,34 +218,125 @@ module Sortifiable
|
|
|
188
218
|
|
|
189
219
|
# Swap positions with the next higher item, if one exists.
|
|
190
220
|
def move_higher
|
|
191
|
-
in_list?
|
|
221
|
+
if in_list?
|
|
222
|
+
list_class.transaction do
|
|
223
|
+
ids = lock_list!
|
|
224
|
+
current_position, last_position = ids.index(id) + 1, ids.size
|
|
225
|
+
|
|
226
|
+
if current_position > 1
|
|
227
|
+
sql = <<-SQL
|
|
228
|
+
#{quoted_position_column} = CASE
|
|
229
|
+
WHEN #{quoted_position_column} = #{current_position} - 1 THEN #{current_position}
|
|
230
|
+
WHEN #{quoted_position_column} = #{current_position} THEN #{current_position} - 1
|
|
231
|
+
ELSE #{quoted_position_column}
|
|
232
|
+
END
|
|
233
|
+
SQL
|
|
234
|
+
|
|
235
|
+
send("#{position_column}=".to_sym, current_position - 1)
|
|
236
|
+
list_scope.update_all(sql) > 0
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
false
|
|
241
|
+
end
|
|
192
242
|
end
|
|
193
243
|
alias_method :move_up, :move_higher
|
|
194
244
|
|
|
195
245
|
# Swap positions with the next lower item, if one exists.
|
|
196
246
|
def move_lower
|
|
197
|
-
in_list?
|
|
247
|
+
if in_list?
|
|
248
|
+
list_class.transaction do
|
|
249
|
+
ids = lock_list!
|
|
250
|
+
current_position, last_position = ids.index(id) + 1, ids.size
|
|
251
|
+
|
|
252
|
+
if current_position < last_position
|
|
253
|
+
sql = <<-SQL
|
|
254
|
+
#{quoted_position_column} = CASE
|
|
255
|
+
WHEN #{quoted_position_column} = #{current_position} + 1 THEN #{current_position}
|
|
256
|
+
WHEN #{quoted_position_column} = #{current_position} THEN #{current_position} + 1
|
|
257
|
+
ELSE #{quoted_position_column}
|
|
258
|
+
END
|
|
259
|
+
SQL
|
|
260
|
+
|
|
261
|
+
send("#{position_column}=".to_sym, current_position + 1)
|
|
262
|
+
list_scope.update_all(sql) > 0
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
else
|
|
266
|
+
false
|
|
267
|
+
end
|
|
198
268
|
end
|
|
199
269
|
alias_method :move_down, :move_lower
|
|
200
270
|
|
|
201
271
|
# Move to the bottom of the list. If the item is already in the list,
|
|
202
272
|
# the items below it have their position adjusted accordingly.
|
|
203
273
|
def move_to_bottom
|
|
204
|
-
in_list?
|
|
274
|
+
if in_list?
|
|
275
|
+
list_class.transaction do
|
|
276
|
+
ids = lock_list!
|
|
277
|
+
current_position, last_position = ids.index(id) + 1, ids.size
|
|
278
|
+
|
|
279
|
+
if current_position < last_position
|
|
280
|
+
sql = <<-SQL
|
|
281
|
+
#{quoted_position_column} = CASE
|
|
282
|
+
WHEN #{quoted_position_column} = #{current_position} THEN #{last_position}
|
|
283
|
+
WHEN #{quoted_position_column} > #{current_position} THEN #{quoted_position_column} - 1
|
|
284
|
+
ELSE #{quoted_position_column}
|
|
285
|
+
END
|
|
286
|
+
SQL
|
|
287
|
+
|
|
288
|
+
send("#{position_column}=".to_sym, last_position)
|
|
289
|
+
list_scope.update_all(sql) > 0
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
else
|
|
293
|
+
false
|
|
294
|
+
end
|
|
205
295
|
end
|
|
206
296
|
|
|
207
297
|
# Move to the top of the list. If the item is already in the list,
|
|
208
298
|
# the items above it have their position adjusted accordingly.
|
|
209
299
|
def move_to_top
|
|
210
|
-
in_list?
|
|
300
|
+
if in_list?
|
|
301
|
+
list_class.transaction do
|
|
302
|
+
ids = lock_list!
|
|
303
|
+
current_position, last_position = ids.index(id) + 1, ids.size
|
|
304
|
+
|
|
305
|
+
if current_position > 1
|
|
306
|
+
sql = <<-SQL
|
|
307
|
+
#{quoted_position_column} = CASE
|
|
308
|
+
WHEN #{quoted_position_column} = #{current_position} THEN 1
|
|
309
|
+
WHEN #{quoted_position_column} < #{current_position} THEN #{quoted_position_column} + 1
|
|
310
|
+
ELSE #{quoted_position_column}
|
|
311
|
+
END
|
|
312
|
+
SQL
|
|
313
|
+
|
|
314
|
+
send("#{position_column}=".to_sym, 1)
|
|
315
|
+
list_scope.update_all(sql) > 0
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
else
|
|
319
|
+
false
|
|
320
|
+
end
|
|
211
321
|
end
|
|
212
322
|
|
|
213
323
|
# Removes the item from the list.
|
|
214
324
|
def remove_from_list
|
|
215
325
|
if in_list?
|
|
216
326
|
list_class.transaction do
|
|
217
|
-
|
|
218
|
-
|
|
327
|
+
ids = lock_list!
|
|
328
|
+
current_position, last_position = ids.index(id) + 1, ids.size
|
|
329
|
+
|
|
330
|
+
sql = <<-SQL
|
|
331
|
+
#{quoted_position_column} = CASE
|
|
332
|
+
WHEN #{quoted_position_column} = #{current_position} THEN NULL
|
|
333
|
+
WHEN #{quoted_position_column} > #{current_position} THEN #{quoted_position_column} - 1
|
|
334
|
+
ELSE #{quoted_position_column}
|
|
335
|
+
END
|
|
336
|
+
SQL
|
|
337
|
+
|
|
338
|
+
send("#{position_column}=".to_sym, nil)
|
|
339
|
+
list_scope.update_all(sql) > 0
|
|
219
340
|
end
|
|
220
341
|
else
|
|
221
342
|
false
|
|
@@ -223,20 +344,14 @@ module Sortifiable
|
|
|
223
344
|
end
|
|
224
345
|
|
|
225
346
|
private
|
|
226
|
-
def add_to_list_bottom #:nodoc:
|
|
227
|
-
send("#{position_column}=".to_sym, last_position + 1)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
347
|
def base_scope #:nodoc:
|
|
231
348
|
list_class.unscoped.where(scope_condition)
|
|
232
349
|
end
|
|
233
350
|
|
|
234
351
|
def decrement_position_on_lower_items #:nodoc:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def increment_position_on_lower_items(position) #:nodoc:
|
|
239
|
-
lower_scope(position).update_all(position_update('+ 1'))
|
|
352
|
+
update = "#{quoted_position_column} = #{quoted_position_column} - 1"
|
|
353
|
+
conditions = "#{quoted_position_column} > #{current_position}"
|
|
354
|
+
list_scope.update_all(update, conditions) > 0
|
|
240
355
|
end
|
|
241
356
|
|
|
242
357
|
def list_class #:nodoc:
|
|
@@ -247,6 +362,10 @@ module Sortifiable
|
|
|
247
362
|
base_scope.order(position_column).where("#{quoted_position_column} IS NOT NULL")
|
|
248
363
|
end
|
|
249
364
|
|
|
365
|
+
def lock_list! #:nodoc:
|
|
366
|
+
connection.select_values(list_scope.select(list_class.primary_key).lock(true).to_sql)
|
|
367
|
+
end
|
|
368
|
+
|
|
250
369
|
def lower_scope(position) #:nodoc:
|
|
251
370
|
base_scope.where(["#{quoted_position_column} > ?", position])
|
|
252
371
|
end
|
|
@@ -259,10 +378,6 @@ module Sortifiable
|
|
|
259
378
|
acts_as_list_options[:column]
|
|
260
379
|
end
|
|
261
380
|
|
|
262
|
-
def position_update(direction) #:nodoc:
|
|
263
|
-
"#{quoted_position_column} = (#{quoted_position_column} #{direction})"
|
|
264
|
-
end
|
|
265
|
-
|
|
266
381
|
def quoted_position_column #:nodoc:
|
|
267
382
|
connection.quote_column_name(position_column)
|
|
268
383
|
end
|
|
@@ -274,6 +389,11 @@ module Sortifiable
|
|
|
274
389
|
Array.wrap(acts_as_list_options[:scope]).inject({}){ |m,k| m[k] = send(k); m }
|
|
275
390
|
end
|
|
276
391
|
end
|
|
392
|
+
|
|
393
|
+
def update_position(new_position)
|
|
394
|
+
list_class.update_all(["#{quoted_position_column} = ?", new_position], list_class.primary_key => id)
|
|
395
|
+
send("#{position_column}=".to_sym, new_position)
|
|
396
|
+
end
|
|
277
397
|
end
|
|
278
398
|
end
|
|
279
399
|
|
data/lib/sortifiable/version.rb
CHANGED
data/test/sortifiable_test.rb
CHANGED
|
@@ -129,6 +129,16 @@ class ListTest < Test::Unit::TestCase
|
|
|
129
129
|
assert_equal [4, 1, 3, 2], ListMixin.where('parent_id = 5').map(&:id)
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
def test_bounds_checking
|
|
133
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:pos)
|
|
134
|
+
|
|
135
|
+
ListMixin.find(1).move_higher
|
|
136
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:pos)
|
|
137
|
+
|
|
138
|
+
ListMixin.find(4).move_lower
|
|
139
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:pos)
|
|
140
|
+
end
|
|
141
|
+
|
|
132
142
|
def test_move_to_bottom_with_next_to_last_item
|
|
133
143
|
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
|
134
144
|
ListMixin.find(3).move_to_bottom
|
|
@@ -345,6 +355,16 @@ class ListSubTest < Test::Unit::TestCase
|
|
|
345
355
|
assert_equal [4, 1, 3, 2], ListMixin.where('parent_id = 5000').map(&:id)
|
|
346
356
|
end
|
|
347
357
|
|
|
358
|
+
def test_bounds_checking
|
|
359
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:pos)
|
|
360
|
+
|
|
361
|
+
ListMixin.find(1).move_higher
|
|
362
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:pos)
|
|
363
|
+
|
|
364
|
+
ListMixin.find(4).move_lower
|
|
365
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:pos)
|
|
366
|
+
end
|
|
367
|
+
|
|
348
368
|
def test_move_to_bottom_with_next_to_last_item
|
|
349
369
|
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
|
350
370
|
ListMixin.find(3).move_to_bottom
|
|
@@ -479,6 +499,16 @@ class ArrayScopeListTest < Test::Unit::TestCase
|
|
|
479
499
|
assert_equal [4, 1, 3, 2], ArrayScopeListMixin.where(conditions).map(&:id)
|
|
480
500
|
end
|
|
481
501
|
|
|
502
|
+
def test_bounds_checking
|
|
503
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:pos)
|
|
504
|
+
|
|
505
|
+
ArrayScopeListMixin.find(1).move_higher
|
|
506
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:pos)
|
|
507
|
+
|
|
508
|
+
ArrayScopeListMixin.find(4).move_lower
|
|
509
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:pos)
|
|
510
|
+
end
|
|
511
|
+
|
|
482
512
|
def test_move_to_bottom_with_next_to_last_item
|
|
483
513
|
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
|
484
514
|
ArrayScopeListMixin.find(3).move_to_bottom
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sortifiable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 23
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
- 1
|
|
9
8
|
- 2
|
|
10
|
-
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.2.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Andrew White
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-
|
|
18
|
+
date: 2011-04-07 00:00:00 +01:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -139,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
139
|
requirements: []
|
|
140
140
|
|
|
141
141
|
rubyforge_project:
|
|
142
|
-
rubygems_version: 1.
|
|
142
|
+
rubygems_version: 1.6.2
|
|
143
143
|
signing_key:
|
|
144
144
|
specification_version: 3
|
|
145
145
|
summary: Sort your models
|