acts_as_list 0.7.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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