acts_as_list 0.7.4 → 0.9.0

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.
@@ -1,10 +1,42 @@
1
+ class << ActiveRecord::Base
2
+ # Configuration options are:
3
+ #
4
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
5
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
6
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
7
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
8
+ # Example: <tt>acts_as_list scope: 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
9
+ # * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection
10
+ # act more like an array in its indexing.
11
+ # * +add_new_at+ - specifies whether objects get added to the :top or :bottom of the list. (default: +bottom+)
12
+ # `nil` will result in new items not being added to the list on create.
13
+ # * +sequential_updates+ - specifies whether insert_at should update objects positions during shuffling
14
+ # one by one to respect position column unique not null constraint.
15
+ # Defaults to true if position column has unique index, otherwise false.
16
+ # If constraint is <tt>deferrable initially deferred<tt>, overriding it with false will speed up insert_at.
17
+ def acts_as_list(options = {})
18
+ configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom }
19
+ configuration.update(options) if options.is_a?(Hash)
20
+
21
+ caller_class = self
22
+
23
+ ActiveRecord::Acts::List::ColumnMethodDefiner.call(caller_class, configuration[:column])
24
+ ActiveRecord::Acts::List::ScopeMethodDefiner.call(caller_class, configuration[:scope])
25
+ ActiveRecord::Acts::List::TopOfListMethodDefiner.call(caller_class, configuration[:top_of_list])
26
+ ActiveRecord::Acts::List::AddNewAtMethodDefiner.call(caller_class, configuration[:add_new_at])
27
+
28
+ ActiveRecord::Acts::List::AuxMethodDefiner.call(caller_class)
29
+ ActiveRecord::Acts::List::CallbackDefiner.call(caller_class, configuration[:add_new_at])
30
+ ActiveRecord::Acts::List::SequentialUpdatesMethodDefiner.call(caller_class, configuration[:column], configuration[:sequential_updates])
31
+
32
+ include ActiveRecord::Acts::List::InstanceMethods
33
+ include ActiveRecord::Acts::List::NoUpdate
34
+ end
35
+ end
36
+
1
37
  module ActiveRecord
2
38
  module Acts #:nodoc:
3
39
  module List #:nodoc:
4
- def self.included(base)
5
- base.extend(ClassMethods)
6
- end
7
-
8
40
  # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
9
41
  # The class that has this specified needs to have a +position+ column defined as an integer on
10
42
  # the mapped database table.
@@ -22,114 +54,6 @@ module ActiveRecord
22
54
  #
23
55
  # todo_list.first.move_to_bottom
24
56
  # todo_list.last.move_higher
25
- module ClassMethods
26
- # Configuration options are:
27
- #
28
- # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
29
- # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
30
- # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
31
- # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
32
- # Example: <tt>acts_as_list scope: 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
- # * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection
34
- # act more like an array in its indexing.
35
- # * +add_new_at+ - specifies whether objects get added to the :top or :bottom of the list. (default: +bottom+)
36
- # `nil` will result in new items not being added to the list on create
37
- def acts_as_list(options = {})
38
- configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom}
39
- configuration.update(options) if options.is_a?(Hash)
40
-
41
- if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
42
- configuration[:scope] = :"#{configuration[:scope]}_id"
43
- end
44
-
45
- if configuration[:scope].is_a?(Symbol)
46
- scope_methods = %(
47
- def scope_condition
48
- { #{configuration[:scope]}: send(:#{configuration[:scope]}) }
49
- end
50
-
51
- def scope_changed?
52
- changed.include?(scope_name.to_s)
53
- end
54
- )
55
- elsif configuration[:scope].is_a?(Array)
56
- scope_methods = %(
57
- def scope_condition
58
- #{configuration[:scope]}.inject({}) do |hash, column|
59
- hash.merge!({ column.to_sym => read_attribute(column.to_sym) })
60
- end
61
- end
62
-
63
- def scope_changed?
64
- (scope_condition.keys & changed.map(&:to_sym)).any?
65
- end
66
- )
67
- else
68
- scope_methods = %(
69
- def scope_condition
70
- "#{configuration[:scope]}"
71
- end
72
-
73
- def scope_changed?() false end
74
- )
75
- end
76
-
77
- class_eval <<-EOV, __FILE__, __LINE__ + 1
78
- include ::ActiveRecord::Acts::List::InstanceMethods
79
-
80
- def acts_as_list_top
81
- #{configuration[:top_of_list]}.to_i
82
- end
83
-
84
- def acts_as_list_class
85
- ::#{self.name}
86
- end
87
-
88
- def position_column
89
- '#{configuration[:column]}'
90
- end
91
-
92
- def scope_name
93
- '#{configuration[:scope]}'
94
- end
95
-
96
- def add_new_at
97
- '#{configuration[:add_new_at]}'
98
- end
99
-
100
- def #{configuration[:column]}=(position)
101
- write_attribute(:#{configuration[:column]}, position)
102
- @position_changed = true
103
- end
104
-
105
- #{scope_methods}
106
-
107
- # only add to attr_accessible
108
- # if the class has some mass_assignment_protection
109
-
110
- if defined?(accessible_attributes) and !accessible_attributes.blank?
111
- attr_accessible :#{configuration[:column]}
112
- end
113
-
114
- before_validation :check_top_position
115
-
116
- before_destroy :reload_position
117
- after_destroy :decrement_positions_on_lower_items
118
-
119
- before_update :check_scope
120
- after_update :update_positions
121
-
122
- after_commit 'remove_instance_variable(:@scope_changed) if defined?(@scope_changed)'
123
-
124
- scope :in_list, lambda { where("#{table_name}.#{configuration[:column]} IS NOT NULL") }
125
- EOV
126
-
127
- if configuration[:add_new_at].present?
128
- self.send :before_create, :"add_to_list_#{configuration[:add_new_at]}"
129
- end
130
-
131
- end
132
- end
133
57
 
134
58
  # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
135
59
  # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
@@ -146,8 +70,12 @@ module ActiveRecord
146
70
  return unless lower_item
147
71
 
148
72
  acts_as_list_class.transaction do
149
- lower_item.decrement_position
150
- increment_position
73
+ if lower_item.send(position_column) != self.send(position_column)
74
+ swap_positions(lower_item, self)
75
+ else
76
+ lower_item.decrement_position
77
+ increment_position
78
+ end
151
79
  end
152
80
  end
153
81
 
@@ -156,8 +84,12 @@ module ActiveRecord
156
84
  return unless higher_item
157
85
 
158
86
  acts_as_list_class.transaction do
159
- higher_item.increment_position
160
- decrement_position
87
+ if higher_item.send(position_column) != self.send(position_column)
88
+ swap_positions(higher_item, self)
89
+ else
90
+ higher_item.increment_position
91
+ decrement_position
92
+ end
161
93
  end
162
94
  end
163
95
 
@@ -208,16 +140,14 @@ module ActiveRecord
208
140
  set_list_position(self.send(position_column).to_i - 1)
209
141
  end
210
142
 
211
- # Return +true+ if this object is the first in the list.
212
143
  def first?
213
144
  return false unless in_list?
214
- self.send(position_column) == acts_as_list_top
145
+ !higher_item
215
146
  end
216
147
 
217
- # Return +true+ if this object is the last in the list.
218
148
  def last?
219
149
  return false unless in_list?
220
- self.send(position_column) == bottom_position_in_list
150
+ !lower_item
221
151
  end
222
152
 
223
153
  # Return the next higher item in the list.
@@ -232,10 +162,10 @@ module ActiveRecord
232
162
  limit ||= acts_as_list_list.count
233
163
  position_value = send(position_column)
234
164
  acts_as_list_list.
235
- where("#{position_column} < ?", position_value).
236
- where("#{position_column} >= ?", position_value - limit).
237
- limit(limit).
238
- order("#{acts_as_list_class.table_name}.#{position_column} ASC")
165
+ where("#{quoted_position_column_with_table_name} <= ?", position_value).
166
+ where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.send(self.class.primary_key)).
167
+ order("#{quoted_position_column_with_table_name} DESC").
168
+ limit(limit)
239
169
  end
240
170
 
241
171
  # Return the next lower item in the list.
@@ -250,10 +180,10 @@ module ActiveRecord
250
180
  limit ||= acts_as_list_list.count
251
181
  position_value = send(position_column)
252
182
  acts_as_list_list.
253
- where("#{position_column} > ?", position_value).
254
- where("#{position_column} <= ?", position_value + limit).
255
- limit(limit).
256
- order("#{acts_as_list_class.table_name}.#{position_column} ASC")
183
+ where("#{quoted_position_column_with_table_name} >= ?", position_value).
184
+ where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.send(self.class.primary_key)).
185
+ order("#{quoted_position_column_with_table_name} ASC").
186
+ limit(limit)
257
187
  end
258
188
 
259
189
  # Test if this record is in a list
@@ -280,214 +210,240 @@ module ActiveRecord
280
210
  end
281
211
 
282
212
  private
283
- def acts_as_list_list
284
- acts_as_list_class.unscoped do
285
- acts_as_list_class.where(scope_condition)
286
- end
213
+
214
+ def swap_positions(item1, item2)
215
+ item1_position = item1.send(position_column)
216
+
217
+ item1.set_list_position(item2.send(position_column))
218
+ item2.set_list_position(item1_position)
219
+ end
220
+
221
+ def acts_as_list_list
222
+ acts_as_list_class.unscoped do
223
+ acts_as_list_class.where(scope_condition)
287
224
  end
225
+ end
288
226
 
289
- def add_to_list_top
227
+ # Poorly named methods. They will insert the item at the desired position if the position
228
+ # has been set manually using position=, not necessarily the top or bottom of the list:
229
+
230
+ def add_to_list_top
231
+ if not_in_list? || internal_scope_changed? && !position_changed || default_position?
290
232
  increment_positions_on_all_items
291
233
  self[position_column] = acts_as_list_top
292
- # Make sure we know that we've processed this scope change already
293
- @scope_changed = false
294
- #dont halt the callback chain
295
- true
234
+ else
235
+ increment_positions_on_lower_items(self[position_column], id)
296
236
  end
297
237
 
298
- # A poorly named method. It will insert the item at the desired position if the position
299
- # has been set manually using position=, not necessarily the bottom of the list
300
- def add_to_list_bottom
301
- if not_in_list? || internal_scope_changed? && !@position_changed || default_position?
302
- self[position_column] = bottom_position_in_list.to_i + 1
303
- else
304
- increment_positions_on_lower_items(self[position_column], id)
305
- end
238
+ # Make sure we know that we've processed this scope change already
239
+ @scope_changed = false
306
240
 
307
- # Make sure we know that we've processed this scope change already
308
- @scope_changed = false
241
+ # Don't halt the callback chain
242
+ true
243
+ end
309
244
 
310
- #dont halt the callback chain
311
- true
245
+ def add_to_list_bottom
246
+ if not_in_list? || internal_scope_changed? && !position_changed || default_position?
247
+ self[position_column] = bottom_position_in_list.to_i + 1
248
+ else
249
+ increment_positions_on_lower_items(self[position_column], id)
312
250
  end
313
251
 
314
- # Overwrite this method to define the scope of the list changes
315
- def scope_condition() {} end
252
+ # Make sure we know that we've processed this scope change already
253
+ @scope_changed = false
316
254
 
317
- # Returns the bottom position number in the list.
318
- # bottom_position_in_list # => 2
319
- def bottom_position_in_list(except = nil)
320
- item = bottom_item(except)
321
- item ? item.send(position_column) : acts_as_list_top - 1
322
- end
255
+ # Don't halt the callback chain
256
+ true
257
+ end
323
258
 
324
- # Returns the bottom item
325
- def bottom_item(except = nil)
326
- conditions = scope_condition
327
- conditions = except ? "#{self.class.primary_key} != #{self.class.connection.quote(except.id)}" : {}
328
- acts_as_list_list.in_list.where(
329
- conditions
330
- ).order(
331
- "#{acts_as_list_class.table_name}.#{position_column} DESC"
332
- ).first
333
- end
259
+ # Overwrite this method to define the scope of the list changes
260
+ def scope_condition() {} end
334
261
 
335
- # Forces item to assume the bottom position in the list.
336
- def assume_bottom_position
337
- set_list_position(bottom_position_in_list(self).to_i + 1)
338
- end
262
+ # Returns the bottom position number in the list.
263
+ # bottom_position_in_list # => 2
264
+ def bottom_position_in_list(except = nil)
265
+ item = bottom_item(except)
266
+ item ? item.send(position_column) : acts_as_list_top - 1
267
+ end
339
268
 
340
- # Forces item to assume the top position in the list.
341
- def assume_top_position
342
- set_list_position(acts_as_list_top)
343
- end
269
+ # Returns the bottom item
270
+ def bottom_item(except = nil)
271
+ conditions = except ? "#{quoted_table_name}.#{self.class.primary_key} != #{self.class.connection.quote(except.id)}" : {}
272
+ acts_as_list_list.in_list.where(
273
+ conditions
274
+ ).order(
275
+ "#{quoted_position_column_with_table_name} DESC"
276
+ ).first
277
+ end
344
278
 
345
- # This has the effect of moving all the higher items up one.
346
- def decrement_positions_on_higher_items(position)
347
- acts_as_list_list.where(
348
- "#{position_column} <= #{position}"
349
- ).update_all(
350
- "#{position_column} = (#{position_column} - 1)"
351
- )
352
- end
279
+ # Forces item to assume the bottom position in the list.
280
+ def assume_bottom_position
281
+ set_list_position(bottom_position_in_list(self).to_i + 1)
282
+ end
353
283
 
354
- # This has the effect of moving all the lower items up one.
355
- def decrement_positions_on_lower_items(position=nil)
356
- return unless in_list?
357
- position ||= send(position_column).to_i
358
- acts_as_list_list.where(
359
- "#{position_column} > #{position}"
360
- ).update_all(
361
- "#{position_column} = (#{position_column} - 1)"
362
- )
363
- end
284
+ # Forces item to assume the top position in the list.
285
+ def assume_top_position
286
+ set_list_position(acts_as_list_top)
287
+ end
364
288
 
365
- # This has the effect of moving all the higher items down one.
366
- def increment_positions_on_higher_items
367
- return unless in_list?
368
- acts_as_list_list.where(
369
- "#{position_column} < #{send(position_column).to_i}"
370
- ).update_all(
371
- "#{position_column} = (#{position_column} + 1)"
372
- )
289
+ # This has the effect of moving all the higher items down one.
290
+ def increment_positions_on_higher_items
291
+ return unless in_list?
292
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} < ?", send(position_column).to_i).increment_all
293
+ end
294
+
295
+ # This has the effect of moving all the lower items down one.
296
+ def increment_positions_on_lower_items(position, avoid_id = nil)
297
+ scope = acts_as_list_list
298
+
299
+ if avoid_id
300
+ scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.class.connection.quote(avoid_id))
373
301
  end
374
302
 
375
- # This has the effect of moving all the lower items down one.
376
- def increment_positions_on_lower_items(position, avoid_id = nil)
377
- avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
303
+ scope.where("#{quoted_position_column_with_table_name} >= ?", position).increment_all
304
+ end
378
305
 
379
- acts_as_list_list.where(
380
- "#{position_column} >= #{position}#{avoid_id_condition}"
381
- ).update_all(
382
- "#{position_column} = (#{position_column} + 1)"
383
- )
306
+ # This has the effect of moving all the higher items up one.
307
+ def decrement_positions_on_higher_items(position)
308
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} <= ?", position).decrement_all
309
+ end
310
+
311
+ # This has the effect of moving all the lower items up one.
312
+ def decrement_positions_on_lower_items(position=nil)
313
+ return unless in_list?
314
+ position ||= send(position_column).to_i
315
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} > ?", position).decrement_all
316
+ end
317
+
318
+ # Increments position (<tt>position_column</tt>) of all items in the list.
319
+ def increment_positions_on_all_items
320
+ acts_as_list_list.increment_all
321
+ end
322
+
323
+ # Reorders intermediate items to support moving an item from old_position to new_position.
324
+ # unique constraint prevents regular increment_all and forces to do increments one by one
325
+ # http://stackoverflow.com/questions/7703196/sqlite-increment-unique-integer-field
326
+ # both SQLite and PostgreSQL (and most probably MySQL too) has same issue
327
+ # that's why *sequential_updates?* check alters implementation behavior
328
+ def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil)
329
+ return if old_position == new_position
330
+ scope = acts_as_list_list
331
+
332
+ if avoid_id
333
+ scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.class.connection.quote(avoid_id))
384
334
  end
385
335
 
386
- # Increments position (<tt>position_column</tt>) of all items in the list.
387
- def increment_positions_on_all_items
388
- acts_as_list_list.update_all(
389
- "#{position_column} = (#{position_column} + 1)"
336
+ if old_position < new_position
337
+ # Decrement position of intermediate items
338
+ #
339
+ # e.g., if moving an item from 2 to 5,
340
+ # move [3, 4, 5] to [2, 3, 4]
341
+ items = scope.where(
342
+ "#{quoted_position_column_with_table_name} > ?", old_position
343
+ ).where(
344
+ "#{quoted_position_column_with_table_name} <= ?", new_position
390
345
  )
391
- end
392
346
 
393
- # Reorders intermediate items to support moving an item from old_position to new_position.
394
- def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil)
395
- return if old_position == new_position
396
- avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
397
-
398
- if old_position < new_position
399
- # Decrement position of intermediate items
400
- #
401
- # e.g., if moving an item from 2 to 5,
402
- # move [3, 4, 5] to [2, 3, 4]
403
- acts_as_list_list.where(
404
- "#{position_column} > #{old_position}"
405
- ).where(
406
- "#{position_column} <= #{new_position}#{avoid_id_condition}"
407
- ).update_all(
408
- "#{position_column} = (#{position_column} - 1)"
409
- )
347
+ if sequential_updates?
348
+ items.order("#{quoted_position_column_with_table_name} ASC").each do |item|
349
+ item.decrement!(position_column)
350
+ end
410
351
  else
411
- # Increment position of intermediate items
412
- #
413
- # e.g., if moving an item from 5 to 2,
414
- # move [2, 3, 4] to [3, 4, 5]
415
- acts_as_list_list.where(
416
- "#{position_column} >= #{new_position}"
417
- ).where(
418
- "#{position_column} < #{old_position}#{avoid_id_condition}"
419
- ).update_all(
420
- "#{position_column} = (#{position_column} + 1)"
421
- )
352
+ items.decrement_all
422
353
  end
423
- end
354
+ else
355
+ # Increment position of intermediate items
356
+ #
357
+ # e.g., if moving an item from 5 to 2,
358
+ # move [2, 3, 4] to [3, 4, 5]
359
+ items = scope.where(
360
+ "#{quoted_position_column_with_table_name} >= ?", new_position
361
+ ).where(
362
+ "#{quoted_position_column_with_table_name} < ?", old_position
363
+ )
424
364
 
425
- def insert_at_position(position)
426
- return set_list_position(position) if new_record?
365
+ if sequential_updates?
366
+ items.order("#{quoted_position_column_with_table_name} DESC").each do |item|
367
+ item.increment!(position_column)
368
+ end
369
+ else
370
+ items.increment_all
371
+ end
372
+ end
373
+ end
374
+
375
+ def insert_at_position(position)
376
+ return set_list_position(position) if new_record?
377
+ with_lock do
427
378
  if in_list?
428
379
  old_position = send(position_column).to_i
429
380
  return if position == old_position
430
- shuffle_positions_on_intermediate_items(old_position, position)
381
+ # temporary move after bottom with gap, avoiding duplicate values
382
+ # gap is required to leave room for position increments
383
+ # positive number will be valid with unique not null check (>= 0) db constraint
384
+ temporary_position = acts_as_list_class.maximum(position_column).to_i + 2
385
+ set_list_position(temporary_position)
386
+ shuffle_positions_on_intermediate_items(old_position, position, id)
431
387
  else
432
388
  increment_positions_on_lower_items(position)
433
389
  end
434
390
  set_list_position(position)
435
391
  end
392
+ end
436
393
 
437
- # used by insert_at_position instead of remove_from_list, as postgresql raises error if position_column has non-null constraint
438
- def store_at_0
439
- if in_list?
440
- old_position = send(position_column).to_i
441
- set_list_position(0)
442
- decrement_positions_on_lower_items(old_position)
443
- end
444
- end
394
+ def update_positions
395
+ old_position = send("#{position_column}_was") || bottom_position_in_list + 1
396
+ new_position = send(position_column).to_i
445
397
 
446
- def update_positions
447
- old_position = send("#{position_column}_was").to_i
448
- new_position = send(position_column).to_i
398
+ return unless acts_as_list_list.where(
399
+ "#{quoted_position_column_with_table_name} = #{new_position}"
400
+ ).count > 1
401
+ shuffle_positions_on_intermediate_items old_position, new_position, id
402
+ end
449
403
 
450
- return unless acts_as_list_list.where(
451
- "#{position_column} = #{new_position}"
452
- ).count > 1
453
- shuffle_positions_on_intermediate_items old_position, new_position, id
454
- end
404
+ def internal_scope_changed?
405
+ return @scope_changed if defined?(@scope_changed)
455
406
 
456
- def internal_scope_changed?
457
- return @scope_changed if defined?(@scope_changed)
458
-
459
- @scope_changed = scope_changed?
460
- end
407
+ @scope_changed = scope_changed?
408
+ end
461
409
 
462
- # Temporarily swap changes attributes with current attributes
463
- def swap_changed_attributes
464
- @changed_attributes.each do |k, _|
465
- if self.class.column_names.include? k
466
- @changed_attributes[k], self[k] = self[k], @changed_attributes[k]
467
- end
468
- end
469
- end
410
+ def clear_scope_changed
411
+ remove_instance_variable(:@scope_changed) if defined?(@scope_changed)
412
+ end
470
413
 
471
- def check_scope
472
- if internal_scope_changed?
473
- swap_changed_attributes
474
- send('decrement_positions_on_lower_items') if lower_item
475
- swap_changed_attributes
476
- send("add_to_list_#{add_new_at}")
477
- end
478
- end
414
+ def check_scope
415
+ if internal_scope_changed?
416
+ cached_changes = changes
479
417
 
480
- def reload_position
481
- self.reload
418
+ cached_changes.each { |attribute, values| self[attribute] = values[0] }
419
+ send('decrement_positions_on_lower_items') if lower_item
420
+ cached_changes.each { |attribute, values| self[attribute] = values[1] }
421
+
422
+ send("add_to_list_#{add_new_at}") if add_new_at.present?
482
423
  end
424
+ end
483
425
 
484
- # This check is skipped if the position is currently the default position from the table
485
- # as modifying the default position on creation is handled elsewhere
486
- def check_top_position
487
- if send(position_column) && !default_position? && send(position_column) < acts_as_list_top
488
- self[position_column] = acts_as_list_top
489
- end
426
+ # This check is skipped if the position is currently the default position from the table
427
+ # as modifying the default position on creation is handled elsewhere
428
+ def check_top_position
429
+ if send(position_column) && !default_position? && send(position_column) < acts_as_list_top
430
+ self[position_column] = acts_as_list_top
490
431
  end
432
+ end
433
+
434
+ # When using raw column name it must be quoted otherwise it can raise syntax errors with SQL keywords (e.g. order)
435
+ def quoted_position_column
436
+ @_quoted_position_column ||= self.class.connection.quote_column_name(position_column)
437
+ end
438
+
439
+ # Used in order clauses
440
+ def quoted_table_name
441
+ @_quoted_table_name ||= acts_as_list_class.quoted_table_name
442
+ end
443
+
444
+ def quoted_position_column_with_table_name
445
+ @_quoted_position_column_with_table_name ||= "#{quoted_table_name}.#{quoted_position_column}"
446
+ end
491
447
  end
492
448
  end
493
449
  end