acts_as_list 1.2.0 → 1.2.2

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.
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
-