active_admin_import 8.0.0 → 8.0.1

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_admin_import/version.rb +1 -1
  3. metadata +2 -42
  4. data/.github/workflows/test.yml +0 -219
  5. data/.gitignore +0 -19
  6. data/.rubocop.yml +0 -43
  7. data/Gemfile +0 -37
  8. data/Rakefile +0 -5
  9. data/active_admin_import.gemspec +0 -22
  10. data/spec/fixtures/files/author.csv +0 -2
  11. data/spec/fixtures/files/author_broken_header.csv +0 -2
  12. data/spec/fixtures/files/author_invalid.csv +0 -2
  13. data/spec/fixtures/files/author_invalid_format.txt +0 -2
  14. data/spec/fixtures/files/authors.csv +0 -3
  15. data/spec/fixtures/files/authors_blank_header_end.csv +0 -3
  16. data/spec/fixtures/files/authors_blank_header_middle.csv +0 -3
  17. data/spec/fixtures/files/authors_bom.csv +0 -3
  18. data/spec/fixtures/files/authors_empty_header.csv +0 -3
  19. data/spec/fixtures/files/authors_invalid_db.csv +0 -3
  20. data/spec/fixtures/files/authors_invalid_model.csv +0 -3
  21. data/spec/fixtures/files/authors_many.csv +0 -6
  22. data/spec/fixtures/files/authors_no_headers.csv +0 -2
  23. data/spec/fixtures/files/authors_values_exceeded_headers.csv +0 -3
  24. data/spec/fixtures/files/authors_win1251_win_endline.csv +0 -3
  25. data/spec/fixtures/files/authors_with_ids.csv +0 -3
  26. data/spec/fixtures/files/authors_with_semicolons.csv +0 -3
  27. data/spec/fixtures/files/authors_with_tabs.tsv +0 -3
  28. data/spec/fixtures/files/empty.csv +0 -0
  29. data/spec/fixtures/files/only_headers.csv +0 -1
  30. data/spec/fixtures/files/post_comments.csv +0 -3
  31. data/spec/fixtures/files/posts.csv +0 -4
  32. data/spec/fixtures/files/posts_for_author.csv +0 -3
  33. data/spec/fixtures/files/posts_for_author_no_headers.csv +0 -2
  34. data/spec/import_result_spec.rb +0 -39
  35. data/spec/import_spec.rb +0 -859
  36. data/spec/model_spec.rb +0 -6
  37. data/spec/spec_helper.rb +0 -54
  38. data/spec/support/active_model_lint.rb +0 -15
  39. data/spec/support/admin.rb +0 -32
  40. data/spec/support/import_form_selectors.rb +0 -24
  41. data/spec/support/rails_template.rb +0 -71
  42. data/spec/support/test_app_paths.rb +0 -14
  43. data/tasks/test.rake +0 -39
data/spec/import_spec.rb DELETED
@@ -1,859 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'spec_helper'
3
-
4
- describe 'import', type: :feature do
5
- shared_examples 'successful inserts' do |encoding, csv_file_name|
6
- let(:options) do
7
- attributes = { force_encoding: encoding }
8
- { template_object: ActiveAdminImport::Model.new(attributes) }
9
- end
10
-
11
- before do
12
- upload_file!(csv_file_name)
13
- end
14
-
15
- it 'should import file with many records' do
16
- expect(page).to have_content 'Successfully imported 2 authors'
17
- expect(Author.count).to eq(2)
18
- Author.all.each do |author|
19
- expect(author).to be_valid
20
- expect(author.name).to be_present
21
- expect(author.last_name).to be_present
22
- end
23
- end
24
- end
25
-
26
- def with_zipped_csv(name, &block)
27
- zip_file = File.expand_path("./spec/fixtures/files/#{name}.zip")
28
-
29
- begin
30
- Zip::File.open(zip_file, create: true) do |z|
31
- z.add "#{name}.csv", File.expand_path("./spec/fixtures/files/#{name}.csv")
32
- end
33
- instance_eval &block
34
- ensure
35
- begin
36
- File.delete zip_file
37
- rescue
38
- nil
39
- end
40
- end
41
- end
42
-
43
- def upload_file!(name, ext = 'csv')
44
- attach_file(ImportFormSelectors.file_input_id, File.expand_path("./spec/fixtures/files/#{name}.#{ext}"))
45
- find_button(ImportFormSelectors.import_button_text).click
46
- end
47
-
48
- context 'posts index' do
49
- before do
50
- Author.create!(name: 'John', last_name: 'Doe')
51
- Author.create!(name: 'Jane', last_name: 'Roe')
52
- end
53
-
54
- context 'for csv for particular author' do
55
- let(:author) { Author.take }
56
-
57
- shared_examples 'successful inserts for author' do
58
- it 'should use predefined author_id' do
59
- expect(Post.where(author_id: author.id).count).to eq(Post.count)
60
- end
61
-
62
- it 'should be imported' do
63
- expect(Post.count).to eq(2)
64
- expect(page).to have_content 'Successfully imported 2 posts'
65
- end
66
- end
67
-
68
- context 'no headers' do
69
- before do
70
- add_post_resource(template_object: ActiveAdminImport::Model.new(author_id: author.id,
71
- csv_headers: [:title, :body, :author_id]),
72
- validate: true,
73
- before_batch_import: lambda do |importer|
74
- importer.csv_lines.map! { |row| row << importer.model.author_id }
75
- end
76
- )
77
-
78
- visit '/admin/posts/import'
79
- upload_file!(:posts_for_author_no_headers)
80
- end
81
- include_examples 'successful inserts for author'
82
- end
83
-
84
- context 'with headers' do
85
- before do
86
- add_post_resource(template_object: ActiveAdminImport::Model.new(author_id: author.id),
87
- validate: true,
88
- before_batch_import: lambda do |importer|
89
- importer.csv_lines.map! { |row| row << importer.model.author_id }
90
- importer.headers.merge!(:'Author Id' => :author_id)
91
- end
92
- )
93
-
94
- visit '/admin/posts/import'
95
- upload_file!(:posts_for_author)
96
- end
97
- include_examples 'successful inserts for author'
98
- end
99
-
100
- context 'when template object passed like proc' do
101
- before do
102
- add_post_resource(template_object: -> { ActiveAdminImport::Model.new(author_id: author.id) },
103
- validate: true,
104
- before_batch_import: lambda do |importer|
105
- importer.csv_lines.map! { |row| row << importer.model.author_id }
106
- importer.headers.merge!(:'Author Id' => :author_id)
107
- end
108
- )
109
-
110
- visit '/admin/posts/import'
111
- upload_file!(:posts_for_author)
112
- end
113
-
114
- include_examples 'successful inserts for author'
115
-
116
- context 'after successful import try without file' do
117
- let(:after_successful_import_do!) do
118
- # reload page
119
- visit '/admin/posts/import'
120
- # submit form without file
121
- find_button(ImportFormSelectors.import_button_text).click
122
- end
123
-
124
- it 'should render validation error' do
125
- after_successful_import_do!
126
-
127
- expect(page).to have_content I18n.t('active_admin_import.no_file_error')
128
- end
129
- end
130
- end
131
- end
132
-
133
- context 'for csv with author name' do
134
- before do
135
- add_post_resource(
136
- validate: true,
137
- template_object: ActiveAdminImport::Model.new,
138
- headers_rewrites: { :'Author Name' => :author_id },
139
- before_batch_import: lambda do |importer|
140
- authors_names = importer.values_at(:author_id)
141
- # replacing author name with author id
142
- authors = Author.where(name: authors_names).pluck(:name, :id)
143
- # {"Jane" => 2, "John" => 1}
144
- options = Hash[*authors.flatten]
145
- importer.batch_replace(:author_id, options)
146
- end
147
- )
148
- visit '/admin/posts/import'
149
- upload_file!(:posts)
150
- end
151
-
152
- it 'should resolve author_id by author name' do
153
- Post.all.each do |post|
154
- expect(Author.where(id: post.author.id)).to be_present
155
- end
156
- end
157
-
158
- it 'should be imported' do
159
- expect(Post.count).to eq(2)
160
- expect(page).to have_content 'Successfully imported 2 posts'
161
- end
162
- end
163
- end
164
-
165
- context 'authors index' do
166
- before do
167
- add_author_resource
168
- end
169
-
170
- it 'should navigate to import page' do
171
- # TODO: removing this causes undefined method `ransack' for #<ActiveRecord::Relation []>
172
- allow_any_instance_of(Admin::AuthorsController).to receive(:find_collection).and_return(Author.all)
173
- visit '/admin/authors'
174
- find_link(ImportFormSelectors.import_link_text).click
175
- expect(current_path).to eq('/admin/authors/import')
176
- end
177
- end
178
-
179
- context 'with custom block' do
180
- before do
181
- add_author_resource({}) do
182
- flash[:notice] = 'some custom message'
183
- end
184
- visit '/admin/authors/import'
185
- end
186
-
187
- it 'should display notice from custom block' do
188
- upload_file!(:author)
189
- expect(page).to have_content 'some custom message'
190
- end
191
- end
192
-
193
- # Issue #197: a CSV with an empty header cell used to crash with
194
- # `undefined method 'underscore' for nil`, wherever the blank sits.
195
- context 'with a blank column header' do
196
- shared_examples 'a rejected blank header' do |fixture, column|
197
- before do
198
- add_author_resource
199
- visit '/admin/authors/import'
200
- upload_file!(fixture)
201
- end
202
-
203
- it 'reports a clear error and imports nothing' do
204
- expect(page).to have_content "blank column header at column #{column}"
205
- expect(page).to have_no_content 'undefined method'
206
- expect(Author.count).to eq(0)
207
- end
208
- end
209
-
210
- context 'at the beginning' do
211
- it_behaves_like 'a rejected blank header', :authors_empty_header, 1
212
- end
213
-
214
- context 'in the middle' do
215
- it_behaves_like 'a rejected blank header', :authors_blank_header_middle, 2
216
- end
217
-
218
- context 'at the end' do
219
- it_behaves_like 'a rejected blank header', :authors_blank_header_end, 4
220
- end
221
- end
222
-
223
- context 'authors already exist' do
224
- before do
225
- Author.create!(id: 1, name: 'Jane', last_name: 'Roe')
226
- Author.create!(id: 2, name: 'John', last_name: 'Doe')
227
- end
228
-
229
- context 'having authors with the same Id' do
230
- before do
231
- add_author_resource(
232
- before_batch_import: lambda do |importer|
233
- Author.where(id: importer.values_at('id')).delete_all
234
- end
235
- )
236
- visit '/admin/authors/import'
237
- upload_file!(:authors_with_ids)
238
- end
239
-
240
- it 'should replace authors' do
241
- expect(page).to have_content 'Successfully imported 2 authors'
242
- expect(Author.count).to eq(2)
243
- end
244
-
245
- it 'should replace authors by id' do
246
- expect(Author.find(1).name).to eq('John')
247
- expect(Author.find(2).name).to eq('Jane')
248
- end
249
- end
250
-
251
- # Issue #187: update existing records by id without a delete_all workaround.
252
- # On databases that support upserts, :on_duplicate_key_update lets a single
253
- # import update colliding rows and insert new ones in one pass.
254
- context 'upserting authors by id via :on_duplicate_key_update' do
255
- before do
256
- # Existing row shares its id with the first CSV row but carries a stale
257
- # birthday; the second CSV row (id 2) has no match and must be inserted.
258
- Author.delete_all
259
- Author.create!(id: 1, name: 'John', last_name: 'Doe', birthday: '1900-01-01')
260
-
261
- # The option shape is adapter-specific: MySQL infers the conflicting key
262
- # and only wants the column list, while PostgreSQL/SQLite need an explicit
263
- # :conflict_target (see README).
264
- on_duplicate_key_update =
265
- if ActiveRecord::Base.connection.adapter_name.match?(/mysql/i)
266
- %i[name last_name birthday]
267
- else
268
- { conflict_target: [:id], columns: %i[name last_name birthday] }
269
- end
270
-
271
- add_author_resource(
272
- # Uniqueness validation runs against the rows the upsert is about to
273
- # overwrite, so it must be off for an id-based upsert (see README).
274
- validate: false,
275
- on_duplicate_key_update: on_duplicate_key_update
276
- )
277
- visit '/admin/authors/import'
278
- upload_file!(:authors_with_ids)
279
- end
280
-
281
- it 'reports every row as imported' do
282
- expect(page).to have_content 'Successfully imported 2 authors'
283
- end
284
-
285
- it 'updates the existing author instead of duplicating it' do
286
- expect(Author.count).to eq(2)
287
- expect(Author.find(1).birthday).to eq(Date.new(1986, 5, 1))
288
- end
289
-
290
- it 'inserts the non-colliding author' do
291
- expect(Author.find(2).name).to eq('Jane')
292
- expect(Author.find(2).last_name).to eq('Roe')
293
- end
294
- end
295
- end
296
-
297
- context 'with valid options' do
298
- let(:options) { {} }
299
-
300
- before do
301
- add_author_resource(options)
302
- visit '/admin/authors/import'
303
- end
304
-
305
- it 'has valid form' do
306
- form = find(ImportFormSelectors.form_css)
307
- expect(form['action']).to eq('/admin/authors/do_import')
308
- expect(form['enctype']).to eq('multipart/form-data')
309
- file_input = form.find(ImportFormSelectors.file_input_css)
310
- expect(file_input[:type]).to eq('file')
311
- expect(file_input.value).to be_blank
312
- submit_input = form.find(ImportFormSelectors.submit_css)
313
- expect(submit_input[:value]).to eq(ImportFormSelectors.import_button_text)
314
- expect(submit_input[:type]).to eq('submit')
315
- end
316
-
317
- context 'with hint defined' do
318
- let(:options) do
319
- { template_object: ActiveAdminImport::Model.new(hint: 'hint') }
320
- end
321
- it 'renders hint at upload page' do
322
- expect(page).to have_content options[:template_object].hint
323
- end
324
- end
325
-
326
- context 'when importing file' do
327
- [:empty, :only_headers].each do |file|
328
- context "when #{file} file" do
329
- it 'should render warning' do
330
- upload_file!(file)
331
- expect(page).to have_content I18n.t('active_admin_import.file_empty_error')
332
- expect(Author.count).to eq(0)
333
- end
334
- end
335
- end
336
-
337
- context 'when no file' do
338
- it 'should render error' do
339
- find_button(ImportFormSelectors.import_button_text).click
340
- expect(Author.count).to eq(0)
341
- expect(page).to have_content I18n.t('active_admin_import.no_file_error')
342
- end
343
- end
344
-
345
- context 'auto detect encoding' do
346
- include_examples 'successful inserts',
347
- :auto,
348
- :authors_win1251_win_endline
349
- end
350
-
351
- context 'Win1251' do
352
- include_examples 'successful inserts',
353
- 'windows-1251',
354
- :authors_win1251_win_endline
355
- end
356
-
357
- context 'BOM' do
358
- it 'should import file with many records' do
359
- upload_file!(:authors_bom)
360
- expect(page).to have_content 'Successfully imported 2 authors'
361
- expect(Author.count).to eq(2)
362
- end
363
- end
364
-
365
- context 'with headers' do
366
- it 'should import file with many records' do
367
- upload_file!(:authors)
368
- expect(page).to have_content 'Successfully imported 2 authors'
369
- expect(Author.count).to eq(2)
370
- end
371
-
372
- it 'should import file with 1 record' do
373
- upload_file!(:author)
374
- expect(page).to have_content 'Successfully imported 1 author'
375
- expect(Author.count).to eq(1)
376
- end
377
- end
378
-
379
- context 'without headers' do
380
- context 'with known csv headers' do
381
- let(:options) do
382
- attributes = { csv_headers: ['Name', 'Last name', 'Birthday'] }
383
- { template_object: ActiveAdminImport::Model.new(attributes) }
384
- end
385
-
386
- it 'should import file' do
387
- upload_file!(:authors_no_headers)
388
- expect(page).to have_content 'Successfully imported 2 authors'
389
- expect(Author.count).to eq(2)
390
- end
391
- end
392
-
393
- context 'with unknown csv headers' do
394
- it 'should render error' do
395
- upload_file!(:authors_no_headers)
396
- expect(page).to have_content 'Error:'
397
- expect(Author.count).to eq(0)
398
- end
399
- end
400
- end
401
-
402
- context 'with invalid data insert on DB constraint' do
403
- # :name field has an uniq index
404
- it 'should render error' do
405
- upload_file!(:authors_invalid_db)
406
- expect(page).to have_content 'Error:'
407
- expect(Author.count).to eq(0)
408
- end
409
- end
410
-
411
- context 'with invalid data insert on model validation' do
412
- let(:options) { { validate: true } }
413
-
414
- before do
415
- Author.create!(name: 'John', last_name: 'Doe')
416
- end
417
-
418
- it 'should render both successful and failed message' do
419
- upload_file!(:authors_invalid_model)
420
- expect(page).to have_content 'Failed to import 1 author'
421
- expect(page).to have_content 'Successfully imported 1 author'
422
- expect(page).to have_content 'Last name has already been taken - Doe'
423
- expect(Author.count).to eq(2)
424
- end
425
-
426
- context 'use batch_transaction to make transaction work on model validation' do
427
- let(:options) { { validate: true, batch_transaction: true } }
428
-
429
- it 'should render only the failed message' do
430
- upload_file!(:authors_invalid_model)
431
- expect(page).to have_content 'Failed to import 1 author'
432
- expect(page).to_not have_content 'Successfully imported'
433
- expect(Author.count).to eq(1)
434
- end
435
- end
436
- end
437
-
438
- context 'with invalid records' do
439
- context 'with validation' do
440
- it 'should render error' do
441
- upload_file!(:author_invalid)
442
- expect(page).to have_content 'Failed to import 1 author'
443
- expect(Author.count).to eq(0)
444
- end
445
- end
446
-
447
- context 'without validation' do
448
- let(:options) { { validate: false } }
449
- it 'should render error' do
450
- upload_file!(:author_invalid)
451
- expect(page).to have_content 'Successfully imported 1 author'
452
- expect(Author.count).to eq(1)
453
- end
454
- end
455
- end
456
-
457
- context 'when zipped' do
458
- context 'when allowed' do
459
- it 'should import file' do
460
- with_zipped_csv(:authors) do
461
- upload_file!(:authors, :zip)
462
- expect(page).to have_content 'Successfully imported 2 authors'
463
- expect(Author.count).to eq(2)
464
- end
465
- end
466
- end
467
-
468
- context 'when not allowed' do
469
- let(:options) do
470
- attributes = { allow_archive: false }
471
- { template_object: ActiveAdminImport::Model.new(attributes) }
472
- end
473
- it 'should render error' do
474
- with_zipped_csv(:authors) do
475
- upload_file!(:authors, :zip)
476
- expect(page).to have_content I18n.t('active_admin_import.file_format_error')
477
- expect(Author.count).to eq(0)
478
- end
479
- end
480
- end
481
-
482
- context 'when zipped with Win1251 file' do
483
- let(:options) do
484
- attributes = { force_encoding: :auto }
485
- { template_object: ActiveAdminImport::Model.new(attributes) }
486
- end
487
- it 'should import file' do
488
- with_zipped_csv(:authors_win1251_win_endline) do
489
- upload_file!(:authors_win1251_win_endline, :zip)
490
- expect(page).to have_content 'Successfully imported 2 authors'
491
- expect(Author.count).to eq(2)
492
- end
493
- end
494
- end
495
- end
496
-
497
- context 'with different header attribute names' do
498
- let(:options) do
499
- { headers_rewrites: { :'Second name' => :last_name } }
500
- end
501
-
502
- it 'should import file' do
503
- upload_file!(:author_broken_header)
504
- expect(page).to have_content 'Successfully imported 1 author'
505
- expect(Author.count).to eq(1)
506
- end
507
- end
508
-
509
- context 'with semicolons separator' do
510
- let(:options) do
511
- attributes = { csv_options: { col_sep: ';' } }
512
- { template_object: ActiveAdminImport::Model.new(attributes) }
513
- end
514
-
515
- it 'should import file' do
516
- upload_file!(:authors_with_semicolons)
517
- expect(page).to have_content 'Successfully imported 2 authors'
518
- expect(Author.count).to eq(2)
519
- end
520
- end
521
-
522
- context 'with tab separator' do
523
- let(:options) do
524
- attributes = { csv_options: { col_sep: "\t" } }
525
- { template_object: ActiveAdminImport::Model.new(attributes) }
526
- end
527
-
528
- it 'should import file' do
529
- upload_file!(:authors_with_tabs, 'tsv')
530
- expect(page).to have_content 'Successfully imported 2 authors'
531
- expect(Author.count).to eq(2)
532
- end
533
- end
534
-
535
- context 'with empty csv and auto detect encoding' do
536
- let(:options) do
537
- attributes = { force_encoding: :auto }
538
- { template_object: ActiveAdminImport::Model.new(attributes) }
539
- end
540
-
541
- before do
542
- upload_file!(:empty)
543
- end
544
-
545
- it 'should render warning' do
546
- expect(page).to have_content I18n.t('active_admin_import.file_empty_error')
547
- expect(Author.count).to eq(0)
548
- end
549
- end
550
-
551
- context 'with csv which has exceeded values' do
552
- before do
553
- upload_file!(:authors_values_exceeded_headers)
554
- end
555
-
556
- it 'should render warning' do
557
- # 5 columns: 'birthday, name, last_name, created_at, updated_at'
558
- # 6 values: '"1988-11-16", "Jane", "Roe", " exceeded value", datetime, datetime'
559
- msg = 'Number of values (6) exceeds number of columns (5)'
560
- expect(page).to have_content I18n.t('active_admin_import.file_error', message: msg)
561
- expect(Author.count).to eq(0)
562
- end
563
- end
564
- end
565
-
566
- context 'with callback procs options' do
567
- let(:options) do
568
- {
569
- before_import: ->(_) { true },
570
- after_import: ->(_) { true },
571
- before_batch_import: ->(_) { true },
572
- after_batch_import: ->(_) { true }
573
- }
574
- end
575
-
576
- it 'should call each callback' do
577
- expect(options[:before_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
578
- expect(options[:after_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
579
- expect(options[:before_batch_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
580
- expect(options[:after_batch_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
581
- upload_file!(:authors)
582
- expect(Author.count).to eq(2)
583
- end
584
-
585
- context 'when the option before_import raises a ActiveAdminImport::Exception' do
586
- let(:options) { { before_import: ->(_) { raise ActiveAdminImport::Exception, 'error message' } } }
587
-
588
- before { upload_file!(:authors) }
589
-
590
- it 'should show error' do
591
- expect(page).to have_content I18n.t('active_admin_import.file_error', message: 'error message')
592
- expect(Author.count).to eq(0)
593
- end
594
- end
595
-
596
- context 'when the option before_batch_import raises a ActiveAdminImport::Exception' do
597
- let(:options) { { before_batch_import: ->(_) { raise ActiveAdminImport::Exception, 'error message' } } }
598
-
599
- before { upload_file!(:authors) }
600
-
601
- it 'should show error' do
602
- expect(page).to have_content I18n.t('active_admin_import.file_error', message: 'error message')
603
- expect(Author.count).to eq(0)
604
- end
605
- end
606
- end
607
- end
608
-
609
- context "with slice_columns option" do
610
- let(:batch_size) { 2 }
611
-
612
- before do
613
- add_author_resource template_object: ActiveAdminImport::Model.new,
614
- before_batch_import: lambda { |importer|
615
- importer.batch_slice_columns(slice_columns)
616
- },
617
- batch_size: batch_size
618
- visit "/admin/authors/import"
619
- upload_file!(:authors)
620
- end
621
-
622
- context "slice last column and superfluous column" do
623
- let(:slice_columns) { %w(name last_name not_existing_column) }
624
-
625
- shared_examples_for "birthday column removed" do
626
- it "should not fill `birthday` column" do
627
- expect(Author.pluck(:name, :last_name, :birthday)).to match_array(
628
- [
629
- ["Jane", "Roe", nil],
630
- ["John", "Doe", nil]
631
- ]
632
- )
633
- end
634
- end
635
-
636
- it_behaves_like "birthday column removed"
637
-
638
- context "when doing more than one batch" do
639
- let(:batch_size) { 1 }
640
-
641
- it_behaves_like "birthday column removed"
642
- end
643
- end
644
-
645
- context "slice column from the middle" do
646
- let(:slice_columns) { %w(name birthday) }
647
-
648
- it "should not fill `last_name` column" do
649
- expect(Author.pluck(:name, :last_name, :birthday)).to match_array(
650
- [
651
- ["Jane", nil, "1988-11-16".to_date],
652
- ["John", nil, "1986-05-01".to_date]
653
- ]
654
- )
655
- end
656
- end
657
- end
658
-
659
- # Issue #186: each call must slice the result of the previous one, not re-apply
660
- # the first call's indices to an already-sliced row.
661
- context "with successive batch_slice_columns calls" do
662
- before do
663
- # validate: false isolates the slicing behaviour from the model's
664
- # uniqueness validation, which would otherwise reject a later batch's
665
- # author for sharing a NULL last_name with an earlier one.
666
- add_author_resource template_object: ActiveAdminImport::Model.new,
667
- validate: false,
668
- before_batch_import: lambda { |importer|
669
- importer.batch_slice_columns(%w(name last_name))
670
- importer.batch_slice_columns(%w(name))
671
- },
672
- batch_size: batch_size
673
- visit "/admin/authors/import"
674
- upload_file!(fixture)
675
- end
676
-
677
- context "within a single batch" do
678
- let(:batch_size) { 2 } # authors.csv has 2 rows -> 1 batch
679
- let(:fixture) { :authors }
680
-
681
- it "narrows the columns progressively" do
682
- expect(Author.pluck(:name, :last_name, :birthday)).to match_array(
683
- [
684
- ["Jane", nil, nil],
685
- ["John", nil, nil]
686
- ]
687
- )
688
- end
689
- end
690
-
691
- context "across several batches" do
692
- let(:batch_size) { 2 } # authors_many.csv has 5 rows -> 3 batches
693
- let(:fixture) { :authors_many }
694
-
695
- it "narrows the columns progressively in every batch" do
696
- expect(Author.pluck(:name, :last_name, :birthday)).to match_array(
697
- [
698
- ["John", nil, nil],
699
- ["Jane", nil, nil],
700
- ["Jack", nil, nil],
701
- ["Jill", nil, nil],
702
- ["Joe", nil, nil]
703
- ]
704
- )
705
- end
706
- end
707
- end
708
-
709
- context 'with invalid options' do
710
- let(:options) { { invalid_option: :invalid_value } }
711
-
712
- it 'should raise TypeError' do
713
- expect { add_author_resource(options) }.to raise_error(ArgumentError)
714
- end
715
- end
716
-
717
- context 'when submitting empty form after validation error' do
718
- let(:options) { {} }
719
-
720
- before do
721
- add_author_resource(options)
722
- visit '/admin/authors/import'
723
- end
724
-
725
- it 'should NOT reuse cached file from previous submission' do
726
- expect do
727
- upload_file!(:author_broken_header)
728
- expect(page).to have_content("can't write unknown attribute")
729
- end.not_to change { Author.count }
730
-
731
- # Second submission without selecting a file
732
- expect do
733
- find_button(ImportFormSelectors.import_button_text).click
734
- expect(page).to have_content(I18n.t('active_admin_import.no_file_error'))
735
- end.not_to change { Author.count }
736
- end
737
- end
738
-
739
- context 'when importing file with invalid format and auto force_encoding' do
740
- let(:options) { { template_object: ActiveAdminImport::Model.new(force_encoding: :auto) } }
741
-
742
- before do
743
- add_author_resource(options)
744
- visit '/admin/authors/import'
745
- end
746
-
747
- it 'should reject invalid file format before encoding' do
748
- expect do
749
- upload_file!(:author_invalid_format, 'txt')
750
- expect(page).to have_content I18n.t('active_admin_import.file_format_error')
751
- end.not_to change { Author.count }
752
- end
753
- end
754
-
755
- context 'with active_admin_import_context defined on the controller' do
756
- before { Author.create!(name: 'John', last_name: 'Doe') }
757
-
758
- let(:author) { Author.take }
759
-
760
- context 'when context returns request-derived attributes' do
761
- before do
762
- author_id = author.id
763
- add_post_resource(
764
- template_object: ActiveAdminImport::Model.new(author_id: author_id),
765
- before_batch_import: lambda do |importer|
766
- ip = importer.model.request_ip
767
- a_id = importer.model.author_id
768
- importer.csv_lines.map! { |row| row << ip << a_id }
769
- importer.headers.merge!(:'Request Ip' => :request_ip, :'Author Id' => :author_id)
770
- end,
771
- controller_block: proc do
772
- def active_admin_import_context
773
- { request_ip: request.remote_ip }
774
- end
775
- end
776
- )
777
- visit '/admin/posts/import'
778
- upload_file!(:posts_for_author)
779
- end
780
-
781
- it 'merges the context into the import model so callbacks see it' do
782
- expect(page).to have_content 'Successfully imported 2 posts'
783
- expect(Post.count).to eq(2)
784
- Post.all.each do |post|
785
- expect(post.request_ip).to eq('127.0.0.1')
786
- expect(post.author_id).to eq(author.id)
787
- end
788
- end
789
- end
790
-
791
- context 'when context returns parent id for a nested belongs_to resource' do
792
- let(:post) { Post.create!(title: 'A post', body: 'body', author: author) }
793
-
794
- before do
795
- add_nested_post_comment_resource(
796
- before_batch_import: lambda do |importer|
797
- importer.csv_lines.map! { |row| row << importer.model.post_id }
798
- importer.headers.merge!(:'Post Id' => :post_id)
799
- end,
800
- controller_block: proc do
801
- def active_admin_import_context
802
- { post_id: parent.id }
803
- end
804
- end
805
- )
806
- visit "/admin/posts/#{post.id}/post_comments/import"
807
- upload_file!(:post_comments)
808
- end
809
-
810
- it 'automatically assigns the parent post_id to every imported comment' do
811
- expect(page).to have_content 'Successfully imported 2 post comments'
812
- expect(PostComment.count).to eq(2)
813
- expect(PostComment.where(post_id: post.id).count).to eq(2)
814
- end
815
- end
816
- end
817
-
818
- # PG-only: activerecord-import populates `result.ids` reliably on PostgreSQL
819
- # via RETURNING. On MySQL/SQLite the array is not populated by default, so
820
- # the assertion would not be meaningful there. The :result_class option
821
- # itself works on every adapter — this spec just demonstrates the canonical
822
- # PR #191 use case (collecting inserted ids) on the adapter that supports it.
823
- if ENV['DB'] == 'postgres'
824
- context 'with custom result_class (PostgreSQL)' do
825
- # Subclass that captures inserted ids alongside the standard counters.
826
- # Lives in user-land so the gem itself stays free of adapter-specific
827
- # quirks around RETURNING. This is the example documented in the README.
828
- class ImportResultWithIds < ActiveAdminImport::ImportResult
829
- attr_reader :ids
830
-
831
- def initialize
832
- super
833
- @ids = []
834
- end
835
-
836
- def add(batch_result, qty)
837
- super
838
- @ids.concat(Array(batch_result.ids))
839
- end
840
- end
841
-
842
- before do
843
- add_author_resource(result_class: ImportResultWithIds) do |result, _options|
844
- # Expose the captured ids on the flash so the test asserts via the
845
- # rendered page rather than closure capture.
846
- flash[:notice] = "Imported ids: [#{result.ids.sort.join(',')}]"
847
- end
848
- visit '/admin/authors/import'
849
- upload_file!(:authors)
850
- end
851
-
852
- it 'collects the ids of inserted records via the custom subclass' do
853
- expect(Author.count).to eq(2)
854
- expected = "Imported ids: [#{Author.pluck(:id).sort.join(',')}]"
855
- expect(page).to have_content(expected)
856
- end
857
- end
858
- end
859
- end