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.
@@ -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
- update_attributes! position_column => nil
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
- update_attributes! position_column => self.send(position_column).to_i + 1
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
- update_attributes! position_column => self.send(position_column).to_i - 1
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} = #{(send(position_column).to_i - 1).to_s}"
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} = #{(send(position_column).to_i + 1).to_s}"
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
- update_attributes!(position_column => bottom_position_in_list(self).to_i + 1)
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
- update_attributes!(position_column => acts_as_list_top)
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
- self.update_attributes!(position_column => position)
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
- update_attributes!(position_column => 0)
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
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts
3
3
  module List
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.9"
5
5
  end
6
6
  end
7
7
  end
@@ -1,7 +1,11 @@
1
1
  module Shared
2
2
  module List
3
3
  def setup
4
- (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
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.create(:pos => 1, :parent_id => 5)
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.create(:pos => 3, :parent_id => 5)
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?
@@ -1,7 +1,11 @@
1
1
  module Shared
2
2
  module ListSub
3
3
  def setup
4
- (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
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
@@ -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! :pos => counter }
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).update_attributes!(:pos => 4)
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).update_attributes!(:pos => 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).update_attributes!(:pos => 4)
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).update_attributes!(:pos => 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).update_attributes!(:pos => 4)
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).update_attributes!(:pos => 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).update_attributes!(:pos => 4)
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).update_attributes!(:pos => 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.8
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-08-09 00:00:00.000000000 Z
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: &2152115680 !ruby/object:Gem::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: *2152115680
26
+ version_requirements: *2155911360
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
- requirement: &2152115040 !ruby/object:Gem::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: *2152115040
37
+ version_requirements: *2155910780
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: rdoc
40
- requirement: &2152114620 !ruby/object:Gem::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: *2152114620
48
+ version_requirements: *2155910340
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: sqlite3
51
- requirement: &2152114020 !ruby/object:Gem::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: *2152114020
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.