acts_as_list 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/acts_as_list/active_record/acts/list.rb +29 -9
- data/lib/acts_as_list/version.rb +1 -1
- data/test/shared_list.rb +11 -3
- data/test/shared_list_sub.rb +5 -1
- data/test/test_list.rb +115 -9
- metadata +10 -10
@@ -75,6 +75,14 @@ module ActiveRecord
|
|
75
75
|
|
76
76
|
#{scope_condition_method}
|
77
77
|
|
78
|
+
# only add to attr_accessible
|
79
|
+
# if the class has some mass_assignment_protection
|
80
|
+
|
81
|
+
unless accessible_attributes.blank?
|
82
|
+
attr_accessible :#{configuration[:column]}
|
83
|
+
end
|
84
|
+
|
85
|
+
before_destroy :reload_position
|
78
86
|
after_destroy :decrement_positions_on_lower_items
|
79
87
|
before_create :add_to_list_#{configuration[:add_new_at]}
|
80
88
|
after_update :update_positions
|
@@ -136,20 +144,20 @@ module ActiveRecord
|
|
136
144
|
def remove_from_list
|
137
145
|
if in_list?
|
138
146
|
decrement_positions_on_lower_items
|
139
|
-
|
147
|
+
set_list_position(nil)
|
140
148
|
end
|
141
149
|
end
|
142
150
|
|
143
151
|
# Increase the position of this item without adjusting the rest of the list.
|
144
152
|
def increment_position
|
145
153
|
return unless in_list?
|
146
|
-
|
154
|
+
set_list_position(self.send(position_column).to_i + 1)
|
147
155
|
end
|
148
156
|
|
149
157
|
# Decrease the position of this item without adjusting the rest of the list.
|
150
158
|
def decrement_position
|
151
159
|
return unless in_list?
|
152
|
-
|
160
|
+
set_list_position(self.send(position_column).to_i - 1)
|
153
161
|
end
|
154
162
|
|
155
163
|
# Return +true+ if this object is the first in the list.
|
@@ -168,7 +176,8 @@ module ActiveRecord
|
|
168
176
|
def higher_item
|
169
177
|
return nil unless in_list?
|
170
178
|
acts_as_list_class.unscoped.find(:first, :conditions =>
|
171
|
-
"#{scope_condition} AND #{position_column}
|
179
|
+
"#{scope_condition} AND #{position_column} < #{(send(position_column).to_i).to_s}",
|
180
|
+
:order => "#{acts_as_list_class.table_name}.#{position_column} DESC"
|
172
181
|
)
|
173
182
|
end
|
174
183
|
|
@@ -176,7 +185,8 @@ module ActiveRecord
|
|
176
185
|
def lower_item
|
177
186
|
return nil unless in_list?
|
178
187
|
acts_as_list_class.unscoped.find(:first, :conditions =>
|
179
|
-
"#{scope_condition} AND #{position_column}
|
188
|
+
"#{scope_condition} AND #{position_column} > #{(send(position_column).to_i).to_s}",
|
189
|
+
:order => "#{acts_as_list_class.table_name}.#{position_column} ASC"
|
180
190
|
)
|
181
191
|
end
|
182
192
|
|
@@ -197,6 +207,12 @@ module ActiveRecord
|
|
197
207
|
default_position == send(position_column)
|
198
208
|
end
|
199
209
|
|
210
|
+
# Sets the new position and saves it
|
211
|
+
def set_list_position(new_position)
|
212
|
+
send("#{position_column}=", new_position)
|
213
|
+
save!
|
214
|
+
end
|
215
|
+
|
200
216
|
private
|
201
217
|
def add_to_list_top
|
202
218
|
increment_positions_on_all_items
|
@@ -230,12 +246,12 @@ module ActiveRecord
|
|
230
246
|
|
231
247
|
# Forces item to assume the bottom position in the list.
|
232
248
|
def assume_bottom_position
|
233
|
-
|
249
|
+
set_list_position(bottom_position_in_list(self).to_i + 1)
|
234
250
|
end
|
235
251
|
|
236
252
|
# Forces item to assume the top position in the list.
|
237
253
|
def assume_top_position
|
238
|
-
|
254
|
+
set_list_position(acts_as_list_top)
|
239
255
|
end
|
240
256
|
|
241
257
|
# This has the effect of moving all the higher items up one.
|
@@ -307,14 +323,14 @@ module ActiveRecord
|
|
307
323
|
else
|
308
324
|
increment_positions_on_lower_items(position)
|
309
325
|
end
|
310
|
-
|
326
|
+
set_list_position(position)
|
311
327
|
end
|
312
328
|
|
313
329
|
# used by insert_at_position instead of remove_from_list, as postgresql raises error if position_column has non-null constraint
|
314
330
|
def store_at_0
|
315
331
|
if in_list?
|
316
332
|
old_position = send(position_column).to_i
|
317
|
-
|
333
|
+
set_list_position(0)
|
318
334
|
decrement_positions_on_lower_items(old_position)
|
319
335
|
end
|
320
336
|
end
|
@@ -325,6 +341,10 @@ module ActiveRecord
|
|
325
341
|
return unless acts_as_list_class.unscoped.where("#{scope_condition} AND #{position_column} = #{new_position}").count > 1
|
326
342
|
shuffle_positions_on_intermediate_items old_position, new_position, id
|
327
343
|
end
|
344
|
+
|
345
|
+
def reload_position
|
346
|
+
self.reload
|
347
|
+
end
|
328
348
|
end
|
329
349
|
end
|
330
350
|
end
|
data/lib/acts_as_list/version.rb
CHANGED
data/test/shared_list.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module Shared
|
2
2
|
module List
|
3
3
|
def setup
|
4
|
-
(1..4).each
|
4
|
+
(1..4).each do |counter|
|
5
|
+
node = ListMixin.new :parent_id => 5
|
6
|
+
node.pos = counter
|
7
|
+
node.save!
|
8
|
+
end
|
5
9
|
end
|
6
10
|
|
7
11
|
def test_reordering
|
@@ -204,14 +208,18 @@ module Shared
|
|
204
208
|
def test_before_create_callback_adds_to_given_position
|
205
209
|
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
206
210
|
|
207
|
-
new = ListMixin.
|
211
|
+
new = ListMixin.new(:parent_id => 5)
|
212
|
+
new.pos = 1
|
213
|
+
new.save!
|
208
214
|
assert_equal 1, new.pos
|
209
215
|
assert new.first?
|
210
216
|
assert !new.last?
|
211
217
|
|
212
218
|
assert_equal [5, 1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
213
219
|
|
214
|
-
new = ListMixin.
|
220
|
+
new = ListMixin.new(:parent_id => 5)
|
221
|
+
new.pos = 3
|
222
|
+
new.save!
|
215
223
|
assert_equal 3, new.pos
|
216
224
|
assert !new.first?
|
217
225
|
assert !new.last?
|
data/test/shared_list_sub.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module Shared
|
2
2
|
module ListSub
|
3
3
|
def setup
|
4
|
-
(1..4).each
|
4
|
+
(1..4).each do |i|
|
5
|
+
node = ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).new :parent_id => 5000
|
6
|
+
node.pos = i
|
7
|
+
node.save!
|
8
|
+
end
|
5
9
|
end
|
6
10
|
|
7
11
|
def test_reordering
|
data/test/test_list.rb
CHANGED
@@ -36,8 +36,27 @@ end
|
|
36
36
|
|
37
37
|
class Mixin < ActiveRecord::Base
|
38
38
|
self.table_name = 'mixins'
|
39
|
+
attr_accessible :active, :parent_id, :parent_type
|
39
40
|
end
|
40
41
|
|
42
|
+
class ProtectedMixin < ActiveRecord::Base
|
43
|
+
self.table_name = 'mixins'
|
44
|
+
attr_protected :active
|
45
|
+
end
|
46
|
+
|
47
|
+
class ProtectedListMixin < ProtectedMixin
|
48
|
+
acts_as_list :column => "pos"
|
49
|
+
end
|
50
|
+
|
51
|
+
class UnProtectedMixin < ActiveRecord::Base
|
52
|
+
self.table_name = 'mixins'
|
53
|
+
end
|
54
|
+
|
55
|
+
class UnProtectedListMixin < UnProtectedMixin
|
56
|
+
acts_as_list :column => "pos"
|
57
|
+
end
|
58
|
+
|
59
|
+
|
41
60
|
class ListMixin < Mixin
|
42
61
|
acts_as_list :column => "pos", :scope => :parent
|
43
62
|
end
|
@@ -164,7 +183,7 @@ end
|
|
164
183
|
class DefaultScopedTest < ActsAsListTestCase
|
165
184
|
def setup
|
166
185
|
setup_db
|
167
|
-
(1..4).each { |counter| DefaultScopedMixin.create!
|
186
|
+
(1..4).each { |counter| DefaultScopedMixin.create!({:pos => counter}) }
|
168
187
|
end
|
169
188
|
|
170
189
|
def test_insert
|
@@ -243,13 +262,13 @@ class DefaultScopedTest < ActsAsListTestCase
|
|
243
262
|
|
244
263
|
def test_update_position
|
245
264
|
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
246
|
-
DefaultScopedMixin.find(2).
|
265
|
+
DefaultScopedMixin.find(2).set_list_position(4)
|
247
266
|
assert_equal [1, 3, 4, 2], DefaultScopedMixin.find(:all).map(&:id)
|
248
|
-
DefaultScopedMixin.find(2).
|
267
|
+
DefaultScopedMixin.find(2).set_list_position(2)
|
249
268
|
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
250
|
-
DefaultScopedMixin.find(1).
|
269
|
+
DefaultScopedMixin.find(1).set_list_position(4)
|
251
270
|
assert_equal [2, 3, 4, 1], DefaultScopedMixin.find(:all).map(&:id)
|
252
|
-
DefaultScopedMixin.find(1).
|
271
|
+
DefaultScopedMixin.find(1).set_list_position(1)
|
253
272
|
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
254
273
|
end
|
255
274
|
|
@@ -337,18 +356,63 @@ class DefaultScopedWhereTest < ActsAsListTestCase
|
|
337
356
|
|
338
357
|
def test_update_position
|
339
358
|
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
340
|
-
DefaultScopedWhereMixin.where(:active => false).find(2).
|
359
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).set_list_position(4)
|
341
360
|
assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
342
|
-
DefaultScopedWhereMixin.where(:active => false).find(2).
|
361
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).set_list_position(2)
|
343
362
|
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
344
|
-
DefaultScopedWhereMixin.where(:active => false).find(1).
|
363
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).set_list_position(4)
|
345
364
|
assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
346
|
-
DefaultScopedWhereMixin.where(:active => false).find(1).
|
365
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).set_list_position(1)
|
347
366
|
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
348
367
|
end
|
349
368
|
|
350
369
|
end
|
351
370
|
|
371
|
+
class MultiDestroyTest < ActsAsListTestCase
|
372
|
+
|
373
|
+
def setup
|
374
|
+
setup_db
|
375
|
+
end
|
376
|
+
|
377
|
+
# example:
|
378
|
+
#
|
379
|
+
# class TodoList < ActiveRecord::Base
|
380
|
+
# has_many :todo_items, :order => "position"
|
381
|
+
# accepts_nested_attributes_for :todo_items, :allow_destroy => true
|
382
|
+
# end
|
383
|
+
#
|
384
|
+
# class TodoItem < ActiveRecord::Base
|
385
|
+
# belongs_to :todo_list
|
386
|
+
# acts_as_list :scope => :todo_list
|
387
|
+
# end
|
388
|
+
#
|
389
|
+
# Assume that there are three items.
|
390
|
+
# The user mark two items as deleted, click save button, form will be post:
|
391
|
+
#
|
392
|
+
# todo_list.todo_items_attributes = [
|
393
|
+
# {id: 1, _destroy: true},
|
394
|
+
# {id: 2, _destroy: true}
|
395
|
+
# ]
|
396
|
+
#
|
397
|
+
# Save toto_list, the position of item #3 should eql 1.
|
398
|
+
#
|
399
|
+
def test_destroy
|
400
|
+
new1 = DefaultScopedMixin.create
|
401
|
+
assert_equal 1, new1.pos
|
402
|
+
|
403
|
+
new2 = DefaultScopedMixin.create
|
404
|
+
assert_equal 2, new2.pos
|
405
|
+
|
406
|
+
new3 = DefaultScopedMixin.create
|
407
|
+
assert_equal 3, new3.pos
|
408
|
+
|
409
|
+
new1.destroy
|
410
|
+
new2.destroy
|
411
|
+
new3.reload
|
412
|
+
assert_equal 1, new3.pos
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
352
416
|
#class TopAdditionMixin < Mixin
|
353
417
|
|
354
418
|
class TopAdditionTest < ActsAsListTestCase
|
@@ -360,6 +424,7 @@ class TopAdditionTest < ActsAsListTestCase
|
|
360
424
|
end
|
361
425
|
end
|
362
426
|
|
427
|
+
|
363
428
|
class TopAdditionTestWithDefault < ActsAsListTestCase
|
364
429
|
include Shared::TopAddition
|
365
430
|
|
@@ -368,3 +433,44 @@ class TopAdditionTestWithDefault < ActsAsListTestCase
|
|
368
433
|
super
|
369
434
|
end
|
370
435
|
end
|
436
|
+
|
437
|
+
class RespectMixinProtection < ActsAsListTestCase
|
438
|
+
def setup
|
439
|
+
setup_db_with_default
|
440
|
+
super
|
441
|
+
end
|
442
|
+
|
443
|
+
# if an attribute is set attr_protected
|
444
|
+
# it should be unchanged by update_attributes
|
445
|
+
def test_unmodified_protection
|
446
|
+
a = ProtectedMixin.new
|
447
|
+
a.update_attributes({:active => false})
|
448
|
+
assert_equal true, a.active
|
449
|
+
end
|
450
|
+
|
451
|
+
# even after the acts_as_list mixin is joined
|
452
|
+
# that protection should continue to exist
|
453
|
+
def test_still_protected
|
454
|
+
b = ProtectedListMixin.new
|
455
|
+
b.update_attributes({:active => false})
|
456
|
+
assert_equal true, b.active
|
457
|
+
end
|
458
|
+
|
459
|
+
# similarly, if a class lacks mass_assignment protection
|
460
|
+
# it should be able to be changed
|
461
|
+
def test_unprotected
|
462
|
+
a = UnProtectedMixin.new
|
463
|
+
a.update_attributes({:active => false})
|
464
|
+
assert_equal false, a.active
|
465
|
+
end
|
466
|
+
|
467
|
+
# and it should continue to be mutable by mass_assignment
|
468
|
+
# even after the acts_as_list plugin has been joined
|
469
|
+
def test_still_unprotected_mixin
|
470
|
+
b = UnProtectedListMixin.new
|
471
|
+
b.assign_attributes({:active => false})
|
472
|
+
# p UnProtectedListMixin.accessible_attributes.length
|
473
|
+
assert_equal false, b.active
|
474
|
+
end
|
475
|
+
|
476
|
+
end
|
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.9
|
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-12-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
18
|
-
requirement: &
|
18
|
+
requirement: &2155911360 !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: *2155911360
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
|
-
requirement: &
|
29
|
+
requirement: &2155910780 !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: *2155910780
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: rdoc
|
40
|
-
requirement: &
|
40
|
+
requirement: &2155910340 !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: *2155910340
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
50
|
name: sqlite3
|
51
|
-
requirement: &
|
51
|
+
requirement: &2155909700 !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: *2155909700
|
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.
|