acts_as_list 0.1.6 → 0.1.7
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/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.
|