acts_as_list 0.7.2 → 0.7.5
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.
- checksums.yaml +5 -13
- data/.gitignore +2 -0
- data/.travis.yml +21 -0
- data/Appraisals +7 -3
- data/CHANGELOG.md +263 -0
- data/Gemfile +4 -1
- data/README.md +12 -3
- data/Rakefile +8 -0
- data/gemfiles/rails_3_2.gemfile +5 -2
- data/gemfiles/rails_4_1.gemfile +5 -2
- data/gemfiles/rails_4_2.gemfile +4 -2
- data/gemfiles/rails_5_0.gemfile +23 -0
- data/lib/acts_as_list/active_record/acts/list.rb +113 -106
- data/lib/acts_as_list/version.rb +1 -1
- data/test/helper.rb +7 -0
- data/test/shared.rb +1 -0
- data/test/shared_list.rb +7 -1
- data/test/shared_quoting.rb +21 -0
- data/test/test_joined_list.rb +64 -0
- data/test/test_list.rb +125 -2
- metadata +17 -14
- data/gemfiles/rails_3_2.gemfile.lock +0 -258
- data/gemfiles/rails_4_1.gemfile.lock +0 -262
- data/gemfiles/rails_4_2.gemfile.lock +0 -53
|
@@ -38,32 +38,30 @@ module ActiveRecord
|
|
|
38
38
|
configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom}
|
|
39
39
|
configuration.update(options) if options.is_a?(Hash)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
|
|
42
|
+
configuration[:scope] = :"#{configuration[:scope]}_id"
|
|
43
|
+
end
|
|
42
44
|
|
|
43
45
|
if configuration[:scope].is_a?(Symbol)
|
|
44
46
|
scope_methods = %(
|
|
45
47
|
def scope_condition
|
|
46
|
-
{
|
|
48
|
+
{ #{configuration[:scope]}: send(:#{configuration[:scope]}) }
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def scope_changed?
|
|
50
|
-
|
|
52
|
+
changed.include?(scope_name.to_s)
|
|
51
53
|
end
|
|
52
54
|
)
|
|
53
55
|
elsif configuration[:scope].is_a?(Array)
|
|
54
56
|
scope_methods = %(
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
def scope_condition
|
|
58
|
+
#{configuration[:scope]}.inject({}) do |hash, column|
|
|
59
|
+
hash.merge!({ column.to_sym => read_attribute(column.to_sym) })
|
|
58
60
|
end
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def scope_changed?
|
|
62
|
-
(
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def scope_condition
|
|
66
|
-
attrs
|
|
64
|
+
(scope_condition.keys & changed.map(&:to_sym)).any?
|
|
67
65
|
end
|
|
68
66
|
)
|
|
69
67
|
else
|
|
@@ -76,9 +74,9 @@ module ActiveRecord
|
|
|
76
74
|
)
|
|
77
75
|
end
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
include ::ActiveRecord::Acts::List::InstanceMethods
|
|
77
|
+
quoted_position_column = connection.quote_column_name(configuration[:column])
|
|
81
78
|
|
|
79
|
+
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
|
82
80
|
def acts_as_list_top
|
|
83
81
|
#{configuration[:top_of_list]}.to_i
|
|
84
82
|
end
|
|
@@ -113,19 +111,46 @@ module ActiveRecord
|
|
|
113
111
|
attr_accessible :#{configuration[:column]}
|
|
114
112
|
end
|
|
115
113
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
scope :in_list, lambda { where(%q{#{quoted_table_name}.#{quoted_position_column} IS NOT NULL}) }
|
|
115
|
+
|
|
116
|
+
def self.decrement_all
|
|
117
|
+
update_all_with_touch %q(#{quoted_position_column} = (#{quoted_table_name}.#{quoted_position_column} - 1))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.increment_all
|
|
121
|
+
update_all_with_touch %q(#{quoted_position_column} = (#{quoted_table_name}.#{quoted_position_column} + 1))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.update_all_with_touch(updates)
|
|
125
|
+
record = new
|
|
126
|
+
attrs = record.send(:timestamp_attributes_for_update_in_model)
|
|
127
|
+
now = record.send(:current_time_from_proper_timezone)
|
|
128
|
+
|
|
129
|
+
query = attrs.map { |attr| %(\#{connection.quote_column_name(attr)} = :now) }
|
|
130
|
+
query.push updates
|
|
131
|
+
query = query.join(", ")
|
|
121
132
|
|
|
122
|
-
|
|
133
|
+
update_all([query, now: now])
|
|
134
|
+
end
|
|
123
135
|
EOV
|
|
124
136
|
|
|
137
|
+
attr_reader :position_changed
|
|
138
|
+
|
|
139
|
+
before_validation :check_top_position
|
|
140
|
+
|
|
141
|
+
before_destroy :lock!
|
|
142
|
+
after_destroy :decrement_positions_on_lower_items
|
|
143
|
+
|
|
144
|
+
before_update :check_scope
|
|
145
|
+
after_update :update_positions
|
|
146
|
+
|
|
147
|
+
after_commit :clear_scope_changed
|
|
148
|
+
|
|
125
149
|
if configuration[:add_new_at].present?
|
|
126
|
-
|
|
150
|
+
before_create "add_to_list_#{configuration[:add_new_at]}".to_sym
|
|
127
151
|
end
|
|
128
152
|
|
|
153
|
+
include ::ActiveRecord::Acts::List::InstanceMethods
|
|
129
154
|
end
|
|
130
155
|
end
|
|
131
156
|
|
|
@@ -221,10 +246,7 @@ module ActiveRecord
|
|
|
221
246
|
# Return the next higher item in the list.
|
|
222
247
|
def higher_item
|
|
223
248
|
return nil unless in_list?
|
|
224
|
-
|
|
225
|
-
acts_as_list_class.where(scope_condition).where("#{position_column} < #{(send(position_column).to_i).to_s}").
|
|
226
|
-
order("#{acts_as_list_class.table_name}.#{position_column} DESC").first
|
|
227
|
-
end
|
|
249
|
+
higher_items(1).first
|
|
228
250
|
end
|
|
229
251
|
|
|
230
252
|
# Return the next n higher items in the list
|
|
@@ -233,19 +255,16 @@ module ActiveRecord
|
|
|
233
255
|
limit ||= acts_as_list_list.count
|
|
234
256
|
position_value = send(position_column)
|
|
235
257
|
acts_as_list_list.
|
|
236
|
-
where("#{
|
|
237
|
-
where("#{
|
|
258
|
+
where("#{quoted_table_name}.#{quoted_position_column} < ?", position_value).
|
|
259
|
+
where("#{quoted_table_name}.#{quoted_position_column} >= ?", position_value - limit).
|
|
238
260
|
limit(limit).
|
|
239
|
-
order("#{
|
|
261
|
+
order("#{quoted_table_name}.#{quoted_position_column} ASC")
|
|
240
262
|
end
|
|
241
263
|
|
|
242
264
|
# Return the next lower item in the list.
|
|
243
265
|
def lower_item
|
|
244
266
|
return nil unless in_list?
|
|
245
|
-
|
|
246
|
-
acts_as_list_class.where(scope_condition).where("#{position_column} > #{(send(position_column).to_i).to_s}").
|
|
247
|
-
order("#{acts_as_list_class.table_name}.#{position_column} ASC").first
|
|
248
|
-
end
|
|
267
|
+
lower_items(1).first
|
|
249
268
|
end
|
|
250
269
|
|
|
251
270
|
# Return the next n lower items in the list
|
|
@@ -254,10 +273,10 @@ module ActiveRecord
|
|
|
254
273
|
limit ||= acts_as_list_list.count
|
|
255
274
|
position_value = send(position_column)
|
|
256
275
|
acts_as_list_list.
|
|
257
|
-
where("#{
|
|
258
|
-
where("#{
|
|
276
|
+
where("#{quoted_table_name}.#{quoted_position_column} > ?", position_value).
|
|
277
|
+
where("#{quoted_table_name}.#{quoted_position_column} <= ?", position_value + limit).
|
|
259
278
|
limit(limit).
|
|
260
|
-
order("#{
|
|
279
|
+
order("#{quoted_table_name}.#{quoted_position_column} ASC")
|
|
261
280
|
end
|
|
262
281
|
|
|
263
282
|
# Test if this record is in a list
|
|
@@ -293,14 +312,26 @@ module ActiveRecord
|
|
|
293
312
|
def add_to_list_top
|
|
294
313
|
increment_positions_on_all_items
|
|
295
314
|
self[position_column] = acts_as_list_top
|
|
315
|
+
# Make sure we know that we've processed this scope change already
|
|
316
|
+
@scope_changed = false
|
|
317
|
+
#dont halt the callback chain
|
|
318
|
+
true
|
|
296
319
|
end
|
|
297
320
|
|
|
321
|
+
# A poorly named method. It will insert the item at the desired position if the position
|
|
322
|
+
# has been set manually using position=, not necessarily the bottom of the list
|
|
298
323
|
def add_to_list_bottom
|
|
299
|
-
if not_in_list? ||
|
|
324
|
+
if not_in_list? || internal_scope_changed? && !position_changed || default_position?
|
|
300
325
|
self[position_column] = bottom_position_in_list.to_i + 1
|
|
301
326
|
else
|
|
302
327
|
increment_positions_on_lower_items(self[position_column], id)
|
|
303
328
|
end
|
|
329
|
+
|
|
330
|
+
# Make sure we know that we've processed this scope change already
|
|
331
|
+
@scope_changed = false
|
|
332
|
+
|
|
333
|
+
#dont halt the callback chain
|
|
334
|
+
true
|
|
304
335
|
end
|
|
305
336
|
|
|
306
337
|
# Overwrite this method to define the scope of the list changes
|
|
@@ -315,11 +346,12 @@ module ActiveRecord
|
|
|
315
346
|
|
|
316
347
|
# Returns the bottom item
|
|
317
348
|
def bottom_item(except = nil)
|
|
318
|
-
conditions = scope_condition
|
|
319
349
|
conditions = except ? "#{self.class.primary_key} != #{self.class.connection.quote(except.id)}" : {}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
350
|
+
acts_as_list_list.in_list.where(
|
|
351
|
+
conditions
|
|
352
|
+
).order(
|
|
353
|
+
"#{quoted_table_name}.#{quoted_position_column} DESC"
|
|
354
|
+
).first
|
|
323
355
|
end
|
|
324
356
|
|
|
325
357
|
# Forces item to assume the bottom position in the list.
|
|
@@ -334,62 +366,32 @@ module ActiveRecord
|
|
|
334
366
|
|
|
335
367
|
# This has the effect of moving all the higher items up one.
|
|
336
368
|
def decrement_positions_on_higher_items(position)
|
|
337
|
-
|
|
338
|
-
acts_as_list_class.where(scope_condition).where(
|
|
339
|
-
"#{position_column} <= #{position}"
|
|
340
|
-
).update_all(
|
|
341
|
-
"#{position_column} = (#{position_column} - 1)"
|
|
342
|
-
)
|
|
343
|
-
end
|
|
369
|
+
acts_as_list_list.where("#{quoted_position_column} <= #{position}").decrement_all
|
|
344
370
|
end
|
|
345
371
|
|
|
346
372
|
# This has the effect of moving all the lower items up one.
|
|
347
373
|
def decrement_positions_on_lower_items(position=nil)
|
|
348
374
|
return unless in_list?
|
|
349
375
|
position ||= send(position_column).to_i
|
|
350
|
-
|
|
351
|
-
acts_as_list_class.where(scope_condition).where(
|
|
352
|
-
"#{position_column} > #{position}"
|
|
353
|
-
).update_all(
|
|
354
|
-
"#{position_column} = (#{position_column} - 1)"
|
|
355
|
-
)
|
|
356
|
-
end
|
|
376
|
+
acts_as_list_list.where("#{quoted_position_column} > #{position}").decrement_all
|
|
357
377
|
end
|
|
358
378
|
|
|
359
379
|
# This has the effect of moving all the higher items down one.
|
|
360
380
|
def increment_positions_on_higher_items
|
|
361
381
|
return unless in_list?
|
|
362
|
-
|
|
363
|
-
acts_as_list_class.where(scope_condition).where(
|
|
364
|
-
"#{position_column} < #{send(position_column).to_i}"
|
|
365
|
-
).update_all(
|
|
366
|
-
"#{position_column} = (#{position_column} + 1)"
|
|
367
|
-
)
|
|
368
|
-
end
|
|
382
|
+
acts_as_list_list.where("#{quoted_position_column} < #{send(position_column).to_i}").increment_all
|
|
369
383
|
end
|
|
370
384
|
|
|
371
385
|
# This has the effect of moving all the lower items down one.
|
|
372
386
|
def increment_positions_on_lower_items(position, avoid_id = nil)
|
|
373
387
|
avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
|
|
374
388
|
|
|
375
|
-
|
|
376
|
-
acts_as_list_class.where(scope_condition).where(
|
|
377
|
-
"#{position_column} >= #{position}#{avoid_id_condition}"
|
|
378
|
-
).update_all(
|
|
379
|
-
"#{position_column} = (#{position_column} + 1)"
|
|
380
|
-
)
|
|
381
|
-
end
|
|
389
|
+
acts_as_list_list.where("#{quoted_position_column} >= #{position}#{avoid_id_condition}").increment_all
|
|
382
390
|
end
|
|
383
391
|
|
|
384
392
|
# Increments position (<tt>position_column</tt>) of all items in the list.
|
|
385
393
|
def increment_positions_on_all_items
|
|
386
|
-
|
|
387
|
-
acts_as_list_class.where(
|
|
388
|
-
scope_condition
|
|
389
|
-
).update_all(
|
|
390
|
-
"#{position_column} = (#{position_column} + 1)"
|
|
391
|
-
)
|
|
392
|
-
end
|
|
394
|
+
acts_as_list_list.increment_all
|
|
393
395
|
end
|
|
394
396
|
|
|
395
397
|
# Reorders intermediate items to support moving an item from old_position to new_position.
|
|
@@ -402,29 +404,21 @@ module ActiveRecord
|
|
|
402
404
|
#
|
|
403
405
|
# e.g., if moving an item from 2 to 5,
|
|
404
406
|
# move [3, 4, 5] to [2, 3, 4]
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
).update_all(
|
|
411
|
-
"#{position_column} = (#{position_column} - 1)"
|
|
412
|
-
)
|
|
413
|
-
end
|
|
407
|
+
acts_as_list_list.where(
|
|
408
|
+
"#{quoted_position_column} > #{old_position}"
|
|
409
|
+
).where(
|
|
410
|
+
"#{quoted_position_column} <= #{new_position}#{avoid_id_condition}"
|
|
411
|
+
).decrement_all
|
|
414
412
|
else
|
|
415
413
|
# Increment position of intermediate items
|
|
416
414
|
#
|
|
417
415
|
# e.g., if moving an item from 5 to 2,
|
|
418
416
|
# move [2, 3, 4] to [3, 4, 5]
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
).update_all(
|
|
425
|
-
"#{position_column} = (#{position_column} + 1)"
|
|
426
|
-
)
|
|
427
|
-
end
|
|
417
|
+
acts_as_list_list.where(
|
|
418
|
+
"#{quoted_position_column} >= #{new_position}"
|
|
419
|
+
).where(
|
|
420
|
+
"#{quoted_position_column} < #{old_position}#{avoid_id_condition}"
|
|
421
|
+
).increment_all
|
|
428
422
|
end
|
|
429
423
|
end
|
|
430
424
|
|
|
@@ -453,31 +447,34 @@ module ActiveRecord
|
|
|
453
447
|
old_position = send("#{position_column}_was").to_i
|
|
454
448
|
new_position = send(position_column).to_i
|
|
455
449
|
|
|
456
|
-
return unless
|
|
457
|
-
|
|
458
|
-
|
|
450
|
+
return unless acts_as_list_list.where(
|
|
451
|
+
"#{quoted_position_column} = #{new_position}"
|
|
452
|
+
).count > 1
|
|
459
453
|
shuffle_positions_on_intermediate_items old_position, new_position, id
|
|
460
454
|
end
|
|
461
455
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
456
|
+
def internal_scope_changed?
|
|
457
|
+
return @scope_changed if defined?(@scope_changed)
|
|
458
|
+
|
|
459
|
+
@scope_changed = scope_changed?
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def clear_scope_changed
|
|
463
|
+
remove_instance_variable(:@scope_changed) if defined?(@scope_changed)
|
|
466
464
|
end
|
|
467
465
|
|
|
468
466
|
def check_scope
|
|
469
|
-
if
|
|
470
|
-
|
|
467
|
+
if internal_scope_changed?
|
|
468
|
+
cached_changes = changes
|
|
469
|
+
|
|
470
|
+
cached_changes.each { |attribute, values| self[attribute] = values[0] }
|
|
471
471
|
send('decrement_positions_on_lower_items') if lower_item
|
|
472
|
-
|
|
472
|
+
cached_changes.each { |attribute, values| self[attribute] = values[1] }
|
|
473
|
+
|
|
473
474
|
send("add_to_list_#{add_new_at}")
|
|
474
475
|
end
|
|
475
476
|
end
|
|
476
477
|
|
|
477
|
-
def reload_position
|
|
478
|
-
self.reload
|
|
479
|
-
end
|
|
480
|
-
|
|
481
478
|
# This check is skipped if the position is currently the default position from the table
|
|
482
479
|
# as modifying the default position on creation is handled elsewhere
|
|
483
480
|
def check_top_position
|
|
@@ -485,6 +482,16 @@ module ActiveRecord
|
|
|
485
482
|
self[position_column] = acts_as_list_top
|
|
486
483
|
end
|
|
487
484
|
end
|
|
485
|
+
|
|
486
|
+
# When using raw column name it must be quoted otherwise it can raise syntax errors with SQL keywords (e.g. order)
|
|
487
|
+
def quoted_position_column
|
|
488
|
+
@_quoted_position_column ||= self.class.connection.quote_column_name(position_column)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Used in order clauses
|
|
492
|
+
def quoted_table_name
|
|
493
|
+
@_quoted_table_name ||= acts_as_list_class.quoted_table_name
|
|
494
|
+
end
|
|
488
495
|
end
|
|
489
496
|
end
|
|
490
497
|
end
|
data/lib/acts_as_list/version.rb
CHANGED
data/test/helper.rb
CHANGED
|
@@ -11,4 +11,11 @@ require "active_record"
|
|
|
11
11
|
require "minitest/autorun"
|
|
12
12
|
require "#{File.dirname(__FILE__)}/../init"
|
|
13
13
|
|
|
14
|
+
if defined?(ActiveRecord::VERSION) &&
|
|
15
|
+
ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2
|
|
16
|
+
|
|
17
|
+
# Was removed in Rails 5 and is effectively true.
|
|
18
|
+
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
|
19
|
+
end
|
|
20
|
+
|
|
14
21
|
require "shared"
|
data/test/shared.rb
CHANGED
data/test/shared_list.rb
CHANGED
|
@@ -142,7 +142,7 @@ module Shared
|
|
|
142
142
|
|
|
143
143
|
def test_update_position_when_scope_changes
|
|
144
144
|
assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
|
|
145
|
-
|
|
145
|
+
ListMixin.create(parent_id: 6)
|
|
146
146
|
|
|
147
147
|
ListMixin.where(id: 2).first.move_within_scope(6)
|
|
148
148
|
|
|
@@ -246,5 +246,11 @@ module Shared
|
|
|
246
246
|
|
|
247
247
|
assert_equal [5, 1, 6, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
|
|
248
248
|
end
|
|
249
|
+
|
|
250
|
+
def test_non_persisted_records_dont_get_lock_called
|
|
251
|
+
new = ListMixin.new(parent_id: 5)
|
|
252
|
+
|
|
253
|
+
new.destroy
|
|
254
|
+
end
|
|
249
255
|
end
|
|
250
256
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Shared
|
|
2
|
+
module Quoting
|
|
3
|
+
|
|
4
|
+
def setup
|
|
5
|
+
3.times { |counter| QuotedList.create! order: counter }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_create
|
|
9
|
+
assert_equal QuotedList.in_list.size, 3
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# This test execute raw queries involving table name
|
|
13
|
+
def test_moving
|
|
14
|
+
item = QuotedList.first
|
|
15
|
+
item.higher_items
|
|
16
|
+
item.lower_items
|
|
17
|
+
item.send :bottom_item # Part of private api
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
|
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
|
4
|
+
ActiveRecord::Schema.verbose = false
|
|
5
|
+
|
|
6
|
+
class Section < ActiveRecord::Base
|
|
7
|
+
has_many :items
|
|
8
|
+
acts_as_list
|
|
9
|
+
|
|
10
|
+
scope :visible, -> { where(visible: true) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Item < ActiveRecord::Base
|
|
14
|
+
belongs_to :section
|
|
15
|
+
acts_as_list scope: :section
|
|
16
|
+
|
|
17
|
+
scope :visible, -> { where(visible: true).joins(:section).merge(Section.visible) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class JoinedTestCase < Minitest::Test
|
|
21
|
+
def setup
|
|
22
|
+
ActiveRecord::Base.connection.create_table :sections do |t|
|
|
23
|
+
t.column :position, :integer
|
|
24
|
+
t.column :visible, :boolean, default: true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
ActiveRecord::Base.connection.create_table :items do |t|
|
|
28
|
+
t.column :position, :integer
|
|
29
|
+
t.column :section_id, :integer
|
|
30
|
+
t.column :visible, :boolean, default: true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
ActiveRecord::Base.connection.schema_cache.clear!
|
|
34
|
+
[Section, Item].each(&:reset_column_information)
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def teardown
|
|
39
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
|
40
|
+
ActiveRecord::Base.connection.drop_table(table)
|
|
41
|
+
end
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# joining the relation returned by `#higher_items` or `#lower_items` to another table
|
|
47
|
+
# previously could result in ambiguous column names in the query
|
|
48
|
+
class TestHigherLowerItems < JoinedTestCase
|
|
49
|
+
def test_higher_items
|
|
50
|
+
section = Section.create
|
|
51
|
+
item1 = Item.create section: section
|
|
52
|
+
item2 = Item.create section: section
|
|
53
|
+
item3 = Item.create section: section
|
|
54
|
+
assert_equal item3.higher_items.visible, [item1, item2]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_lower_items
|
|
58
|
+
section = Section.create
|
|
59
|
+
item1 = Item.create section: section
|
|
60
|
+
item2 = Item.create section: section
|
|
61
|
+
item3 = Item.create section: section
|
|
62
|
+
assert_equal item1.lower_items.visible, [item2, item3]
|
|
63
|
+
end
|
|
64
|
+
end
|
data/test/test_list.rb
CHANGED
|
@@ -16,9 +16,14 @@ def setup_db(position_options = {})
|
|
|
16
16
|
t.column :state, :integer
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# This table is used to test table names and column names quoting
|
|
20
|
+
ActiveRecord::Base.connection.create_table 'table-name' do |t|
|
|
21
|
+
t.column :order, :integer
|
|
22
|
+
end
|
|
23
|
+
|
|
19
24
|
mixins = [ Mixin, ListMixin, ListMixinSub1, ListMixinSub2, ListWithStringScopeMixin,
|
|
20
25
|
ArrayScopeListMixin, ZeroBasedMixin, DefaultScopedMixin,
|
|
21
|
-
DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin ]
|
|
26
|
+
DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin, QuotedList ]
|
|
22
27
|
|
|
23
28
|
mixins << EnumArrayScopeListMixin if rails_4
|
|
24
29
|
|
|
@@ -42,7 +47,13 @@ def rails_4
|
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def teardown_db
|
|
45
|
-
ActiveRecord::
|
|
50
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
|
51
|
+
tables = ActiveRecord::Base.connection.data_sources
|
|
52
|
+
else
|
|
53
|
+
tables = ActiveRecord::Base.connection.tables
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
tables.each do |table|
|
|
46
57
|
ActiveRecord::Base.connection.drop_table(table)
|
|
47
58
|
end
|
|
48
59
|
end
|
|
@@ -109,6 +120,29 @@ class NoAdditionMixin < Mixin
|
|
|
109
120
|
acts_as_list column: "pos", add_new_at: nil, scope: :parent_id
|
|
110
121
|
end
|
|
111
122
|
|
|
123
|
+
##
|
|
124
|
+
# The way we track changes to
|
|
125
|
+
# scope and position can get tripped up
|
|
126
|
+
# by someone using update_attributes within
|
|
127
|
+
# a callback because it causes multiple passes
|
|
128
|
+
# through the callback chain
|
|
129
|
+
module CallbackMixin
|
|
130
|
+
|
|
131
|
+
def self.included(base)
|
|
132
|
+
base.send :include, InstanceMethods
|
|
133
|
+
base.after_create :change_field
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
module InstanceMethods
|
|
137
|
+
def change_field
|
|
138
|
+
# doesn't matter what column changes, just
|
|
139
|
+
# need to change something
|
|
140
|
+
|
|
141
|
+
self.update_attributes(active: !self.active)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
112
146
|
class TheAbstractClass < ActiveRecord::Base
|
|
113
147
|
self.abstract_class = true
|
|
114
148
|
self.table_name = 'mixins'
|
|
@@ -126,6 +160,11 @@ end
|
|
|
126
160
|
class TheBaseSubclass < TheBaseClass
|
|
127
161
|
end
|
|
128
162
|
|
|
163
|
+
class QuotedList < ActiveRecord::Base
|
|
164
|
+
self.table_name = 'table-name'
|
|
165
|
+
acts_as_list column: :order
|
|
166
|
+
end
|
|
167
|
+
|
|
129
168
|
class ActsAsListTestCase < Minitest::Test
|
|
130
169
|
# No default test required as this class is abstract.
|
|
131
170
|
# Need for test/unit.
|
|
@@ -163,6 +202,18 @@ class ListTest < ActsAsListTestCase
|
|
|
163
202
|
end
|
|
164
203
|
end
|
|
165
204
|
|
|
205
|
+
class ListWithCallbackTest < ActsAsListTestCase
|
|
206
|
+
|
|
207
|
+
include Shared::List
|
|
208
|
+
|
|
209
|
+
def setup
|
|
210
|
+
ListMixin.send(:include, CallbackMixin)
|
|
211
|
+
setup_db
|
|
212
|
+
super
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
|
|
166
217
|
class ListTestWithDefault < ActsAsListTestCase
|
|
167
218
|
include Shared::List
|
|
168
219
|
|
|
@@ -208,6 +259,15 @@ class ArrayScopeListTestWithDefault < ActsAsListTestCase
|
|
|
208
259
|
end
|
|
209
260
|
end
|
|
210
261
|
|
|
262
|
+
class QuotingTestList < ActsAsListTestCase
|
|
263
|
+
include Shared::Quoting
|
|
264
|
+
|
|
265
|
+
def setup
|
|
266
|
+
setup_db_with_default
|
|
267
|
+
super
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
211
271
|
class DefaultScopedTest < ActsAsListTestCase
|
|
212
272
|
def setup
|
|
213
273
|
setup_db
|
|
@@ -568,3 +628,66 @@ class MultipleListsArrayScopeTest < ActsAsListTestCase
|
|
|
568
628
|
assert_equal [1], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').order(:pos).map(&:pos)
|
|
569
629
|
end
|
|
570
630
|
end
|
|
631
|
+
|
|
632
|
+
class TouchTest < ActsAsListTestCase
|
|
633
|
+
def setup
|
|
634
|
+
setup_db
|
|
635
|
+
4.times { ListMixin.create! updated_at: yesterday }
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def now
|
|
639
|
+
Time.now.utc
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def yesterday
|
|
643
|
+
1.day.ago
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def updated_ats
|
|
647
|
+
ListMixin.pluck(:updated_at)
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
def test_moving_item_lower_touches_self_and_lower_item
|
|
651
|
+
ListMixin.first.move_lower
|
|
652
|
+
updated_ats[0..1].each do |updated_at|
|
|
653
|
+
assert_in_delta updated_at, now, 1.second
|
|
654
|
+
end
|
|
655
|
+
updated_ats[2..3].each do |updated_at|
|
|
656
|
+
assert_in_delta updated_at, yesterday, 1.second
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def test_moving_item_higher_touches_self_and_higher_item
|
|
661
|
+
ListMixin.all.second.move_higher
|
|
662
|
+
updated_ats[0..1].each do |updated_at|
|
|
663
|
+
assert_in_delta updated_at, now, 1.second
|
|
664
|
+
end
|
|
665
|
+
updated_ats[2..3].each do |updated_at|
|
|
666
|
+
assert_in_delta updated_at, yesterday, 1.second
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def test_moving_item_to_bottom_touches_all_other_items
|
|
671
|
+
ListMixin.first.move_to_bottom
|
|
672
|
+
updated_ats.each do |updated_at|
|
|
673
|
+
assert_in_delta updated_at, now, 1.second
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def test_moving_item_to_top_touches_all_other_items
|
|
678
|
+
ListMixin.last.move_to_top
|
|
679
|
+
updated_ats.each do |updated_at|
|
|
680
|
+
assert_in_delta updated_at, now, 1.second
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def test_removing_item_touches_all_lower_items
|
|
685
|
+
ListMixin.all.third.remove_from_list
|
|
686
|
+
updated_ats[0..1].each do |updated_at|
|
|
687
|
+
assert_in_delta updated_at, yesterday, 1.second
|
|
688
|
+
end
|
|
689
|
+
updated_ats[2..2].each do |updated_at|
|
|
690
|
+
assert_in_delta updated_at, now, 1.second
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
end
|