active_admin_import 3.0.0.pre → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +43 -0
- data/.travis.yml +14 -6
- data/Gemfile +8 -8
- data/README.md +25 -33
- data/Rakefile +3 -2
- data/active_admin_import.gemspec +14 -17
- data/config/locales/fr.yml +23 -0
- data/config/locales/ja.yml +24 -0
- data/config/locales/ko.yml +23 -0
- data/config/locales/pt-BR.yml +23 -0
- data/config/locales/ru.yml +27 -0
- data/config/locales/uk.yml +27 -0
- data/lib/active_admin_import.rb +1 -2
- data/lib/active_admin_import/authorization.rb +1 -0
- data/lib/active_admin_import/dsl.rb +57 -41
- data/lib/active_admin_import/engine.rb +2 -3
- data/lib/active_admin_import/import_result.rb +9 -8
- data/lib/active_admin_import/importer.rb +18 -18
- data/lib/active_admin_import/model.rb +47 -36
- data/lib/active_admin_import/options.rb +34 -33
- data/lib/active_admin_import/version.rb +2 -1
- data/spec/fixtures/files/authors_with_tabs.tsv +3 -0
- data/spec/import_result_spec.rb +15 -9
- data/spec/import_spec.rb +148 -137
- data/spec/model_spec.rb +4 -3
- data/spec/spec_helper.rb +13 -25
- data/spec/support/active_model_lint.rb +5 -4
- data/spec/support/admin.rb +5 -9
- data/spec/support/rails_template.rb +10 -10
- data/tasks/test.rake +5 -4
- metadata +25 -24
- data/.hound.yml +0 -4
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ActiveAdminImport
|
2
3
|
class ImportResult
|
3
4
|
attr_reader :failed, :total
|
@@ -16,12 +17,12 @@ module ActiveAdminImport
|
|
16
17
|
total - failed.count
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
+
def imported?
|
20
21
|
imported_qty > 0
|
21
22
|
end
|
22
23
|
|
23
|
-
def
|
24
|
-
|
24
|
+
def failed?
|
25
|
+
failed.any?
|
25
26
|
end
|
26
27
|
|
27
28
|
def empty?
|
@@ -29,11 +30,11 @@ module ActiveAdminImport
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def failed_message(options = {})
|
32
|
-
limit = options
|
33
|
-
failed.first(limit).map
|
33
|
+
limit = options[:limit] || failed.count
|
34
|
+
failed.first(limit).map do |record|
|
34
35
|
errors = record.errors
|
35
|
-
(errors.full_messages.zip errors.keys.map{|k| record.send k}).map{|ms| ms.join(' - ')}.join(', ')
|
36
|
-
|
36
|
+
(errors.full_messages.zip errors.keys.map { |k| record.send k }).map { |ms| ms.join(' - ') }.join(', ')
|
37
|
+
end.join(' ; ')
|
37
38
|
end
|
38
39
|
end
|
39
|
-
end
|
40
|
+
end
|
@@ -1,23 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'csv'
|
2
3
|
module ActiveAdminImport
|
3
4
|
class Importer
|
4
|
-
|
5
5
|
attr_reader :resource, :options, :result, :model
|
6
6
|
attr_accessor :csv_lines, :headers
|
7
7
|
|
8
8
|
OPTIONS = [
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
:validate,
|
10
|
+
:on_duplicate_key_update,
|
11
|
+
:ignore,
|
12
|
+
:timestamps,
|
13
|
+
:before_import,
|
14
|
+
:after_import,
|
15
|
+
:before_batch_import,
|
16
|
+
:after_batch_import,
|
17
|
+
:headers_rewrites,
|
18
|
+
:batch_size,
|
19
|
+
:batch_transaction,
|
20
|
+
:csv_options
|
21
21
|
].freeze
|
22
22
|
|
23
23
|
def initialize(resource, model, options)
|
@@ -55,7 +55,7 @@ module ActiveAdminImport
|
|
55
55
|
index = header_index(header_key)
|
56
56
|
csv_lines.map! do |line|
|
57
57
|
from = line[index]
|
58
|
-
line[index] = options[from] if options.
|
58
|
+
line[index] = options[from] if options.key?(from)
|
59
59
|
line
|
60
60
|
end
|
61
61
|
end
|
@@ -71,7 +71,8 @@ module ActiveAdminImport
|
|
71
71
|
protected
|
72
72
|
|
73
73
|
def process_file
|
74
|
-
lines
|
74
|
+
lines = []
|
75
|
+
batch_size = options[:batch_size].to_i
|
75
76
|
File.open(file.path) do |f|
|
76
77
|
# capture headers if not exist
|
77
78
|
prepare_headers { CSV.parse(f.readline, @csv_options).first }
|
@@ -109,7 +110,7 @@ module ActiveAdminImport
|
|
109
110
|
end
|
110
111
|
|
111
112
|
def assign_options(options)
|
112
|
-
@options = {batch_size: 1000, validate: true}.merge(options.slice(*OPTIONS))
|
113
|
+
@options = { batch_size: 1000, validate: true }.merge(options.slice(*OPTIONS))
|
113
114
|
detect_csv_options
|
114
115
|
end
|
115
116
|
|
@@ -118,8 +119,7 @@ module ActiveAdminImport
|
|
118
119
|
model.csv_options
|
119
120
|
else
|
120
121
|
options[:csv_options] || {}
|
121
|
-
end.reject { |_, value| value.
|
122
|
+
end.reject { |_, value| value.nil? || value == "" }
|
122
123
|
end
|
123
|
-
|
124
124
|
end
|
125
125
|
end
|
@@ -1,17 +1,36 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'rchardet'
|
5
|
+
require 'zip'
|
4
6
|
|
5
7
|
module ActiveAdminImport
|
6
8
|
class Model
|
7
|
-
|
8
9
|
include ActiveModel::Model
|
9
10
|
include ActiveModel::Validations
|
10
11
|
include ActiveModel::Validations::Callbacks
|
11
12
|
|
13
|
+
module CONST
|
14
|
+
ZIP_TYPE = 'application/zip'.freeze
|
15
|
+
TMP_FILE = 'active-admin-import-unzipped'.freeze
|
16
|
+
CSV_TYPES = %w(
|
17
|
+
text/csv
|
18
|
+
text/x-csv
|
19
|
+
text/x-comma-separated-values
|
20
|
+
text/comma-separated-values
|
21
|
+
application/csv
|
22
|
+
application/vnd.ms-excel
|
23
|
+
application/vnd.msexcel
|
24
|
+
text/tsv
|
25
|
+
text/x-tsv
|
26
|
+
text/tab-separated-values
|
27
|
+
text/x-tab-separated-values
|
28
|
+
).freeze
|
29
|
+
end
|
30
|
+
|
12
31
|
validates :file, presence: {
|
13
|
-
message: ->(*_){ I18n.t(
|
14
|
-
}, unless: ->(me){ me.new_record? }
|
32
|
+
message: ->(*_) { I18n.t('active_admin_import.no_file_error') }
|
33
|
+
}, unless: ->(me) { me.new_record? }
|
15
34
|
|
16
35
|
validate :correct_content_type, if: ->(me) { me.file.present? }
|
17
36
|
validate :file_contents_present, if: ->(me) { me.file.present? }
|
@@ -21,7 +40,7 @@ module ActiveAdminImport
|
|
21
40
|
|
22
41
|
attr_reader :attributes
|
23
42
|
|
24
|
-
def initialize(args={})
|
43
|
+
def initialize(args = {})
|
25
44
|
@new_record = true
|
26
45
|
@attributes = {}
|
27
46
|
assign_attributes(default_attributes.merge(args), true)
|
@@ -44,21 +63,21 @@ module ActiveAdminImport
|
|
44
63
|
allow_archive: true,
|
45
64
|
csv_headers: [],
|
46
65
|
file: nil,
|
47
|
-
force_encoding:
|
48
|
-
hint:
|
66
|
+
force_encoding: 'UTF-8',
|
67
|
+
hint: ''
|
49
68
|
}
|
50
69
|
end
|
51
70
|
|
52
71
|
def allow_archive?
|
53
|
-
|
72
|
+
attributes[:allow_archive].present?
|
54
73
|
end
|
55
74
|
|
56
75
|
def new_record?
|
57
|
-
|
76
|
+
@new_record.present?
|
58
77
|
end
|
59
78
|
|
60
79
|
def force_encoding?
|
61
|
-
|
80
|
+
attributes[:force_encoding].present?
|
62
81
|
end
|
63
82
|
|
64
83
|
def persisted?
|
@@ -66,10 +85,10 @@ module ActiveAdminImport
|
|
66
85
|
end
|
67
86
|
|
68
87
|
def archive?
|
69
|
-
file_type ==
|
88
|
+
file_type == CONST::ZIP_TYPE
|
70
89
|
end
|
71
90
|
|
72
|
-
alias
|
91
|
+
alias to_hash attributes
|
73
92
|
|
74
93
|
protected
|
75
94
|
|
@@ -90,32 +109,27 @@ module ActiveAdminImport
|
|
90
109
|
|
91
110
|
def unzip_file
|
92
111
|
Zip::File.open(file_path) do |zip_file|
|
93
|
-
self.file = Tempfile.new(
|
94
|
-
data = zip_file.entries.select
|
95
|
-
|
96
|
-
|
112
|
+
self.file = Tempfile.new(CONST::TMP_FILE)
|
113
|
+
data = zip_file.entries.select(&:file?).first.get_input_stream.read
|
114
|
+
file << data
|
115
|
+
file.close
|
97
116
|
end
|
98
117
|
end
|
99
118
|
|
100
119
|
def csv_allowed_types
|
101
|
-
|
102
|
-
'text/csv',
|
103
|
-
'text/x-csv',
|
104
|
-
'text/comma-separated-values',
|
105
|
-
'application/csv',
|
106
|
-
'application/vnd.ms-excel',
|
107
|
-
'application/vnd.msexcel'
|
108
|
-
]
|
120
|
+
CONST::CSV_TYPES
|
109
121
|
end
|
110
122
|
|
111
123
|
def correct_content_type
|
112
|
-
|
113
|
-
|
114
|
-
|
124
|
+
return if file.blank? ||
|
125
|
+
file.is_a?(Tempfile) ||
|
126
|
+
(csv_allowed_types.include? file_type)
|
127
|
+
errors.add(:file, I18n.t('active_admin_import.file_format_error'))
|
115
128
|
end
|
116
129
|
|
117
130
|
def file_contents_present
|
118
|
-
|
131
|
+
return unless File.zero?(file_path)
|
132
|
+
errors.add(:file, I18n.t('active_admin_import.file_empty_error'))
|
119
133
|
end
|
120
134
|
|
121
135
|
def file_type
|
@@ -126,10 +140,8 @@ module ActiveAdminImport
|
|
126
140
|
end
|
127
141
|
end
|
128
142
|
|
129
|
-
protected
|
130
|
-
|
131
143
|
def define_methods_for(attr_name)
|
132
|
-
#generate methods for instance object by attributes
|
144
|
+
# generate methods for instance object by attributes
|
133
145
|
singleton_class.class_eval do
|
134
146
|
define_set_method(attr_name)
|
135
147
|
define_get_method(attr_name)
|
@@ -143,7 +155,7 @@ module ActiveAdminImport
|
|
143
155
|
invalid: :replace, undef: :replace, universal_newline: true
|
144
156
|
)
|
145
157
|
begin
|
146
|
-
data.sub("\xEF\xBB\xBF", '')
|
158
|
+
data.sub("\xEF\xBB\xBF", '') # bom
|
147
159
|
rescue StandardError => _
|
148
160
|
data
|
149
161
|
end
|
@@ -168,15 +180,14 @@ module ActiveAdminImport
|
|
168
180
|
|
169
181
|
class <<self
|
170
182
|
def define_set_method(attr_name)
|
171
|
-
|
183
|
+
return if method_defined? attr_name
|
184
|
+
define_method(attr_name) { attributes[attr_name] }
|
172
185
|
end
|
173
186
|
|
174
187
|
def define_get_method(attr_name)
|
175
|
-
|
188
|
+
return if method_defined? "#{attr_name}="
|
189
|
+
define_method("#{attr_name}=") { |new_value| @attributes[attr_name] = new_value }
|
176
190
|
end
|
177
191
|
end
|
178
|
-
|
179
192
|
end
|
180
193
|
end
|
181
|
-
|
182
|
-
|
@@ -1,44 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ActiveAdminImport
|
2
|
-
|
3
3
|
module Options
|
4
4
|
VALID_OPTIONS = [
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
:back,
|
6
|
+
:csv_options,
|
7
|
+
:validate,
|
8
|
+
:batch_size,
|
9
|
+
:batch_transaction,
|
10
|
+
:before_import,
|
11
|
+
:after_import,
|
12
|
+
:before_batch_import,
|
13
|
+
:after_batch_import,
|
14
|
+
:on_duplicate_key_update,
|
15
|
+
:timestamps,
|
16
|
+
:ignore,
|
17
|
+
:template,
|
18
|
+
:template_object,
|
19
|
+
:resource_class,
|
20
|
+
:resource_label,
|
21
|
+
:plural_resource_label,
|
22
|
+
:error_limit,
|
23
|
+
:headers_rewrites,
|
24
|
+
:if
|
23
25
|
].freeze
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
def self.options_for(config, options = {})
|
28
|
+
unless options.key? :template_object
|
29
|
+
options[:template_object] = ActiveAdminImport::Model.new
|
30
|
+
end
|
28
31
|
|
29
32
|
{
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
back: { action: :import },
|
34
|
+
csv_options: {},
|
35
|
+
template: 'admin/import',
|
36
|
+
resource_class: config.resource_class,
|
37
|
+
resource_label: config.resource_label,
|
38
|
+
plural_resource_label: config.plural_resource_label,
|
39
|
+
error_limit: 5,
|
40
|
+
headers_rewrites: {},
|
41
|
+
if: true
|
37
42
|
}.deep_merge(options)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
43
|
end
|
42
|
-
|
43
44
|
end
|
44
45
|
end
|
data/spec/import_result_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
4
|
describe ActiveAdminImport::ImportResult do
|
4
|
-
context
|
5
|
+
context 'failed_message' do
|
5
6
|
let(:import_result) { ActiveAdminImport::ImportResult.new }
|
6
7
|
|
7
8
|
before do
|
@@ -10,23 +11,28 @@ describe ActiveAdminImport::ImportResult do
|
|
10
11
|
|
11
12
|
@result = double \
|
12
13
|
failed_instances: [
|
13
|
-
|
14
|
-
Author.create(name:
|
14
|
+
# {:last_name=>["has already been taken"]}
|
15
|
+
Author.create(name: 'Jim', last_name: 'Doe'),
|
16
|
+
# {:name=>["can't be blank"], :last_name=>["has already been taken"]}
|
17
|
+
Author.create(name: nil, last_name: 'Doe')
|
15
18
|
]
|
16
19
|
end
|
17
20
|
|
18
|
-
it
|
19
|
-
expect(import_result.failed_message).to eq(
|
21
|
+
it 'should work without any failed instances' do
|
22
|
+
expect(import_result.failed_message).to eq('')
|
20
23
|
end
|
21
24
|
|
22
|
-
it
|
25
|
+
it 'should work' do
|
23
26
|
import_result.add(@result, 4)
|
24
|
-
expect(import_result.failed_message)
|
27
|
+
expect(import_result.failed_message)
|
28
|
+
.to eq(
|
29
|
+
"Last name has already been taken - Doe ; Name can't be blank - , Last name has already been taken - Doe"
|
30
|
+
)
|
25
31
|
end
|
26
32
|
|
27
|
-
it
|
33
|
+
it 'should work on limit param' do
|
28
34
|
import_result.add(@result, 4)
|
29
|
-
expect(import_result.failed_message(limit: 1)).to eq(
|
35
|
+
expect(import_result.failed_message(limit: 1)).to eq('Last name has already been taken - Doe')
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
data/spec/import_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
4
|
describe 'import', type: :feature do
|
4
|
-
|
5
5
|
shared_examples 'successful inserts' do |encoding, csv_file_name|
|
6
6
|
let(:options) do
|
7
7
|
attributes = { force_encoding: encoding }
|
@@ -12,8 +12,8 @@ describe 'import', type: :feature do
|
|
12
12
|
upload_file!(csv_file_name)
|
13
13
|
end
|
14
14
|
|
15
|
-
it
|
16
|
-
expect(page).to have_content
|
15
|
+
it 'should import file with many records' do
|
16
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
17
17
|
expect(Author.count).to eq(2)
|
18
18
|
Author.all.each do |author|
|
19
19
|
expect(author).to be_valid
|
@@ -24,7 +24,6 @@ describe 'import', type: :feature do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def with_zipped_csv(name, &block)
|
27
|
-
|
28
27
|
zip_file = File.expand_path("./spec/fixtures/files/#{name}.zip")
|
29
28
|
|
30
29
|
begin
|
@@ -33,115 +32,119 @@ describe 'import', type: :feature do
|
|
33
32
|
end
|
34
33
|
instance_eval &block
|
35
34
|
ensure
|
36
|
-
|
35
|
+
begin
|
36
|
+
File.delete zip_file
|
37
|
+
rescue
|
38
|
+
nil
|
39
|
+
end
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
40
|
-
def upload_file!(name, ext='csv')
|
43
|
+
def upload_file!(name, ext = 'csv')
|
41
44
|
attach_file('active_admin_import_model_file', File.expand_path("./spec/fixtures/files/#{name}.#{ext}"))
|
42
45
|
find_button('Import').click
|
43
46
|
end
|
44
47
|
|
45
|
-
context
|
48
|
+
context 'posts index' do
|
46
49
|
before do
|
47
|
-
Author.create!(name:
|
48
|
-
Author.create!(name:
|
50
|
+
Author.create!(name: 'John', last_name: 'Doe')
|
51
|
+
Author.create!(name: 'Jane', last_name: 'Roe')
|
49
52
|
end
|
50
53
|
|
51
|
-
context
|
54
|
+
context 'for csv for particular author' do
|
52
55
|
let(:author) { Author.take }
|
53
56
|
|
54
57
|
shared_examples 'successful inserts for author' do
|
55
|
-
it
|
58
|
+
it 'should use predefined author_id' do
|
56
59
|
expect(Post.where(author_id: author.id).count).to eq(Post.count)
|
57
60
|
end
|
58
61
|
|
59
|
-
it
|
62
|
+
it 'should be imported' do
|
60
63
|
expect(Post.count).to eq(2)
|
61
|
-
expect(page).to have_content
|
64
|
+
expect(page).to have_content 'Successfully imported 2 posts'
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
context
|
68
|
+
context 'no headers' do
|
66
69
|
before do
|
67
70
|
add_post_resource(template_object: ActiveAdminImport::Model.new(author_id: author.id,
|
68
71
|
csv_headers: [:title, :body, :author_id]),
|
69
72
|
validate: true,
|
70
|
-
before_batch_import:
|
73
|
+
before_batch_import: lambda do |importer|
|
71
74
|
importer.csv_lines.map! { |row| row << importer.model.author_id }
|
72
75
|
end
|
73
|
-
|
76
|
+
)
|
74
77
|
|
75
|
-
visit
|
78
|
+
visit '/admin/posts/import'
|
76
79
|
upload_file!(:posts_for_author_no_headers)
|
77
80
|
end
|
78
81
|
include_examples 'successful inserts for author'
|
79
82
|
end
|
80
83
|
|
81
|
-
context
|
84
|
+
context 'with headers' do
|
82
85
|
before do
|
83
86
|
add_post_resource(template_object: ActiveAdminImport::Model.new(author_id: author.id),
|
84
87
|
validate: true,
|
85
|
-
before_batch_import:
|
88
|
+
before_batch_import: lambda do |importer|
|
86
89
|
importer.csv_lines.map! { |row| row << importer.model.author_id }
|
87
|
-
importer.headers.merge!(
|
90
|
+
importer.headers.merge!(:'Author Id' => :author_id)
|
88
91
|
end
|
89
|
-
|
92
|
+
)
|
90
93
|
|
91
|
-
visit
|
94
|
+
visit '/admin/posts/import'
|
92
95
|
upload_file!(:posts_for_author)
|
93
96
|
end
|
94
97
|
include_examples 'successful inserts for author'
|
95
98
|
end
|
96
99
|
end
|
97
100
|
|
98
|
-
context
|
101
|
+
context 'for csv with author name' do
|
99
102
|
before do
|
100
103
|
add_post_resource(
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
104
|
+
validate: true,
|
105
|
+
template_object: ActiveAdminImport::Model.new,
|
106
|
+
headers_rewrites: { :'Author Name' => :author_id },
|
107
|
+
before_batch_import: lambda do |importer|
|
108
|
+
authors_names = importer.values_at(:author_id)
|
109
|
+
# replacing author name with author id
|
110
|
+
authors = Author.where(name: authors_names).pluck(:name, :id)
|
111
|
+
# {"Jane" => 2, "John" => 1}
|
112
|
+
options = Hash[*authors.flatten]
|
113
|
+
importer.batch_replace(:author_id, options)
|
114
|
+
end
|
112
115
|
)
|
113
|
-
visit
|
116
|
+
visit '/admin/posts/import'
|
114
117
|
upload_file!(:posts)
|
115
118
|
end
|
116
119
|
|
117
|
-
it
|
120
|
+
it 'should resolve author_id by author name' do
|
118
121
|
Post.all.each do |post|
|
119
122
|
expect(Author.where(id: post.author.id)).to be_present
|
120
123
|
end
|
121
124
|
end
|
122
125
|
|
123
|
-
it
|
126
|
+
it 'should be imported' do
|
124
127
|
expect(Post.count).to eq(2)
|
125
|
-
expect(page).to have_content
|
128
|
+
expect(page).to have_content 'Successfully imported 2 posts'
|
126
129
|
end
|
127
130
|
end
|
128
131
|
end
|
129
132
|
|
130
|
-
context
|
133
|
+
context 'authors index' do
|
131
134
|
before do
|
132
135
|
add_author_resource
|
133
136
|
end
|
134
137
|
|
135
|
-
it
|
136
|
-
#
|
138
|
+
it 'should navigate to import page' do
|
139
|
+
# TODO: removing this causes undefined method `ransack' for #<ActiveRecord::Relation []>
|
137
140
|
allow_any_instance_of(Admin::AuthorsController).to receive(:find_collection).and_return(Author.all)
|
138
141
|
visit '/admin/authors'
|
139
142
|
find_link('Import Authors').click
|
140
|
-
expect(current_path).to eq(
|
143
|
+
expect(current_path).to eq('/admin/authors/import')
|
141
144
|
end
|
142
145
|
end
|
143
146
|
|
144
|
-
context
|
147
|
+
context 'with custom block' do
|
145
148
|
before do
|
146
149
|
add_author_resource({}) do
|
147
150
|
flash[:notice] = 'some custom message'
|
@@ -149,44 +152,42 @@ describe 'import', type: :feature do
|
|
149
152
|
visit '/admin/authors/import'
|
150
153
|
end
|
151
154
|
|
152
|
-
it
|
155
|
+
it 'should display notice from custom block' do
|
153
156
|
upload_file!(:author)
|
154
|
-
expect(page).to have_content
|
157
|
+
expect(page).to have_content 'some custom message'
|
155
158
|
end
|
156
|
-
|
157
159
|
end
|
158
160
|
|
159
|
-
context
|
161
|
+
context 'authors already exist' do
|
160
162
|
before do
|
161
|
-
Author.create!(id: 1, name:
|
162
|
-
Author.create!(id: 2, name:
|
163
|
+
Author.create!(id: 1, name: 'Jane', last_name: 'Roe')
|
164
|
+
Author.create!(id: 2, name: 'John', last_name: 'Doe')
|
163
165
|
end
|
164
166
|
|
165
|
-
context
|
167
|
+
context 'having authors with the same Id' do
|
166
168
|
before do
|
167
169
|
add_author_resource(
|
168
|
-
|
169
|
-
|
170
|
-
|
170
|
+
before_batch_import: lambda do |importer|
|
171
|
+
Author.where(id: importer.values_at('id')).delete_all
|
172
|
+
end
|
171
173
|
)
|
172
|
-
visit
|
174
|
+
visit '/admin/authors/import'
|
173
175
|
upload_file!(:authors_with_ids)
|
174
176
|
end
|
175
177
|
|
176
|
-
it
|
177
|
-
expect(page).to have_content
|
178
|
+
it 'should replace authors' do
|
179
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
178
180
|
expect(Author.count).to eq(2)
|
179
181
|
end
|
180
182
|
|
181
|
-
it
|
182
|
-
expect(Author.find(1).name).to eq(
|
183
|
-
expect(Author.find(2).name).to eq(
|
183
|
+
it 'should replace authors by id' do
|
184
|
+
expect(Author.find(1).name).to eq('John')
|
185
|
+
expect(Author.find(2).name).to eq('Jane')
|
184
186
|
end
|
185
187
|
end
|
186
188
|
end
|
187
189
|
|
188
|
-
context
|
189
|
-
|
190
|
+
context 'with valid options' do
|
190
191
|
let(:options) { {} }
|
191
192
|
|
192
193
|
before do
|
@@ -194,32 +195,31 @@ describe 'import', type: :feature do
|
|
194
195
|
visit '/admin/authors/import'
|
195
196
|
end
|
196
197
|
|
197
|
-
it
|
198
|
+
it 'has valid form' do
|
198
199
|
form = find('#new_active_admin_import_model')
|
199
|
-
expect(form['action']).to eq(
|
200
|
-
expect(form['enctype']).to eq(
|
201
|
-
file_input = form.find(
|
202
|
-
expect(file_input[:type]).to eq(
|
200
|
+
expect(form['action']).to eq('/admin/authors/do_import')
|
201
|
+
expect(form['enctype']).to eq('multipart/form-data')
|
202
|
+
file_input = form.find('input#active_admin_import_model_file')
|
203
|
+
expect(file_input[:type]).to eq('file')
|
203
204
|
expect(file_input.value).to be_blank
|
204
|
-
submit_input = form.find(
|
205
|
-
expect(submit_input[:value]).to eq(
|
206
|
-
expect(submit_input[:type]).to eq(
|
205
|
+
submit_input = form.find('#active_admin_import_model_submit_action input')
|
206
|
+
expect(submit_input[:value]).to eq('Import')
|
207
|
+
expect(submit_input[:type]).to eq('submit')
|
207
208
|
end
|
208
209
|
|
209
|
-
context
|
210
|
+
context 'with hint defined' do
|
210
211
|
let(:options) do
|
211
|
-
{ template_object: ActiveAdminImport::Model.new(hint:
|
212
|
+
{ template_object: ActiveAdminImport::Model.new(hint: 'hint') }
|
212
213
|
end
|
213
|
-
it
|
214
|
+
it 'renders hint at upload page' do
|
214
215
|
expect(page).to have_content options[:template_object].hint
|
215
216
|
end
|
216
217
|
end
|
217
218
|
|
218
|
-
context
|
219
|
-
|
219
|
+
context 'when importing file' do
|
220
220
|
[:empty, :only_headers].each do |file|
|
221
221
|
context "when #{file} file" do
|
222
|
-
it
|
222
|
+
it 'should render warning' do
|
223
223
|
upload_file!(file)
|
224
224
|
expect(page).to have_content I18n.t('active_admin_import.file_empty_error')
|
225
225
|
expect(Author.count).to eq(0)
|
@@ -227,142 +227,143 @@ describe 'import', type: :feature do
|
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
230
|
-
context
|
231
|
-
it
|
230
|
+
context 'when no file' do
|
231
|
+
it 'should render error' do
|
232
232
|
find_button('Import').click
|
233
233
|
expect(Author.count).to eq(0)
|
234
234
|
expect(page).to have_content I18n.t('active_admin_import.no_file_error')
|
235
235
|
end
|
236
236
|
end
|
237
237
|
|
238
|
-
context
|
238
|
+
context 'auto detect encoding' do
|
239
239
|
include_examples 'successful inserts',
|
240
240
|
:auto,
|
241
241
|
:authors_win1251_win_endline
|
242
242
|
end
|
243
243
|
|
244
|
-
context
|
244
|
+
context 'Win1251' do
|
245
245
|
include_examples 'successful inserts',
|
246
246
|
'windows-1251',
|
247
247
|
:authors_win1251_win_endline
|
248
248
|
end
|
249
249
|
|
250
|
-
context
|
251
|
-
it
|
250
|
+
context 'BOM' do
|
251
|
+
it 'should import file with many records' do
|
252
252
|
upload_file!(:authors_bom)
|
253
|
-
expect(page).to have_content
|
253
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
254
254
|
expect(Author.count).to eq(2)
|
255
255
|
end
|
256
256
|
end
|
257
257
|
|
258
|
-
context
|
259
|
-
it
|
258
|
+
context 'with headers' do
|
259
|
+
it 'should import file with many records' do
|
260
260
|
upload_file!(:authors)
|
261
|
-
expect(page).to have_content
|
261
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
262
262
|
expect(Author.count).to eq(2)
|
263
263
|
end
|
264
264
|
|
265
|
-
it
|
265
|
+
it 'should import file with 1 record' do
|
266
266
|
upload_file!(:author)
|
267
|
-
expect(page).to have_content
|
267
|
+
expect(page).to have_content 'Successfully imported 1 author'
|
268
268
|
expect(Author.count).to eq(1)
|
269
269
|
end
|
270
270
|
end
|
271
271
|
|
272
|
-
context
|
273
|
-
context
|
272
|
+
context 'without headers' do
|
273
|
+
context 'with known csv headers' do
|
274
274
|
let(:options) do
|
275
275
|
attributes = { csv_headers: ['Name', 'Last name', 'Birthday'] }
|
276
276
|
{ template_object: ActiveAdminImport::Model.new(attributes) }
|
277
277
|
end
|
278
278
|
|
279
|
-
it
|
279
|
+
it 'should import file' do
|
280
280
|
upload_file!(:authors_no_headers)
|
281
|
-
expect(page).to have_content
|
281
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
282
282
|
expect(Author.count).to eq(2)
|
283
283
|
end
|
284
284
|
end
|
285
285
|
|
286
|
-
context
|
287
|
-
it
|
286
|
+
context 'with unknown csv headers' do
|
287
|
+
it 'should render error' do
|
288
288
|
upload_file!(:authors_no_headers)
|
289
|
-
expect(page).to have_content
|
289
|
+
expect(page).to have_content 'Error:'
|
290
290
|
expect(Author.count).to eq(0)
|
291
291
|
end
|
292
292
|
end
|
293
293
|
end
|
294
294
|
|
295
|
-
context
|
295
|
+
context 'with invalid data insert on DB constraint' do
|
296
296
|
# :name field has an uniq index
|
297
|
-
it
|
297
|
+
it 'should render error' do
|
298
298
|
upload_file!(:authors_invalid_db)
|
299
|
-
expect(page).to have_content
|
299
|
+
expect(page).to have_content 'Error:'
|
300
300
|
expect(Author.count).to eq(0)
|
301
301
|
end
|
302
302
|
end
|
303
303
|
|
304
|
-
context
|
304
|
+
context 'with invalid data insert on model validation' do
|
305
305
|
let(:options) { { validate: true } }
|
306
306
|
|
307
307
|
before do
|
308
|
-
Author.create!(name:
|
308
|
+
Author.create!(name: 'John', last_name: 'Doe')
|
309
309
|
end
|
310
310
|
|
311
|
-
it
|
311
|
+
it 'should render both successful and failed message' do
|
312
312
|
upload_file!(:authors_invalid_model)
|
313
|
-
expect(page).to have_content
|
314
|
-
expect(page).to have_content
|
313
|
+
expect(page).to have_content 'Failed to import 1 author'
|
314
|
+
expect(page).to have_content 'Successfully imported 1 author'
|
315
|
+
expect(page).to have_content 'Last name has already been taken - Doe'
|
315
316
|
expect(Author.count).to eq(2)
|
316
317
|
end
|
317
318
|
|
318
|
-
context
|
319
|
+
context 'use batch_transaction to make transaction work on model validation' do
|
319
320
|
let(:options) { { validate: true, batch_transaction: true } }
|
320
321
|
|
321
|
-
it
|
322
|
+
it 'should render only the failed message' do
|
322
323
|
upload_file!(:authors_invalid_model)
|
323
|
-
expect(page).to have_content
|
324
|
-
expect(page).to_not have_content
|
324
|
+
expect(page).to have_content 'Failed to import 1 author'
|
325
|
+
expect(page).to_not have_content 'Successfully imported'
|
325
326
|
expect(Author.count).to eq(1)
|
326
327
|
end
|
327
328
|
end
|
328
329
|
end
|
329
330
|
|
330
|
-
context
|
331
|
-
context
|
332
|
-
it
|
331
|
+
context 'with invalid records' do
|
332
|
+
context 'with validation' do
|
333
|
+
it 'should render error' do
|
333
334
|
upload_file!(:author_invalid)
|
334
|
-
expect(page).to have_content
|
335
|
+
expect(page).to have_content 'Failed to import 1 author'
|
335
336
|
expect(Author.count).to eq(0)
|
336
337
|
end
|
337
338
|
end
|
338
339
|
|
339
|
-
context
|
340
|
+
context 'without validation' do
|
340
341
|
let(:options) { { validate: false } }
|
341
|
-
it
|
342
|
+
it 'should render error' do
|
342
343
|
upload_file!(:author_invalid)
|
343
|
-
expect(page).to have_content
|
344
|
+
expect(page).to have_content 'Successfully imported 1 author'
|
344
345
|
expect(Author.count).to eq(1)
|
345
346
|
end
|
346
347
|
end
|
347
348
|
end
|
348
349
|
|
349
|
-
context
|
350
|
-
context
|
351
|
-
it
|
350
|
+
context 'when zipped' do
|
351
|
+
context 'when allowed' do
|
352
|
+
it 'should import file' do
|
352
353
|
with_zipped_csv(:authors) do
|
353
354
|
upload_file!(:authors, :zip)
|
354
|
-
expect(page).to have_content
|
355
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
355
356
|
expect(Author.count).to eq(2)
|
356
357
|
end
|
357
358
|
end
|
358
359
|
end
|
359
360
|
|
360
|
-
context
|
361
|
+
context 'when not allowed' do
|
361
362
|
let(:options) do
|
362
363
|
attributes = { allow_archive: false }
|
363
364
|
{ template_object: ActiveAdminImport::Model.new(attributes) }
|
364
365
|
end
|
365
|
-
it
|
366
|
+
it 'should render error' do
|
366
367
|
with_zipped_csv(:authors) do
|
367
368
|
upload_file!(:authors, :zip)
|
368
369
|
expect(page).to have_content I18n.t('active_admin_import.file_format_error')
|
@@ -372,43 +373,56 @@ describe 'import', type: :feature do
|
|
372
373
|
end
|
373
374
|
end
|
374
375
|
|
375
|
-
context
|
376
|
+
context 'with different header attribute names' do
|
376
377
|
let(:options) do
|
377
378
|
{ headers_rewrites: { :'Second name' => :last_name } }
|
378
379
|
end
|
379
380
|
|
380
|
-
it
|
381
|
+
it 'should import file' do
|
381
382
|
upload_file!(:author_broken_header)
|
382
|
-
expect(page).to have_content
|
383
|
+
expect(page).to have_content 'Successfully imported 1 author'
|
383
384
|
expect(Author.count).to eq(1)
|
384
385
|
end
|
385
386
|
end
|
386
387
|
|
387
|
-
context
|
388
|
+
context 'with semicolons separator' do
|
388
389
|
let(:options) do
|
389
|
-
attributes = { csv_options: { col_sep:
|
390
|
+
attributes = { csv_options: { col_sep: ';' } }
|
390
391
|
{ template_object: ActiveAdminImport::Model.new(attributes) }
|
391
392
|
end
|
392
393
|
|
393
|
-
it
|
394
|
+
it 'should import file' do
|
394
395
|
upload_file!(:authors_with_semicolons)
|
395
|
-
expect(page).to have_content
|
396
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
397
|
+
expect(Author.count).to eq(2)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context 'with tab separator' do
|
402
|
+
let(:options) do
|
403
|
+
attributes = { csv_options: { col_sep: "\t" } }
|
404
|
+
{ template_object: ActiveAdminImport::Model.new(attributes) }
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'should import file' do
|
408
|
+
upload_file!(:authors_with_tabs, 'tsv')
|
409
|
+
expect(page).to have_content 'Successfully imported 2 authors'
|
396
410
|
expect(Author.count).to eq(2)
|
397
411
|
end
|
398
412
|
end
|
399
413
|
end
|
400
414
|
|
401
|
-
context
|
415
|
+
context 'with callback procs options' do
|
402
416
|
let(:options) do
|
403
417
|
{
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
418
|
+
before_import: ->(_) { true },
|
419
|
+
after_import: ->(_) { true },
|
420
|
+
before_batch_import: ->(_) { true },
|
421
|
+
after_batch_import: ->(_) { true }
|
408
422
|
}
|
409
423
|
end
|
410
424
|
|
411
|
-
it
|
425
|
+
it 'should call each callback' do
|
412
426
|
expect(options[:before_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
|
413
427
|
expect(options[:after_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
|
414
428
|
expect(options[:before_batch_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer))
|
@@ -417,16 +431,13 @@ describe 'import', type: :feature do
|
|
417
431
|
expect(Author.count).to eq(2)
|
418
432
|
end
|
419
433
|
end
|
420
|
-
|
421
434
|
end
|
422
435
|
|
423
|
-
context
|
436
|
+
context 'with invalid options' do
|
424
437
|
let(:options) { { invalid_option: :invalid_value } }
|
425
438
|
|
426
|
-
it
|
439
|
+
it 'should raise TypeError' do
|
427
440
|
expect { add_author_resource(options) }.to raise_error(ArgumentError)
|
428
441
|
end
|
429
|
-
|
430
442
|
end
|
431
|
-
|
432
443
|
end
|