acts_as_list 0.1.8 → 0.1.9

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.
@@ -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.