acts_as_list 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/test/test_list.rb DELETED
@@ -1,1253 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # NOTE: following now done in helper.rb (better Readability)
4
- require 'helper'
5
-
6
- def setup_db(position_options = {})
7
- $default_position = position_options[:default]
8
-
9
- # sqlite cannot drop/rename/alter columns and add constraints after table creation
10
- sqlite = ENV.fetch("DB") == "sqlite"
11
- unique = position_options.delete(:unique)
12
- positive = position_options.delete(:positive)
13
-
14
- # AR caches columns options like defaults etc. Clear them!
15
- ActiveRecord::Base.connection.create_table :mixins do |t|
16
- t.column :pos, :integer, **position_options unless positive && sqlite
17
- t.column :active, :boolean, default: true
18
- t.column :parent_id, :integer
19
- t.column :parent_type, :string
20
- t.column :created_at, :datetime
21
- t.column :updated_at, :datetime
22
- t.column :state, :integer
23
- end
24
-
25
- if unique && !(sqlite && positive)
26
- ActiveRecord::Base.connection.add_index :mixins, :pos, unique: true
27
- end
28
-
29
- if positive
30
- if sqlite
31
- # SQLite cannot add constraint after table creation, also cannot add unique inside ADD COLUMN
32
- ActiveRecord::Base.connection.execute('ALTER TABLE mixins ADD COLUMN pos integer8 NOT NULL CHECK (pos > 0) DEFAULT 1')
33
- ActiveRecord::Base.connection.execute('CREATE UNIQUE INDEX index_mixins_on_pos ON mixins(pos)')
34
- else
35
- ActiveRecord::Base.connection.execute('ALTER TABLE mixins ADD CONSTRAINT pos_check CHECK (pos > 0)')
36
- end
37
- end
38
-
39
- # This table is used to test table names and column names quoting
40
- ActiveRecord::Base.connection.create_table 'table-name' do |t|
41
- t.column :order, :integer
42
- end
43
-
44
- # This table is used to test table names with different primary_key columns
45
- ActiveRecord::Base.connection.create_table 'altid-table', primary_key: 'altid' do |t|
46
- t.column :pos, :integer
47
- t.column :created_at, :datetime
48
- t.column :updated_at, :datetime
49
- end
50
-
51
- ActiveRecord::Base.connection.add_index 'altid-table', :pos, unique: true
52
-
53
- # This table is used to test table names with a composite primary_key
54
- ActiveRecord::Base.connection.create_table 'composite-primary-key-table', primary_key: [:first_id, :second_id] do |t|
55
- t.integer :first_id, null: false
56
- t.integer :second_id, null: false
57
- t.column :parent_id, :integer
58
- t.column :parent_type, :string
59
- t.column :pos, :integer
60
- t.column :created_at, :datetime
61
- t.column :updated_at, :datetime
62
- t.column :state, :integer
63
- end
64
-
65
- mixins = [ Mixin, ListMixin, ListMixinSub1, ListMixinSub2, ListWithStringScopeMixin,
66
- ArrayScopeListMixin, ZeroBasedMixin, DefaultScopedMixin, EnumArrayScopeListMixin,
67
- DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin, QuotedList, TouchDisabledMixin, CompositePrimaryKeyList, CompositePrimaryKeyListScoped ]
68
-
69
- ActiveRecord::Base.connection.schema_cache.clear!
70
- mixins.each do |klass|
71
- klass.reset_column_information
72
- end
73
- end
74
-
75
- def setup_db_with_default
76
- setup_db default: 0
77
- end
78
-
79
- class Mixin < ActiveRecord::Base
80
- self.table_name = 'mixins'
81
- end
82
-
83
- class ListMixin < Mixin
84
- acts_as_list column: "pos", scope: :parent
85
- end
86
-
87
- class TouchDisabledMixin < Mixin
88
- acts_as_list column: "pos", touch_on_update: false
89
- end
90
-
91
- class ListMixinSub1 < ListMixin
92
- end
93
-
94
- class ListMixinSub2 < ListMixin
95
- validates :pos, presence: true
96
- end
97
-
98
- class ListMixinError < ListMixin
99
- validates :state, presence: true
100
- end
101
-
102
- class ListWithStringScopeMixin < Mixin
103
- acts_as_list column: "pos", scope: 'parent_id = #{parent_id}'
104
- end
105
-
106
- class ArrayScopeListMixin < Mixin
107
- acts_as_list column: "pos", scope: [:parent_id, :parent_type]
108
- end
109
-
110
- class ArrayScopeListWithHashMixin < Mixin
111
- acts_as_list column: "pos", scope: [:parent_id, state: nil]
112
- end
113
-
114
- class EnumArrayScopeListMixin < Mixin
115
- STATE_VALUES = %w(active archived)
116
- enum state: STATE_VALUES
117
-
118
- acts_as_list column: "pos", scope: [:parent_id, :state]
119
- end
120
-
121
- class ZeroBasedMixin < Mixin
122
- acts_as_list column: "pos", top_of_list: 0, scope: [:parent_id]
123
- end
124
-
125
- class DefaultScopedMixin < Mixin
126
- acts_as_list column: "pos"
127
- default_scope { order('pos ASC') }
128
- end
129
-
130
- class DefaultScopedWhereMixin < Mixin
131
- acts_as_list column: "pos"
132
- default_scope { order('pos ASC').where(active: true) }
133
-
134
- def self.for_active_false_tests
135
- unscope(:where).where(active: false)
136
- end
137
- end
138
-
139
- class SequentialUpdatesDefault < Mixin
140
- acts_as_list column: "pos"
141
- end
142
-
143
- class SequentialUpdatesAltId < ActiveRecord::Base
144
- self.table_name = "altid-table"
145
- self.primary_key = "altid"
146
-
147
- acts_as_list column: "pos"
148
- end
149
-
150
- class SequentialUpdatesAltIdTouchDisabled < SequentialUpdatesAltId
151
- acts_as_list column: "pos", touch_on_update: false
152
- end
153
-
154
- class SequentialUpdatesFalseMixin < Mixin
155
- acts_as_list column: "pos", sequential_updates: false
156
- end
157
-
158
- class TopAdditionMixin < Mixin
159
- acts_as_list column: "pos", add_new_at: :top, scope: :parent_id
160
- end
161
-
162
- class NoAdditionMixin < Mixin
163
- acts_as_list column: "pos", add_new_at: nil, scope: :parent_id
164
- end
165
-
166
- ##
167
- # The way we track changes to
168
- # scope and position can get tripped up
169
- # by someone using update within
170
- # a callback because it causes multiple passes
171
- # through the callback chain
172
- module CallbackMixin
173
-
174
- def self.included(base)
175
- base.send :include, InstanceMethods
176
- base.after_create :change_field
177
- end
178
-
179
- module InstanceMethods
180
- def change_field
181
- # doesn't matter what column changes, just
182
- # need to change something
183
-
184
- self.update active: !self.active
185
- end
186
- end
187
- end
188
-
189
- class TheAbstractClass < ActiveRecord::Base
190
- self.abstract_class = true
191
- self.table_name = 'mixins'
192
- end
193
-
194
- class TheAbstractSubclass < TheAbstractClass
195
- acts_as_list column: "pos", scope: :parent
196
- end
197
-
198
- class TheBaseClass < ActiveRecord::Base
199
- self.table_name = 'mixins'
200
- acts_as_list column: "pos", scope: :parent
201
- end
202
-
203
- class TheBaseSubclass < TheBaseClass
204
- end
205
-
206
- class QuotedList < ActiveRecord::Base
207
- self.table_name = 'table-name'
208
- acts_as_list column: :order
209
- end
210
-
211
- class CompositePrimaryKeyList < ActiveRecord::Base
212
- self.table_name = "composite-primary-key-table"
213
- self.primary_key = [:first_id, :second_id]
214
-
215
- acts_as_list column: "pos"
216
- end
217
-
218
- class CompositePrimaryKeyListScoped < CompositePrimaryKeyList
219
- acts_as_list column: "pos", scope: :parent_id
220
- end
221
-
222
- class CompositePrimaryKeyListScopedError < CompositePrimaryKeyListScoped
223
- validates :state, presence: true
224
- end
225
-
226
- class ActsAsListTestCase < Minitest::Test
227
- # No default test required as this class is abstract.
228
- # Need for test/unit.
229
- undef_method :default_test if method_defined?(:default_test)
230
-
231
- def teardown
232
- teardown_db
233
- end
234
- end
235
-
236
- class ZeroBasedTest < ActsAsListTestCase
237
- include Shared::ZeroBased
238
-
239
- def setup
240
- setup_db
241
- super
242
- end
243
- end
244
-
245
- class ZeroBasedTestWithDefault < ActsAsListTestCase
246
- include Shared::ZeroBased
247
-
248
- def setup
249
- setup_db_with_default
250
- super
251
- end
252
- end
253
-
254
- class ListTest < ActsAsListTestCase
255
- include Shared::List
256
-
257
- def setup
258
- setup_db
259
- super
260
- end
261
-
262
- def test_insert_race_condition
263
- # the bigger n is the more likely we will have a race condition
264
- n = 1000
265
- (1..n).each do |counter|
266
- node = ListMixin.new parent_id: 1
267
- node.pos = counter
268
- node.save!
269
- end
270
-
271
- wait_for_it = true
272
- threads = []
273
- 4.times do |i|
274
- threads << Thread.new do
275
- true while wait_for_it
276
- ActiveRecord::Base.connection_pool.with_connection do |c|
277
- n.times do
278
- begin
279
- ListMixin.where(parent_id: 1).order('pos').last.insert_at(1)
280
- rescue Exception
281
- # ignore SQLite3::SQLException due to table locking
282
- end
283
- end
284
- end
285
- end
286
- end
287
- wait_for_it = false
288
- threads.each(&:join)
289
-
290
- assert_equal((1..n).to_a, ListMixin.where(parent_id: 1).order('pos').map(&:pos))
291
- end
292
- end
293
-
294
- class ListWithCallbackTest < ActsAsListTestCase
295
-
296
- include Shared::List
297
-
298
- def setup
299
- ListMixin.send(:include, CallbackMixin)
300
- setup_db
301
- super
302
- end
303
-
304
- end
305
-
306
- class ListTestWithDefault < ActsAsListTestCase
307
- include Shared::List
308
-
309
- def setup
310
- setup_db_with_default
311
- super
312
- end
313
- end
314
-
315
- class ListSubTest < ActsAsListTestCase
316
- include Shared::ListSub
317
-
318
- def setup
319
- setup_db
320
- super
321
- end
322
- end
323
-
324
- class ListSubTestWithDefault < ActsAsListTestCase
325
- include Shared::ListSub
326
-
327
- def setup
328
- setup_db_with_default
329
- super
330
- end
331
- end
332
-
333
- class ArrayScopeListTest < ActsAsListTestCase
334
- include Shared::ArrayScopeList
335
-
336
- def setup
337
- setup_db
338
- super
339
- end
340
- end
341
-
342
- class ArrayScopeListTestWithDefault < ActsAsListTestCase
343
- include Shared::ArrayScopeList
344
-
345
- def setup
346
- setup_db_with_default
347
- super
348
- end
349
- end
350
-
351
- class QuotingTestList < ActsAsListTestCase
352
- include Shared::Quoting
353
-
354
- def setup
355
- setup_db_with_default
356
- super
357
- end
358
- end
359
-
360
- class DefaultScopedTest < ActsAsListTestCase
361
- def setup
362
- setup_db
363
- (1..4).each { |counter| DefaultScopedMixin.create!({pos: counter}) }
364
- end
365
-
366
- def test_insert
367
- new = DefaultScopedMixin.create
368
- assert_equal 5, new.pos
369
- assert !new.first?
370
- assert new.last?
371
-
372
- new = DefaultScopedMixin.create
373
- assert_equal 6, new.pos
374
- assert !new.first?
375
- assert new.last?
376
-
377
- new = DefaultScopedMixin.acts_as_list_no_update { DefaultScopedMixin.create }
378
- assert_equal_or_nil $default_position, new.pos
379
- assert_equal $default_position.is_a?(Integer), new.first?
380
- assert !new.last?
381
-
382
- new = DefaultScopedMixin.create
383
- assert_equal 7, new.pos
384
- assert !new.first?
385
- assert new.last?
386
- end
387
-
388
- def test_reordering
389
- assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id)
390
-
391
- DefaultScopedMixin.where(id: 2).first.move_lower
392
- assert_equal [1, 3, 2, 4], DefaultScopedMixin.all.map(&:id)
393
-
394
- DefaultScopedMixin.where(id: 2).first.move_higher
395
- assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id)
396
-
397
- DefaultScopedMixin.where(id: 1).first.move_to_bottom
398
- assert_equal [2, 3, 4, 1], DefaultScopedMixin.all.map(&:id)
399
-
400
- DefaultScopedMixin.where(id: 1).first.move_to_top
401
- assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id)
402
-
403
- DefaultScopedMixin.where(id: 2).first.move_to_bottom
404
- assert_equal [1, 3, 4, 2], DefaultScopedMixin.all.map(&:id)
405
-
406
- DefaultScopedMixin.where(id: 4).first.move_to_top
407
- assert_equal [4, 1, 3, 2], DefaultScopedMixin.all.map(&:id)
408
- end
409
-
410
- def test_insert_at
411
- new = DefaultScopedMixin.create
412
- assert_equal 5, new.pos
413
-
414
- new = DefaultScopedMixin.create
415
- assert_equal 6, new.pos
416
-
417
- new_noup = DefaultScopedMixin.acts_as_list_no_update { DefaultScopedMixin.create }
418
- assert_equal_or_nil $default_position, new_noup.pos
419
-
420
- new = DefaultScopedMixin.create
421
- assert_equal 7, new.pos
422
-
423
- new4 = DefaultScopedMixin.create
424
- assert_equal 8, new4.pos
425
-
426
- new4.insert_at(2)
427
- assert_equal 2, new4.pos
428
-
429
- new.reload
430
- assert_equal 8, new.pos
431
-
432
- new.insert_at(2)
433
- assert_equal 2, new.pos
434
-
435
- new4.reload
436
- assert_equal 3, new4.pos
437
-
438
- new5 = DefaultScopedMixin.create
439
- assert_equal 9, new5.pos
440
-
441
- new5.insert_at(1)
442
- assert_equal 1, new5.pos
443
-
444
- new4.reload
445
- assert_equal 4, new4.pos
446
-
447
- new_noup.reload
448
- assert_equal_or_nil $default_position, new_noup.pos
449
- end
450
-
451
- def test_find_or_create_doesnt_raise_deprecation_warning
452
- assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do
453
- DefaultScopedMixin.find_or_create_by(pos: 5)
454
- end
455
- end
456
-
457
- def test_update_position
458
- assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id)
459
- DefaultScopedMixin.where(id: 2).first.set_list_position(4)
460
- assert_equal [1, 3, 4, 2], DefaultScopedMixin.all.map(&:id)
461
- DefaultScopedMixin.where(id: 2).first.set_list_position(2)
462
- assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id)
463
- DefaultScopedMixin.where(id: 1).first.set_list_position(4)
464
- assert_equal [2, 3, 4, 1], DefaultScopedMixin.all.map(&:id)
465
- DefaultScopedMixin.where(id: 1).first.set_list_position(1)
466
- assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id)
467
- end
468
- end
469
-
470
- class DefaultScopedWhereTest < ActsAsListTestCase
471
- def setup
472
- setup_db
473
- (1..4).each { |counter| DefaultScopedWhereMixin.create! pos: counter, active: false }
474
- end
475
-
476
- def test_insert
477
- new = DefaultScopedWhereMixin.create
478
- assert_equal 5, new.pos
479
- assert !new.first?
480
- assert new.last?
481
-
482
- new = DefaultScopedWhereMixin.create
483
- assert_equal 6, new.pos
484
- assert !new.first?
485
- assert new.last?
486
-
487
- new = DefaultScopedWhereMixin.acts_as_list_no_update { DefaultScopedWhereMixin.create }
488
- assert_equal_or_nil $default_position, new.pos
489
- assert_equal $default_position.is_a?(Integer), new.first?
490
- assert !new.last?
491
-
492
- new = DefaultScopedWhereMixin.create
493
- assert_equal 7, new.pos
494
- assert !new.first?
495
- assert new.last?
496
- end
497
-
498
- def test_reordering
499
- assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
500
-
501
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.move_lower
502
- assert_equal [1, 3, 2, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
503
-
504
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.move_higher
505
- assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
506
-
507
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.move_to_bottom
508
- assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
509
-
510
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.move_to_top
511
- assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
512
-
513
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.move_to_bottom
514
- assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
515
-
516
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 4).first.move_to_top
517
- assert_equal [4, 1, 3, 2], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
518
- end
519
-
520
- def test_insert_at
521
- new = DefaultScopedWhereMixin.create
522
- assert_equal 5, new.pos
523
-
524
- new = DefaultScopedWhereMixin.create
525
- assert_equal 6, new.pos
526
-
527
- new = DefaultScopedWhereMixin.create
528
- assert_equal 7, new.pos
529
-
530
- new_noup = DefaultScopedWhereMixin.acts_as_list_no_update { DefaultScopedWhereMixin.create }
531
- assert_equal_or_nil $default_position, new_noup.pos
532
-
533
- new4 = DefaultScopedWhereMixin.create
534
- assert_equal 8, new4.pos
535
-
536
- new4.insert_at(2)
537
- assert_equal 2, new4.pos
538
-
539
- new.reload
540
- assert_equal 8, new.pos
541
-
542
- new.insert_at(2)
543
- assert_equal 2, new.pos
544
-
545
- new4.reload
546
- assert_equal 3, new4.pos
547
-
548
- new5 = DefaultScopedWhereMixin.create
549
- assert_equal 9, new5.pos
550
-
551
- new5.insert_at(1)
552
- assert_equal 1, new5.pos
553
-
554
- new4.reload
555
- assert_equal 4, new4.pos
556
-
557
- new_noup.reload
558
- assert_equal_or_nil $default_position, new_noup.pos
559
- end
560
-
561
- def test_find_or_create_doesnt_raise_deprecation_warning
562
- assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do
563
- DefaultScopedWhereMixin.find_or_create_by(pos: 5)
564
- end
565
- end
566
-
567
- def test_update_position
568
- assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
569
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.set_list_position(4)
570
- assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
571
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.set_list_position(2)
572
- assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
573
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.set_list_position(4)
574
- assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
575
- DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.set_list_position(1)
576
- assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id)
577
- end
578
-
579
- end
580
-
581
- class MultiDestroyTest < ActsAsListTestCase
582
-
583
- def setup
584
- setup_db
585
- end
586
-
587
- # example:
588
- #
589
- # class TodoList < ActiveRecord::Base
590
- # has_many :todo_items, order: "position"
591
- # accepts_nested_attributes_for :todo_items, allow_destroy: true
592
- # end
593
- #
594
- # class TodoItem < ActiveRecord::Base
595
- # belongs_to :todo_list
596
- # acts_as_list scope: :todo_list
597
- # end
598
- #
599
- # Assume that there are three items.
600
- # The user mark two items as deleted, click save button, form will be post:
601
- #
602
- # todo_list.todo_items_attributes = [
603
- # {id: 1, _destroy: true},
604
- # {id: 2, _destroy: true}
605
- # ]
606
- #
607
- # Save toto_list, the position of item #3 should eql 1.
608
- #
609
- def test_destroy
610
- new1 = DefaultScopedMixin.create
611
- assert_equal 1, new1.pos
612
-
613
- new2 = DefaultScopedMixin.create
614
- assert_equal 2, new2.pos
615
-
616
- new3 = DefaultScopedMixin.create
617
- assert_equal 3, new3.pos
618
-
619
- new4 = DefaultScopedMixin.create
620
- assert_equal 4, new4.pos
621
-
622
- new1.destroy
623
- new2.destroy
624
- new3.reload
625
- new4.reload
626
- assert_equal 1, new3.pos
627
- assert_equal 2, new4.pos
628
-
629
- DefaultScopedMixin.acts_as_list_no_update { new3.destroy }
630
-
631
- new4.reload
632
- assert_equal 2, new4.pos
633
- end
634
- end
635
-
636
- class MultiUpdateTest < ActsAsListTestCase
637
-
638
- def setup
639
- setup_db
640
- end
641
-
642
- def test_multiple_updates_within_transaction
643
- @page = ListMixin.create! id: 100, parent_id: nil, pos: 1
644
- @row = ListMixin.create! parent_id: @page.id, pos: 1
645
- @column1 = ListMixin.create! parent_id: @row.id, pos: 1
646
- @column2 = ListMixin.create! parent_id: @row.id, pos: 2
647
- @rich_text1 = ListMixin.create! parent_id: @column1.id, pos: 1
648
- @rich_text2 = ListMixin.create! parent_id: @column2.id, pos: 1
649
-
650
- ActiveRecord::Base.transaction do
651
- @rich_text1.update!(parent_id: @column2.id, pos: 1)
652
-
653
- assert_equal [@rich_text1.id, @rich_text2.id], ListMixin.where(parent_id: @column2.id).order('pos').map(&:id)
654
- assert_equal [1, 2], ListMixin.where(parent_id: @column2.id).order('pos').map(&:pos)
655
-
656
- @column1.destroy!
657
- assert_equal [@column2.id], ListMixin.where(parent_id: @row.id).order('pos').map(&:id)
658
- assert_equal [1], ListMixin.where(parent_id: @row.id).order('pos').map(&:pos)
659
-
660
- @rich_text1.update!(parent_id: @page.id, pos: 1)
661
- @rich_text2.update!(parent_id: @page.id, pos: 2)
662
- @row.destroy!
663
- @column2.destroy!
664
- end
665
-
666
- assert_equal(1, @page.reload.pos)
667
- assert_equal [@rich_text1.id, @rich_text2.id], ListMixin.where(parent_id: @page.id).order('pos').map(&:id)
668
- assert_equal [1, 2], ListMixin.where(parent_id: @page.id).order('pos').map(&:pos)
669
- end
670
- end
671
-
672
- #class TopAdditionMixin < Mixin
673
-
674
- class TopAdditionTest < ActsAsListTestCase
675
- include Shared::TopAddition
676
-
677
- def setup
678
- setup_db
679
- super
680
- end
681
- end
682
-
683
- class TopAdditionTestWithDefault < ActsAsListTestCase
684
- include Shared::TopAddition
685
-
686
- def setup
687
- setup_db_with_default
688
- super
689
- end
690
- end
691
-
692
- class NoAdditionTest < ActsAsListTestCase
693
- include Shared::NoAddition
694
-
695
- def setup
696
- setup_db
697
- super
698
- end
699
- end
700
-
701
- class MultipleListsTest < ActsAsListTestCase
702
- def setup
703
- setup_db
704
- (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 1}
705
- (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 2}
706
- end
707
-
708
- def test_check_scope_order
709
- assert_equal [1, 2, 3, 4], ListMixin.where(:parent_id => 1).order('pos').map(&:id)
710
- assert_equal [5, 6, 7, 8], ListMixin.where(:parent_id => 2).order('pos').map(&:id)
711
- ListMixin.find(4).update :parent_id => 2, :pos => 2
712
- assert_equal [1, 2, 3], ListMixin.where(:parent_id => 1).order('pos').map(&:id)
713
- assert_equal [5, 4, 6, 7, 8], ListMixin.where(:parent_id => 2).order('pos').map(&:id)
714
- end
715
-
716
- def test_check_scope_position
717
- assert_equal [1, 2, 3, 4], ListMixin.where(:parent_id => 1).map(&:pos)
718
- assert_equal [1, 2, 3, 4], ListMixin.where(:parent_id => 2).map(&:pos)
719
- ListMixin.find(4).update :parent_id => 2, :pos => 2
720
- assert_equal [1, 2, 3], ListMixin.where(:parent_id => 1).order('pos').map(&:pos)
721
- assert_equal [1, 2, 3, 4, 5], ListMixin.where(:parent_id => 2).order('pos').map(&:pos)
722
- end
723
-
724
- def test_find_or_create_doesnt_raise_deprecation_warning
725
- assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do
726
- ListMixin.where(:parent_id => 1).find_or_create_by(pos: 5)
727
- end
728
- end
729
- end
730
-
731
- class EnumArrayScopeListMixinTest < ActsAsListTestCase
732
- def setup
733
- setup_db
734
- EnumArrayScopeListMixin.create! :parent_id => 1, :state => EnumArrayScopeListMixin.states['active']
735
- EnumArrayScopeListMixin.create! :parent_id => 1, :state => EnumArrayScopeListMixin.states['archived']
736
- EnumArrayScopeListMixin.create! :parent_id => 2, :state => EnumArrayScopeListMixin.states["active"]
737
- EnumArrayScopeListMixin.create! :parent_id => 2, :state => EnumArrayScopeListMixin.states["archived"]
738
- end
739
-
740
- def test_positions
741
- assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 1, :state => EnumArrayScopeListMixin.states['active']).map(&:pos)
742
- assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 1, :state => EnumArrayScopeListMixin.states['archived']).map(&:pos)
743
- assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 2, :state => EnumArrayScopeListMixin.states['active']).map(&:pos)
744
- assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 2, :state => EnumArrayScopeListMixin.states['archived']).map(&:pos)
745
- end
746
-
747
- def test_update_state
748
- active_item = EnumArrayScopeListMixin.find_by(:parent_id => 2, :state => EnumArrayScopeListMixin.states['active'])
749
- active_item.update(state: EnumArrayScopeListMixin.states['archived'])
750
- assert_equal [1, 2], EnumArrayScopeListMixin.where(:parent_id => 2, :state => EnumArrayScopeListMixin.states['archived']).map(&:pos).sort
751
- end
752
- end
753
-
754
- class MultipleListsArrayScopeTest < ActsAsListTestCase
755
- def setup
756
- setup_db
757
- (1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter,:parent_id => 1, :parent_type => 'anything'}
758
- (1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter,:parent_id => 2, :parent_type => 'something'}
759
- (1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter,:parent_id => 3, :parent_type => 'anything'}
760
- end
761
-
762
- def test_order_after_all_scope_properties_are_changed
763
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:id)
764
- assert_equal [5, 6, 7, 8], ArrayScopeListMixin.where(:parent_id => 2, :parent_type => 'something').order('pos').map(&:id)
765
- ArrayScopeListMixin.find(2).update :parent_id => 2, :pos => 2,:parent_type => 'something'
766
- assert_equal [1, 3, 4], ArrayScopeListMixin.where(:parent_id => 1,:parent_type => 'anything').order('pos').map(&:id)
767
- assert_equal [5, 2, 6, 7, 8], ArrayScopeListMixin.where(:parent_id => 2,:parent_type => 'something').order('pos').map(&:id)
768
- end
769
-
770
- def test_position_after_all_scope_properties_are_changed
771
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').map(&:pos)
772
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 2, :parent_type => 'something').map(&:pos)
773
- ArrayScopeListMixin.find(4).update :parent_id => 2, :pos => 2, :parent_type => 'something'
774
- assert_equal [1, 2, 3], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:pos)
775
- assert_equal [1, 2, 3, 4, 5], ArrayScopeListMixin.where(:parent_id => 2, :parent_type => 'something').order('pos').map(&:pos)
776
- end
777
-
778
- def test_order_after_one_scope_property_is_changed
779
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:id)
780
- assert_equal [9, 10, 11, 12], ArrayScopeListMixin.where(:parent_id => 3, :parent_type => 'anything').order('pos').map(&:id)
781
- ArrayScopeListMixin.find(2).update :parent_id => 3, :pos => 2
782
- assert_equal [1, 3, 4], ArrayScopeListMixin.where(:parent_id => 1,:parent_type => 'anything').order('pos').map(&:id)
783
- assert_equal [9, 2, 10, 11, 12], ArrayScopeListMixin.where(:parent_id => 3,:parent_type => 'anything').order('pos').map(&:id)
784
- end
785
-
786
- def test_position_after_one_scope_property_is_changed
787
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').map(&:pos)
788
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 3, :parent_type => 'anything').map(&:pos)
789
- ArrayScopeListMixin.find(4).update :parent_id => 3, :pos => 2
790
- assert_equal [1, 2, 3], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:pos)
791
- assert_equal [1, 2, 3, 4, 5], ArrayScopeListMixin.where(:parent_id => 3, :parent_type => 'anything').order('pos').map(&:pos)
792
- end
793
-
794
- def test_order_after_moving_to_empty_list
795
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:id)
796
- assert_equal [], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').order('pos').map(&:id)
797
- ArrayScopeListMixin.find(2).update :parent_id => 4, :pos => 1
798
- assert_equal [1, 3, 4], ArrayScopeListMixin.where(:parent_id => 1,:parent_type => 'anything').order('pos').map(&:id)
799
- assert_equal [2], ArrayScopeListMixin.where(:parent_id => 4,:parent_type => 'anything').order('pos').map(&:id)
800
- end
801
-
802
- def test_position_after_moving_to_empty_list
803
- assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').map(&:pos)
804
- assert_equal [], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').map(&:pos)
805
- ArrayScopeListMixin.find(2).update :parent_id => 4, :pos => 1
806
- assert_equal [1, 2, 3], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:pos)
807
- assert_equal [1], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').order('pos').map(&:pos)
808
- end
809
- end
810
-
811
- class ArrayScopeListWithHashTest
812
- def setup
813
- setup_db
814
- @obj1 = ArrayScopeListWithHashMixin.create! :pos => counter, :parent_id => 1, :state => nil
815
- @obj2 = ArrayScopeListWithHashMixin.create! :pos => counter, :parent_id => 1, :state => 'anything'
816
- end
817
-
818
- def test_scope_condition_correct
819
- [@obj1, @obj2].each do |obj|
820
- assert_equal({ :parent_id => 1, :state => nil }, obj.scope_condition)
821
- end
822
- end
823
- end
824
-
825
- require 'timecop'
826
-
827
- class TouchTest < ActsAsListTestCase
828
- def setup
829
- setup_db
830
- Timecop.freeze(yesterday) do
831
- 4.times { ListMixin.create! }
832
- end
833
- end
834
-
835
- def now
836
- @now ||= Time.current.change(usec: 0)
837
- end
838
-
839
- def yesterday
840
- @yesterday ||= 1.day.ago
841
- end
842
-
843
- def updated_ats
844
- ListMixin.order(:id).pluck(:updated_at)
845
- end
846
-
847
- def test_moving_item_lower_touches_self_and_lower_item
848
- Timecop.freeze(now) do
849
- ListMixin.first.move_lower
850
- updated_ats[0..1].each do |updated_at|
851
- assert_equal updated_at.to_i, now.to_i
852
- end
853
- updated_ats[2..3].each do |updated_at|
854
- assert_equal updated_at.to_i, yesterday.to_i
855
- end
856
- end
857
- end
858
-
859
- def test_moving_item_higher_touches_self_and_higher_item
860
- Timecop.freeze(now) do
861
- ListMixin.all.second.move_higher
862
- updated_ats[0..1].each do |updated_at|
863
- assert_equal updated_at.to_i, now.to_i
864
- end
865
- updated_ats[2..3].each do |updated_at|
866
- assert_equal updated_at.to_i, yesterday.to_i
867
- end
868
- end
869
- end
870
-
871
- def test_moving_item_to_bottom_touches_all_other_items
872
- Timecop.freeze(now) do
873
- ListMixin.first.move_to_bottom
874
- updated_ats.each do |updated_at|
875
- assert_equal updated_at.to_i, now.to_i
876
- end
877
- end
878
- end
879
-
880
- def test_moving_item_to_top_touches_all_other_items
881
- Timecop.freeze(now) do
882
- ListMixin.last.move_to_top
883
- updated_ats.each do |updated_at|
884
- assert_equal updated_at.to_i, now.to_i
885
- end
886
- end
887
- end
888
-
889
- def test_removing_item_touches_all_lower_items
890
- Timecop.freeze(now) do
891
- ListMixin.all.third.remove_from_list
892
- updated_ats[0..1].each do |updated_at|
893
- assert_equal updated_at.to_i, yesterday.to_i
894
- end
895
- updated_ats[2..2].each do |updated_at|
896
- assert_equal updated_at.to_i, now.to_i
897
- end
898
- end
899
- end
900
- end
901
-
902
- class TouchDisabledTest < ActsAsListTestCase
903
- def setup
904
- setup_db
905
- Timecop.freeze(yesterday) do
906
- 4.times { TouchDisabledMixin.create! }
907
- end
908
- end
909
-
910
- def now
911
- @now ||= Time.current.change(usec: 0)
912
- end
913
-
914
- def yesterday
915
- @yesterday ||= 1.day.ago
916
- end
917
-
918
- def updated_ats
919
- TouchDisabledMixin.order(:id).pluck(:updated_at)
920
- end
921
-
922
- def positions
923
- ListMixin.order(:id).pluck(:pos)
924
- end
925
-
926
- def test_deleting_item_does_not_touch_higher_items
927
- Timecop.freeze(now) do
928
- TouchDisabledMixin.first.destroy
929
- updated_ats.each do |updated_at|
930
- assert_equal updated_at.to_i, yesterday.to_i
931
- end
932
- assert_equal positions, [1, 2, 3]
933
- end
934
- end
935
- end
936
-
937
- class ActsAsListTopTest < ActsAsListTestCase
938
- def setup
939
- setup_db
940
- end
941
-
942
- def test_acts_as_list_top
943
- assert_equal 1, TheBaseSubclass.new.acts_as_list_top
944
- assert_equal 0, ZeroBasedMixin.new.acts_as_list_top
945
- end
946
-
947
- def test_class_acts_as_list_top
948
- assert_equal 1, TheBaseSubclass.acts_as_list_top
949
- assert_equal 0, ZeroBasedMixin.acts_as_list_top
950
- end
951
- end
952
-
953
- class NilPositionTest < ActsAsListTestCase
954
- def setup
955
- setup_db
956
- end
957
-
958
- def test_nil_position_ordering
959
- new1 = DefaultScopedMixin.create pos: nil
960
- new2 = DefaultScopedMixin.create pos: nil
961
- new3 = DefaultScopedMixin.create pos: nil
962
- DefaultScopedMixin.update_all(pos: nil)
963
-
964
- assert_equal [nil, nil, nil], DefaultScopedMixin.all.map(&:pos)
965
-
966
- new1.reload.pos = 1
967
- new1.save
968
-
969
- new3.reload.pos = 1
970
- new3.save
971
-
972
- assert_equal [1, 2], DefaultScopedMixin.where("pos IS NOT NULL").map(&:pos)
973
- assert_equal [3, 1], DefaultScopedMixin.where("pos IS NOT NULL").map(&:id)
974
- assert_nil new2.reload.pos
975
-
976
- new2.reload.pos = 1
977
- new2.save
978
-
979
- assert_equal [1, 2, 3], DefaultScopedMixin.all.map(&:pos)
980
- assert_equal [2, 3, 1], DefaultScopedMixin.all.map(&:id)
981
- end
982
- end
983
-
984
- class SequentialUpdatesOptionDefaultTest < ActsAsListTestCase
985
- def setup
986
- setup_db
987
- end
988
-
989
- def test_sequential_updates_default_to_false_without_unique_index
990
- assert_equal false, SequentialUpdatesDefault.new.send(:sequential_updates?)
991
- end
992
- end
993
-
994
- class SequentialUpdatesMixinNotNullUniquePositiveConstraintsTest < ActsAsListTestCase
995
- def setup
996
- setup_db null: false, unique: true, positive: true
997
- (1..4).each { |counter| SequentialUpdatesDefault.create!({pos: counter}) }
998
- end
999
-
1000
- def test_sequential_updates_default_to_true_with_unique_index
1001
- assert_equal true, SequentialUpdatesDefault.new.send(:sequential_updates?)
1002
- end
1003
-
1004
- def test_sequential_updates_option_override_with_false
1005
- assert_equal false, SequentialUpdatesFalseMixin.new.send(:sequential_updates?)
1006
- end
1007
-
1008
- def test_insert_at
1009
- new = SequentialUpdatesDefault.create
1010
- assert_equal 5, new.pos
1011
-
1012
- new.insert_at(1)
1013
- assert_equal 1, new.pos
1014
-
1015
- new.insert_at(5)
1016
- assert_equal 5, new.pos
1017
-
1018
- new.insert_at(3)
1019
- assert_equal 3, new.pos
1020
- end
1021
-
1022
- def test_move_to_bottom
1023
- item = SequentialUpdatesDefault.order(:pos).first
1024
- item.move_to_bottom
1025
- assert_equal 4, item.pos
1026
- end
1027
-
1028
- def test_move_to_top
1029
- new_item = SequentialUpdatesDefault.create!
1030
- assert_equal 5, new_item.pos
1031
-
1032
- new_item.move_to_top
1033
- assert_equal 1, new_item.pos
1034
- end
1035
-
1036
- def test_destroy
1037
- new_item = SequentialUpdatesDefault.create
1038
- assert_equal 5, new_item.pos
1039
-
1040
- new_item.insert_at(2)
1041
- assert_equal 2, new_item.pos
1042
-
1043
- new_item.destroy
1044
- assert_equal [1,2,3,4], SequentialUpdatesDefault.all.map(&:pos).sort
1045
-
1046
- end
1047
-
1048
- def test_exception_on_wrong_position
1049
- new_item = SequentialUpdatesDefault.create
1050
-
1051
- assert_raises ArgumentError do
1052
- new_item.insert_at(0)
1053
- end
1054
- end
1055
-
1056
-
1057
- class SequentialUpdatesMixinNotNullUniquePositiveConstraintsTest < ActsAsListTestCase
1058
- def setup
1059
- setup_db null: false, unique: true, positive: true
1060
- (1..4).each { |counter| SequentialUpdatesAltId.create!({pos: counter}) }
1061
- end
1062
-
1063
- def test_sequential_updates_default_to_true_with_unique_index
1064
- assert_equal true, SequentialUpdatesAltId.new.send(:sequential_updates?)
1065
- end
1066
-
1067
- def test_insert_at
1068
- new = SequentialUpdatesAltId.create
1069
- assert_equal 5, new.pos
1070
-
1071
- new.insert_at(1)
1072
- assert_equal 1, new.pos
1073
-
1074
- new.insert_at(5)
1075
- assert_equal 5, new.pos
1076
-
1077
- new.insert_at(3)
1078
- assert_equal 3, new.pos
1079
- end
1080
-
1081
- def test_create_at_top
1082
- new = SequentialUpdatesAltId.create!(pos: 1)
1083
- assert_equal 1, new.pos
1084
- end
1085
-
1086
- def test_move_to_bottom
1087
- item = SequentialUpdatesAltId.order(:pos).first
1088
- item.move_to_bottom
1089
- assert_equal 4, item.pos
1090
- end
1091
-
1092
- def test_move_to_top
1093
- new_item = SequentialUpdatesAltId.create!
1094
- assert_equal 5, new_item.pos
1095
-
1096
- new_item.move_to_top
1097
- assert_equal 1, new_item.pos
1098
- end
1099
-
1100
- def test_destroy
1101
- new_item = SequentialUpdatesAltId.create
1102
- assert_equal 5, new_item.pos
1103
-
1104
- new_item.insert_at(2)
1105
- assert_equal 2, new_item.pos
1106
-
1107
- new_item.destroy
1108
- assert_equal [1,2,3,4], SequentialUpdatesAltId.all.map(&:pos).sort
1109
-
1110
- end
1111
- end
1112
-
1113
- class SequentialUpdatesAltIdTouchDisabledTest < ActsAsListTestCase
1114
- def setup
1115
- setup_db
1116
- Timecop.freeze(yesterday) do
1117
- 4.times { SequentialUpdatesAltIdTouchDisabled.create! }
1118
- end
1119
- end
1120
-
1121
- def now
1122
- @now ||= Time.current.change(usec: 0)
1123
- end
1124
-
1125
- def yesterday
1126
- @yesterday ||= 1.day.ago
1127
- end
1128
-
1129
- def updated_ats
1130
- SequentialUpdatesAltIdTouchDisabled.order(:altid).pluck(:updated_at)
1131
- end
1132
-
1133
- def positions
1134
- SequentialUpdatesAltIdTouchDisabled.order(:altid).pluck(:pos)
1135
- end
1136
-
1137
- def test_sequential_updates_default_to_true_with_unique_index
1138
- assert_equal true, SequentialUpdatesAltIdTouchDisabled.new.send(:sequential_updates?)
1139
- end
1140
-
1141
- def test_deleting_item_does_not_touch_higher_items
1142
- Timecop.freeze(now) do
1143
- SequentialUpdatesAltIdTouchDisabled.first.destroy
1144
- updated_ats.each do |updated_at|
1145
- assert_equal updated_at.to_i, yesterday.to_i
1146
- end
1147
- assert_equal positions, [1, 2, 3]
1148
- end
1149
- end
1150
- end
1151
- end
1152
-
1153
- if ActiveRecord::VERSION::MAJOR == 7 && ActiveRecord::VERSION::MINOR >= 1 || ActiveRecord::VERSION::MAJOR > 7
1154
- class CompositePrimaryKeyListTest < ActsAsListTestCase
1155
- def setup
1156
- setup_db
1157
- (1..4).each { |counter| CompositePrimaryKeyList.create!({first_id: counter, second_id: counter, pos: counter}) }
1158
- end
1159
-
1160
- def test_insert_at
1161
- new = CompositePrimaryKeyList.create({first_id: 5, second_id: 5})
1162
- assert_equal 5, new.pos
1163
-
1164
- new.insert_at(1)
1165
- assert_equal 1, new.pos
1166
-
1167
- new.insert_at(5)
1168
- assert_equal 5, new.pos
1169
-
1170
- new.insert_at(3)
1171
- assert_equal 3, new.pos
1172
- end
1173
-
1174
- def test_move_lower
1175
- item = CompositePrimaryKeyList.order(:pos).first
1176
- item.move_lower
1177
- assert_equal 2, item.pos
1178
- end
1179
-
1180
- def test_move_higher
1181
- item = CompositePrimaryKeyList.order(:pos).second
1182
- item.move_higher
1183
- assert_equal 1, item.pos
1184
- end
1185
-
1186
- def test_move_to_bottom
1187
- item = CompositePrimaryKeyList.order(:pos).first
1188
- item.move_to_bottom
1189
- assert_equal 4, item.pos
1190
- end
1191
-
1192
- def test_move_to_top
1193
- new_item = CompositePrimaryKeyList.create!({first_id: 5, second_id: 5})
1194
- assert_equal 5, new_item.pos
1195
-
1196
- new_item.move_to_top
1197
- assert_equal 1, new_item.pos
1198
- end
1199
-
1200
- def test_remove_from_list
1201
- assert_equal true, CompositePrimaryKeyList.find([1, 1]).in_list?
1202
- CompositePrimaryKeyList.find([1,1]).remove_from_list
1203
- assert_equal false, CompositePrimaryKeyList.find([1, 1]).in_list?
1204
- end
1205
-
1206
- def test_increment_position
1207
- item = CompositePrimaryKeyList.order(:pos).first
1208
- item.increment_position
1209
- assert_equal 2, item.pos
1210
- end
1211
-
1212
- def test_decrement_position
1213
- item = CompositePrimaryKeyList.order(:pos).second
1214
- item.decrement_position
1215
- assert_equal 1, item.pos
1216
- end
1217
-
1218
- def test_set_list_position
1219
- item = CompositePrimaryKeyList.order(:pos).first
1220
- item.set_list_position(4)
1221
- assert_equal 4, item.pos
1222
- end
1223
-
1224
- def test_create_at_top
1225
- new = CompositePrimaryKeyList.create({first_id: 5, second_id: 5, pos: 1})
1226
- assert_equal 1, new.pos
1227
- end
1228
-
1229
- def test_destroy
1230
- new_item = CompositePrimaryKeyList.create({first_id: 5, second_id: 5})
1231
- assert_equal 5, new_item.pos
1232
- assert_equal true, new_item.last?
1233
-
1234
- new_item.insert_at(1)
1235
- assert_equal 1, new_item.pos
1236
- assert_equal true, new_item.first?
1237
-
1238
- new_item.destroy
1239
- assert_equal [1,2,3,4], CompositePrimaryKeyList.all.map(&:pos).sort
1240
- end
1241
- end
1242
-
1243
- class CompositePrimaryKeyListScopedTest < ActsAsListTestCase
1244
- include Shared::List
1245
-
1246
- def setup
1247
- setup_db
1248
-
1249
- super(CompositePrimaryKeyListScoped, CompositePrimaryKeyListScopedError, :first_id)
1250
- end
1251
- end
1252
- end
1253
-