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 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
 
@@ -20,3 +20,5 @@ module ActsAsList
20
20
  end
21
21
  end
22
22
  end
23
+
24
+ ActsAsList::Railtie.insert
@@ -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
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts
3
3
  module List
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
6
6
  end
7
7
  end
@@ -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
 
@@ -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.6
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-04-19 00:00:00.000000000 Z
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: &2152347900 !ruby/object:Gem::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: *2152347900
26
+ version_requirements: *2156341060
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
- requirement: &2152347320 !ruby/object:Gem::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: *2152347320
37
+ version_requirements: *2156340520
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: rdoc
40
- requirement: &2152346840 !ruby/object:Gem::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: *2152346840
48
+ version_requirements: *2156340020
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: sqlite3
51
- requirement: &2152346240 !ruby/object:Gem::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: *2152346240
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.