acts_as_list 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +11 -0
- data/lib/acts_as_list.rb +2 -0
- data/lib/acts_as_list/active_record/acts/list.rb +22 -14
- data/lib/acts_as_list/version.rb +1 -1
- data/test/shared_array_scope_list.rb +0 -1
- data/test/test_list.rb +113 -0
- metadata +10 -10
data/README.md
CHANGED
@@ -4,6 +4,15 @@
|
|
4
4
|
|
5
5
|
This `acts_as` extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a `position` column defined as an integer on the mapped database table.
|
6
6
|
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
In your Gemfile:
|
10
|
+
|
11
|
+
gem 'acts_as_list'
|
12
|
+
|
13
|
+
Or, from the command line:
|
14
|
+
|
15
|
+
gem install acts_as_list
|
7
16
|
|
8
17
|
## Example
|
9
18
|
|
@@ -22,6 +31,8 @@ This `acts_as` extension provides the capabilities for sorting and reordering a
|
|
22
31
|
## Notes
|
23
32
|
If the `position` column has a default value, then there is a slight change in behavior, i.e if you have 4 items in the list, and you insert 1, with a default position 0, it would be pushed to the bottom of the list. Please look at the tests for this and some recent pull requests for discussions related to this.
|
24
33
|
|
34
|
+
All `position` queries (select, update, etc.) inside gem methods are executed without the default scope (i.e. `Model.unscoped`), this will prevent nasty issues when the default scope is different from `acts_as_list` scope.
|
35
|
+
|
25
36
|
## Versions
|
26
37
|
All versions `0.1.5` onwards require Rails 3.0.x and higher.
|
27
38
|
|
data/lib/acts_as_list.rb
CHANGED
@@ -77,6 +77,7 @@ module ActiveRecord
|
|
77
77
|
|
78
78
|
after_destroy :decrement_positions_on_lower_items
|
79
79
|
before_create :add_to_list_#{configuration[:add_new_at]}
|
80
|
+
after_update :update_positions
|
80
81
|
EOV
|
81
82
|
end
|
82
83
|
end
|
@@ -166,7 +167,7 @@ module ActiveRecord
|
|
166
167
|
# Return the next higher item in the list.
|
167
168
|
def higher_item
|
168
169
|
return nil unless in_list?
|
169
|
-
acts_as_list_class.find(:first, :conditions =>
|
170
|
+
acts_as_list_class.unscoped.find(:first, :conditions =>
|
170
171
|
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
|
171
172
|
)
|
172
173
|
end
|
@@ -174,7 +175,7 @@ module ActiveRecord
|
|
174
175
|
# Return the next lower item in the list.
|
175
176
|
def lower_item
|
176
177
|
return nil unless in_list?
|
177
|
-
acts_as_list_class.find(:first, :conditions =>
|
178
|
+
acts_as_list_class.unscoped.find(:first, :conditions =>
|
178
179
|
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
|
179
180
|
)
|
180
181
|
end
|
@@ -224,7 +225,7 @@ module ActiveRecord
|
|
224
225
|
def bottom_item(except = nil)
|
225
226
|
conditions = scope_condition
|
226
227
|
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
227
|
-
acts_as_list_class.unscoped.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
|
228
|
+
acts_as_list_class.unscoped.find(:first, :conditions => conditions, :order => "#{acts_as_list_class.table_name}.#{position_column} DESC")
|
228
229
|
end
|
229
230
|
|
230
231
|
# Forces item to assume the bottom position in the list.
|
@@ -239,7 +240,7 @@ module ActiveRecord
|
|
239
240
|
|
240
241
|
# This has the effect of moving all the higher items up one.
|
241
242
|
def decrement_positions_on_higher_items(position)
|
242
|
-
acts_as_list_class.update_all(
|
243
|
+
acts_as_list_class.unscoped.update_all(
|
243
244
|
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
|
244
245
|
)
|
245
246
|
end
|
@@ -248,7 +249,7 @@ module ActiveRecord
|
|
248
249
|
def decrement_positions_on_lower_items(position=nil)
|
249
250
|
return unless in_list?
|
250
251
|
position ||= send(position_column).to_i
|
251
|
-
acts_as_list_class.update_all(
|
252
|
+
acts_as_list_class.unscoped.update_all(
|
252
253
|
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{position}"
|
253
254
|
)
|
254
255
|
end
|
@@ -256,44 +257,44 @@ module ActiveRecord
|
|
256
257
|
# This has the effect of moving all the higher items down one.
|
257
258
|
def increment_positions_on_higher_items
|
258
259
|
return unless in_list?
|
259
|
-
acts_as_list_class.update_all(
|
260
|
+
acts_as_list_class.unscoped.update_all(
|
260
261
|
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
|
261
262
|
)
|
262
263
|
end
|
263
264
|
|
264
265
|
# This has the effect of moving all the lower items down one.
|
265
266
|
def increment_positions_on_lower_items(position)
|
266
|
-
acts_as_list_class.update_all(
|
267
|
+
acts_as_list_class.unscoped.update_all(
|
267
268
|
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
|
268
269
|
)
|
269
270
|
end
|
270
271
|
|
271
272
|
# Increments position (<tt>position_column</tt>) of all items in the list.
|
272
273
|
def increment_positions_on_all_items
|
273
|
-
acts_as_list_class.update_all(
|
274
|
+
acts_as_list_class.unscoped.update_all(
|
274
275
|
"#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
|
275
276
|
)
|
276
277
|
end
|
277
278
|
|
278
279
|
# Reorders intermediate items to support moving an item from old_position to new_position.
|
279
|
-
def shuffle_positions_on_intermediate_items(old_position, new_position)
|
280
|
+
def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil)
|
280
281
|
return if old_position == new_position
|
281
|
-
|
282
|
+
avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{avoid_id}" : ''
|
282
283
|
if old_position < new_position
|
283
284
|
# Decrement position of intermediate items
|
284
285
|
#
|
285
286
|
# e.g., if moving an item from 2 to 5,
|
286
287
|
# move [3, 4, 5] to [2, 3, 4]
|
287
|
-
acts_as_list_class.update_all(
|
288
|
-
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{old_position} AND #{position_column} <= #{new_position}"
|
288
|
+
acts_as_list_class.unscoped.update_all(
|
289
|
+
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{old_position} AND #{position_column} <= #{new_position}#{avoid_id_condition}"
|
289
290
|
)
|
290
291
|
else
|
291
292
|
# Increment position of intermediate items
|
292
293
|
#
|
293
294
|
# e.g., if moving an item from 5 to 2,
|
294
295
|
# move [2, 3, 4] to [3, 4, 5]
|
295
|
-
acts_as_list_class.update_all(
|
296
|
-
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{new_position} AND #{position_column} < #{old_position}"
|
296
|
+
acts_as_list_class.unscoped.update_all(
|
297
|
+
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{new_position} AND #{position_column} < #{old_position}#{avoid_id_condition}"
|
297
298
|
)
|
298
299
|
end
|
299
300
|
end
|
@@ -317,6 +318,13 @@ module ActiveRecord
|
|
317
318
|
decrement_positions_on_lower_items(old_position)
|
318
319
|
end
|
319
320
|
end
|
321
|
+
|
322
|
+
def update_positions
|
323
|
+
old_position = send("#{position_column}_was").to_i
|
324
|
+
new_position = send(position_column).to_i
|
325
|
+
return unless acts_as_list_class.unscoped.where("#{position_column} = #{new_position}").count > 1
|
326
|
+
shuffle_positions_on_intermediate_items old_position, new_position, id
|
327
|
+
end
|
320
328
|
end
|
321
329
|
end
|
322
330
|
end
|
data/lib/acts_as_list/version.rb
CHANGED
@@ -41,7 +41,6 @@ module Shared
|
|
41
41
|
|
42
42
|
def test_injection
|
43
43
|
item = ArrayScopeListMixin.new(:parent_id => 1, :parent_type => 'ParentClass')
|
44
|
-
assert_equal '"mixins"."parent_id" = 1 AND "mixins"."parent_type" = \'ParentClass\'', item.scope_condition
|
45
44
|
assert_equal "pos", item.position_column
|
46
45
|
end
|
47
46
|
|
data/test/test_list.rb
CHANGED
@@ -10,6 +10,7 @@ def setup_db(position_options = {})
|
|
10
10
|
ActiveRecord::Schema.define(:version => 1) do
|
11
11
|
create_table :mixins do |t|
|
12
12
|
t.column :pos, :integer, position_options
|
13
|
+
t.column :active, :boolean, :default => true
|
13
14
|
t.column :parent_id, :integer
|
14
15
|
t.column :parent_type, :string
|
15
16
|
t.column :created_at, :datetime
|
@@ -69,6 +70,11 @@ class DefaultScopedMixin < Mixin
|
|
69
70
|
default_scope { order('pos ASC') }
|
70
71
|
end
|
71
72
|
|
73
|
+
class DefaultScopedWhereMixin < Mixin
|
74
|
+
acts_as_list :column => "pos"
|
75
|
+
default_scope { order('pos ASC').where(:active => true) }
|
76
|
+
end
|
77
|
+
|
72
78
|
class TopAdditionMixin < Mixin
|
73
79
|
acts_as_list :column => "pos", :add_new_at => :top, :scope => :parent_id
|
74
80
|
end
|
@@ -235,12 +241,119 @@ class DefaultScopedTest < ActsAsListTestCase
|
|
235
241
|
assert_equal 4, new4.pos
|
236
242
|
end
|
237
243
|
|
244
|
+
def test_update_position
|
245
|
+
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
246
|
+
DefaultScopedMixin.find(2).update_attribute(:pos, 4)
|
247
|
+
assert_equal [1, 3, 4, 2], DefaultScopedMixin.find(:all).map(&:id)
|
248
|
+
DefaultScopedMixin.find(2).update_attribute(:pos, 2)
|
249
|
+
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
250
|
+
DefaultScopedMixin.find(1).update_attribute(:pos, 4)
|
251
|
+
assert_equal [2, 3, 4, 1], DefaultScopedMixin.find(:all).map(&:id)
|
252
|
+
DefaultScopedMixin.find(1).update_attribute(:pos, 1)
|
253
|
+
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
class DefaultScopedWhereTest < ActsAsListTestCase
|
259
|
+
def setup
|
260
|
+
setup_db
|
261
|
+
(1..4).each { |counter| DefaultScopedWhereMixin.create! :pos => counter, :active => false }
|
262
|
+
end
|
263
|
+
|
264
|
+
def test_insert
|
265
|
+
new = DefaultScopedWhereMixin.create
|
266
|
+
assert_equal 5, new.pos
|
267
|
+
assert !new.first?
|
268
|
+
assert new.last?
|
269
|
+
|
270
|
+
new = DefaultScopedWhereMixin.create
|
271
|
+
assert_equal 6, new.pos
|
272
|
+
assert !new.first?
|
273
|
+
assert new.last?
|
274
|
+
|
275
|
+
new = DefaultScopedWhereMixin.create
|
276
|
+
assert_equal 7, new.pos
|
277
|
+
assert !new.first?
|
278
|
+
assert new.last?
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_reordering
|
282
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).map(&:id)
|
283
|
+
|
284
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).move_lower
|
285
|
+
assert_equal [1, 3, 2, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
286
|
+
|
287
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).move_higher
|
288
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
289
|
+
|
290
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).move_to_bottom
|
291
|
+
assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
292
|
+
|
293
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).move_to_top
|
294
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
295
|
+
|
296
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).move_to_bottom
|
297
|
+
assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
298
|
+
|
299
|
+
DefaultScopedWhereMixin.where(:active => false).find(4).move_to_top
|
300
|
+
assert_equal [4, 1, 3, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_insert_at
|
304
|
+
new = DefaultScopedWhereMixin.create
|
305
|
+
assert_equal 5, new.pos
|
306
|
+
|
307
|
+
new = DefaultScopedWhereMixin.create
|
308
|
+
assert_equal 6, new.pos
|
309
|
+
|
310
|
+
new = DefaultScopedWhereMixin.create
|
311
|
+
assert_equal 7, new.pos
|
312
|
+
|
313
|
+
new4 = DefaultScopedWhereMixin.create
|
314
|
+
assert_equal 8, new4.pos
|
315
|
+
|
316
|
+
new4.insert_at(2)
|
317
|
+
assert_equal 2, new4.pos
|
318
|
+
|
319
|
+
new.reload
|
320
|
+
assert_equal 8, new.pos
|
321
|
+
|
322
|
+
new.insert_at(2)
|
323
|
+
assert_equal 2, new.pos
|
324
|
+
|
325
|
+
new4.reload
|
326
|
+
assert_equal 3, new4.pos
|
327
|
+
|
328
|
+
new5 = DefaultScopedWhereMixin.create
|
329
|
+
assert_equal 9, new5.pos
|
330
|
+
|
331
|
+
new5.insert_at(1)
|
332
|
+
assert_equal 1, new5.pos
|
333
|
+
|
334
|
+
new4.reload
|
335
|
+
assert_equal 4, new4.pos
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_update_position
|
339
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
340
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).update_attribute(:pos, 4)
|
341
|
+
assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
342
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).update_attribute(:pos, 2)
|
343
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
344
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).update_attribute(:pos, 4)
|
345
|
+
assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
346
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).update_attribute(:pos, 1)
|
347
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
348
|
+
end
|
349
|
+
|
238
350
|
end
|
239
351
|
|
240
352
|
#class TopAdditionMixin < Mixin
|
241
353
|
|
242
354
|
class TopAdditionTest < ActsAsListTestCase
|
243
355
|
include Shared::TopAddition
|
356
|
+
|
244
357
|
def setup
|
245
358
|
setup_db
|
246
359
|
super
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_list
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-07-25 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
18
|
-
requirement: &
|
18
|
+
requirement: &2156341060 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ! '>='
|
@@ -23,10 +23,10 @@ dependencies:
|
|
23
23
|
version: 1.0.0
|
24
24
|
type: :development
|
25
25
|
prerelease: false
|
26
|
-
version_requirements: *
|
26
|
+
version_requirements: *2156341060
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
|
-
requirement: &
|
29
|
+
requirement: &2156340520 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
32
32
|
- - ! '>='
|
@@ -34,10 +34,10 @@ dependencies:
|
|
34
34
|
version: 1.15.4.7794
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
|
-
version_requirements: *
|
37
|
+
version_requirements: *2156340520
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: rdoc
|
40
|
-
requirement: &
|
40
|
+
requirement: &2156340020 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
43
|
- - ! '>='
|
@@ -45,10 +45,10 @@ dependencies:
|
|
45
45
|
version: '0'
|
46
46
|
type: :development
|
47
47
|
prerelease: false
|
48
|
-
version_requirements: *
|
48
|
+
version_requirements: *2156340020
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
50
|
name: sqlite3
|
51
|
-
requirement: &
|
51
|
+
requirement: &2156339420 !ruby/object:Gem::Requirement
|
52
52
|
none: false
|
53
53
|
requirements:
|
54
54
|
- - ! '>='
|
@@ -56,7 +56,7 @@ dependencies:
|
|
56
56
|
version: '0'
|
57
57
|
type: :development
|
58
58
|
prerelease: false
|
59
|
-
version_requirements: *
|
59
|
+
version_requirements: *2156339420
|
60
60
|
description: This "acts_as" extension provides the capabilities for sorting and reordering
|
61
61
|
a number of objects in a list. The class that has this specified needs to have a
|
62
62
|
"position" column defined as an integer on the mapped database table.
|