active_admin_import 5.0.0 → 6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba919b0429b44accf46af4dcdc52ef1ddcf691a446cddf1bc434b38982c6b0c2
4
- data.tar.gz: b54c35f3d81a3b170a8a27b657f1c0b8137c49716fab6b1ba0717845f1b15649
3
+ metadata.gz: bdc784c6f8a845b491b0dc93e44b073b548bccc182bb3cf6c26cb9f80e59b587
4
+ data.tar.gz: 798237101d7d4403ed8fb1d1d4ed466b4b78bc44740e20137887d2c8b6464e3f
5
5
  SHA512:
6
- metadata.gz: d949ae03350ff838eb97856d4c34afb610708dde36bee45d32b56de0bd1bf48a5e0adb4e67aaa549e1e35393cca146be755258fbc213422ab9a91b299769688e
7
- data.tar.gz: 4ddc52d5273f06837f54a3806e56ebdad52c2a60581aa452b196de25885697348b13af8f56e27716ed2234b3771072f3f93e365035cdfc02b1539e7aa8385fb1
6
+ metadata.gz: '06639b5514e0c5af1f2cfd70f66f7de10b798fc22f9153b4870c0859b3f95fd372cf73f406b15b7df8b3e4d6fc350a911680248e09d15412479c55f092869a0d'
7
+ data.tar.gz: 1b49c36c911a6eae50d653eafa4063a139b5d901b81264b213ece4bb995158bf7685f62facce2d5d1a69b76fe2735483296a26f9f697f6fad802d562ca3deef5
@@ -0,0 +1,162 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [master]
6
+
7
+ permissions:
8
+ contents: read
9
+ pages: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ test:
14
+ name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }} / SQLite
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ ruby: ['3.2', '3.3', '3.4']
20
+ rails: ['7.1.0', '7.2.0', '8.0.0']
21
+ activeadmin: ['3.2.0', '3.3.0', '3.4.0', '3.5.1']
22
+ exclude:
23
+ - rails: '8.0.0'
24
+ activeadmin: '3.2.0'
25
+ env:
26
+ RAILS: ${{ matrix.rails }}
27
+ AA: ${{ matrix.activeadmin }}
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: ruby/setup-ruby@v1
31
+ with:
32
+ ruby-version: ${{ matrix.ruby }}
33
+ bundler-cache: true
34
+ - name: Run tests
35
+ run: bundle exec rspec spec
36
+ test-mysql:
37
+ name: Ruby 3.4 / Rails 8.0.0 / AA 3.5.1 / MySQL 8.0
38
+ runs-on: ubuntu-latest
39
+ env:
40
+ RAILS: '8.0.0'
41
+ AA: '3.5.1'
42
+ DB: mysql
43
+ DB_HOST: 127.0.0.1
44
+ DB_PORT: 3306
45
+ DB_USERNAME: root
46
+ DB_PASSWORD: root
47
+ services:
48
+ mysql:
49
+ image: mysql:8.0
50
+ env:
51
+ MYSQL_ROOT_PASSWORD: root
52
+ MYSQL_DATABASE: active_admin_import_test
53
+ ports:
54
+ - 3306:3306
55
+ options: >-
56
+ --health-cmd="mysqladmin ping -h localhost -uroot -proot"
57
+ --health-interval=10s
58
+ --health-timeout=5s
59
+ --health-retries=10
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+ - uses: ruby/setup-ruby@v1
63
+ with:
64
+ ruby-version: '3.4'
65
+ bundler-cache: true
66
+ - name: Run tests
67
+ run: bundle exec rspec spec
68
+ test-postgres:
69
+ name: Ruby 3.4 / Rails 8.0.0 / AA 3.5.1 / PostgreSQL 16
70
+ runs-on: ubuntu-latest
71
+ env:
72
+ RAILS: '8.0.0'
73
+ AA: '3.5.1'
74
+ DB: postgres
75
+ DB_HOST: 127.0.0.1
76
+ DB_PORT: 5432
77
+ DB_USERNAME: postgres
78
+ DB_PASSWORD: postgres
79
+ services:
80
+ postgres:
81
+ image: postgres:16
82
+ env:
83
+ POSTGRES_USER: postgres
84
+ POSTGRES_PASSWORD: postgres
85
+ POSTGRES_DB: active_admin_import_test
86
+ ports:
87
+ - 5432:5432
88
+ options: >-
89
+ --health-cmd="pg_isready -U postgres"
90
+ --health-interval=10s
91
+ --health-timeout=5s
92
+ --health-retries=10
93
+ steps:
94
+ - uses: actions/checkout@v4
95
+ - uses: ruby/setup-ruby@v1
96
+ with:
97
+ ruby-version: '3.4'
98
+ bundler-cache: true
99
+ - name: Run tests
100
+ run: bundle exec rspec spec
101
+ coverage:
102
+ name: Coverage
103
+ runs-on: ubuntu-latest
104
+ steps:
105
+ - uses: actions/checkout@v4
106
+ - uses: ruby/setup-ruby@v1
107
+ with:
108
+ ruby-version: '3.4'
109
+ bundler-cache: true
110
+ - name: Run tests with coverage
111
+ run: bundle exec rspec spec
112
+ - name: Upload coverage
113
+ uses: actions/upload-artifact@v4
114
+ with:
115
+ name: coverage
116
+ path: coverage/
117
+
118
+ - name: Generate badge.json
119
+ run: |
120
+ LAST_RUN="coverage/.last_run.json"
121
+ if [ ! -f "$LAST_RUN" ]; then
122
+ mkdir -p badge
123
+ echo '{"schemaVersion":1,"label":"coverage","message":"unknown","color":"lightgrey"}' > badge/badge.json
124
+ exit 0
125
+ fi
126
+ PERCENT=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line').round(1)")
127
+ PERCENT_NUM=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line')")
128
+ if ruby -e "exit(($PERCENT_NUM >= 90) ? 0 : 1)"; then COLOR="brightgreen"
129
+ elif ruby -e "exit(($PERCENT_NUM >= 75) ? 0 : 1)"; then COLOR="green"
130
+ elif ruby -e "exit(($PERCENT_NUM >= 60) ? 0 : 1)"; then COLOR="yellow"
131
+ else COLOR="red"; fi
132
+ mkdir -p badge
133
+ echo "{\"schemaVersion\":1,\"label\":\"coverage\",\"message\":\"${PERCENT}%\",\"color\":\"${COLOR}\"}" > badge/badge.json
134
+
135
+ - name: Upload badge artifact
136
+ uses: actions/upload-artifact@v4
137
+ with:
138
+ name: coverage-badge
139
+ path: badge
140
+
141
+ deploy-coverage:
142
+ needs: coverage
143
+ if: github.ref == 'refs/heads/master' && github.event_name == 'push'
144
+ runs-on: ubuntu-latest
145
+ environment:
146
+ name: github-pages
147
+ url: ${{ steps.deployment.outputs.page_url }}
148
+ steps:
149
+ - name: Download coverage badge
150
+ uses: actions/download-artifact@v4
151
+ with:
152
+ name: coverage-badge
153
+ path: .
154
+ - name: Setup Pages
155
+ uses: actions/configure-pages@v5
156
+ - name: Upload Pages artifact
157
+ uses: actions/upload-pages-artifact@v3
158
+ with:
159
+ path: .
160
+ - name: Deploy to GitHub Pages
161
+ id: deployment
162
+ uses: actions/deploy-pages@v4
data/Gemfile CHANGED
@@ -1,21 +1,27 @@
1
- # frozen_string_literal: true
2
1
  source 'https://rubygems.org'
3
-
4
- # Specify your gem's dependencies in active_admin_importable.gemspec
5
2
  gemspec
6
3
 
4
+ default_rails_version = '7.1.0'
5
+ default_activeadmin_version = '3.2.0'
6
+
7
+ gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
8
+ gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
9
+ gem 'sprockets-rails'
10
+ gem 'sass-rails'
7
11
 
8
12
  group :test do
9
- default_rails_version = "~> 5.2.4"
10
- rails_version = ENV['RAILS'] || default_rails_version
11
- gem 'sassc-rails'
12
- gem 'rails', rails_version
13
+ gem 'simplecov', require: false
13
14
  gem 'rspec-rails'
14
- gem 'coveralls', require: false # Test coverage website. Go to https://coveralls.io
15
- gem "sqlite3", "~> 1.4.0"
16
- gem 'launchy'
15
+ case ENV['DB']
16
+ when 'mysql'
17
+ gem 'mysql2'
18
+ when 'postgres', 'postgresql'
19
+ gem 'pg'
20
+ else
21
+ gem 'sqlite3', '~> 2.0'
22
+ end
17
23
  gem 'database_cleaner'
18
24
  gem 'capybara'
19
- gem 'poltergeist'
20
- gem 'jquery-ui-rails', '~> 5.0'
25
+ gem 'cuprite'
26
+ gem 'webrick', require: false
21
27
  end
data/README.md CHANGED
@@ -1,15 +1,13 @@
1
1
  # ActiveAdminImport
2
2
 
3
- [![Travis Build ][build_badge]][build_link]
4
- [![Coverage Status][coveralls_badge]][coveralls_link]
5
- [![Code Climate ][codeclimate_badge]][codeclimate_link]
6
- [![Gem Version ][rubygems_badge]][rubygems_link]
7
- [![License ][license_badge]][license_link]
3
+ [![Build Status][build_badge]][build_link]
4
+ ![Coverage][coverage_badge]
5
+ [![Code Climate][codeclimate_badge]][codeclimate_link]
6
+ [![Gem Version][rubygems_badge]][rubygems_link]
7
+ [![License][license_badge]][license_link]
8
8
 
9
9
 
10
- The most fastest and efficient CSV import for Active Admin with support of validations, bulk inserts and encodings handling.
11
-
12
- For more about ActiveAdminImport installation and usage, check [Documentation website](http://activeadmin-plugins.github.io/active_admin_import/) and [Wiki pages](https://github.com/activeadmin-plugins/active_admin_import/wiki) for some specific cases and caveats.
10
+ The fastest and most efficient CSV import for Active Admin with support for validations, bulk inserts, and encoding handling.
13
11
 
14
12
 
15
13
  ## Installation
@@ -44,7 +42,7 @@ And then execute:
44
42
  #### Basic usage
45
43
 
46
44
  ```ruby
47
- ActiveAdmin.register Post
45
+ ActiveAdmin.register Post do
48
46
  active_admin_import options
49
47
  end
50
48
  ```
@@ -68,6 +66,7 @@ Tool | Description
68
66
  :timestamps |bool, tells activerecord-import to not add timestamps (if false) even if record timestamps is disabled in ActiveRecord::Base
69
67
  :template |custom template rendering
70
68
  :template_object |object passing to view
69
+ :result_class |custom `ImportResult` subclass to collect data from each batch (e.g. inserted ids). Must respond to `add(batch_result, qty)` plus the readers used in flash messages (`failed`, `total`, `imported_qty`, `imported?`, `failed?`, `empty?`, `failed_message`).
71
70
  :resource_class |resource class name
72
71
  :resource_label |resource label value
73
72
  :plural_resource_label |pluralized resource label value (default config.plural_resource_label)
@@ -77,9 +76,248 @@ Tool | Description
77
76
 
78
77
 
79
78
 
80
- #### Wiki
79
+ #### Custom ImportResult
80
+
81
+ To collect extra data from each batch (for example the ids of inserted rows so you can enqueue background jobs against them), pass a subclass of `ActiveAdminImport::ImportResult` via `:result_class`:
82
+
83
+ ```ruby
84
+ class ImportResultWithIds < ActiveAdminImport::ImportResult
85
+ attr_reader :ids
86
+
87
+ def initialize
88
+ super
89
+ @ids = []
90
+ end
91
+
92
+ def add(batch_result, qty)
93
+ super
94
+ @ids.concat(Array(batch_result.ids))
95
+ end
96
+ end
97
+
98
+ ActiveAdmin.register Author do
99
+ active_admin_import result_class: ImportResultWithIds do |result, options|
100
+ EnqueueAuthorsJob.perform_later(result.ids) if result.imported?
101
+ instance_exec(result, options, &ActiveAdminImport::DSL::DEFAULT_RESULT_PROC)
102
+ end
103
+ end
104
+ ```
105
+
106
+ The action block is invoked via `instance_exec` with `result` and `options` as block arguments, so you can either capture them with `do |result, options|` or read them as locals when no arguments are declared.
107
+
108
+ Note: which batch-result attributes are populated depends on the database adapter and the import options. `activerecord-import` returns ids reliably on PostgreSQL; on MySQL/SQLite the behavior depends on the adapter and options like `on_duplicate_key_update`. Putting the collection logic in your own subclass keeps these adapter quirks in your application code.
109
+
110
+
111
+ #### Authorization
112
+
113
+ The current user must be authorized to perform imports. With CanCanCan:
114
+
115
+ ```ruby
116
+ class Ability
117
+ include CanCan::Ability
118
+
119
+ def initialize(user)
120
+ can :import, Post
121
+ end
122
+ end
123
+ ```
124
+
125
+
126
+ #### Per-request context
127
+
128
+ Define an `active_admin_import_context` method on the controller to inject request-derived attributes into every import (current user, parent resource id, request IP, etc.). The returned hash is merged into the import model after form params, so it always wins for the keys it provides:
129
+
130
+ ```ruby
131
+ ActiveAdmin.register PostComment do
132
+ belongs_to :post
133
+
134
+ controller do
135
+ def active_admin_import_context
136
+ { post_id: parent.id, request_ip: request.remote_ip }
137
+ end
138
+ end
139
+
140
+ active_admin_import before_batch_import: ->(importer) {
141
+ importer.csv_lines.map! { |row| row << importer.model.post_id }
142
+ importer.headers.merge!(:'Post Id' => :post_id)
143
+ }
144
+ end
145
+ ```
146
+
147
+
148
+ #### Examples
149
+
150
+ ##### Files without CSV headers
151
+
152
+ ```ruby
153
+ ActiveAdmin.register Post do
154
+ active_admin_import validate: true,
155
+ template_object: ActiveAdminImport::Model.new(
156
+ hint: "expected header order: body, title, author",
157
+ csv_headers: %w[body title author]
158
+ )
159
+ end
160
+ ```
161
+
162
+ ##### Auto-detect file encoding
163
+
164
+ ```ruby
165
+ ActiveAdmin.register Post do
166
+ active_admin_import validate: true,
167
+ template_object: ActiveAdminImport::Model.new(force_encoding: :auto)
168
+ end
169
+ ```
170
+
171
+ ##### Force a specific (non-UTF-8) encoding
172
+
173
+ ```ruby
174
+ ActiveAdmin.register Post do
175
+ active_admin_import validate: true,
176
+ template_object: ActiveAdminImport::Model.new(
177
+ hint: "file is encoded in ISO-8859-1",
178
+ force_encoding: "ISO-8859-1"
179
+ )
180
+ end
181
+ ```
182
+
183
+ ##### Disallow ZIP upload
184
+
185
+ ```ruby
186
+ ActiveAdmin.register Post do
187
+ active_admin_import validate: true,
188
+ template_object: ActiveAdminImport::Model.new(
189
+ hint: "upload a CSV file",
190
+ allow_archive: false
191
+ )
192
+ end
193
+ ```
194
+
195
+ ##### Skip CSV columns
196
+
197
+ Useful when the CSV file has columns that don't exist on the table. Available since 3.1.0.
198
+
199
+ ```ruby
200
+ ActiveAdmin.register Post do
201
+ active_admin_import before_batch_import: ->(importer) {
202
+ importer.batch_slice_columns(['name', 'last_name'])
203
+ }
204
+ end
205
+ ```
206
+
207
+ Tip: pass `Post.column_names` to keep only the columns that exist on the table.
208
+
209
+ ##### Resolve associations on the fly
210
+
211
+ Replace an `Author name` column in the CSV with the matching `author_id` before insert:
212
+
213
+ ```ruby
214
+ ActiveAdmin.register Post do
215
+ active_admin_import validate: true,
216
+ headers_rewrites: { 'Author name': :author_id },
217
+ before_batch_import: ->(importer) {
218
+ names = importer.values_at(:author_id)
219
+ mapping = Author.where(name: names).pluck(:name, :id).to_h
220
+ importer.batch_replace(:author_id, mapping)
221
+ }
222
+ end
223
+ ```
224
+
225
+ ##### Update existing records by id
226
+
227
+ Delete colliding rows just before each batch insert:
228
+
229
+ ```ruby
230
+ ActiveAdmin.register Post do
231
+ active_admin_import before_batch_import: ->(importer) {
232
+ Post.where(id: importer.values_at('id')).delete_all
233
+ }
234
+ end
235
+ ```
236
+
237
+ For databases that support upserts you can use `:on_duplicate_key_update` instead.
238
+
239
+ ##### Tune batch size
240
+
241
+ ```ruby
242
+ ActiveAdmin.register Post do
243
+ active_admin_import validate: false,
244
+ csv_options: { col_sep: ";" },
245
+ batch_size: 1000
246
+ end
247
+ ```
248
+
249
+ ##### Import into an intermediate table
250
+
251
+ ```ruby
252
+ ActiveAdmin.register Post do
253
+ active_admin_import validate: false,
254
+ csv_options: { col_sep: ";" },
255
+ resource_class: ImportedPost, # write to a staging table
256
+ before_import: ->(_) { ImportedPost.delete_all },
257
+ after_import: ->(_) {
258
+ Post.transaction do
259
+ Post.delete_all
260
+ Post.connection.execute("INSERT INTO posts (SELECT * FROM imported_posts)")
261
+ end
262
+ },
263
+ back: ->(_) { config.namespace.resource_for(Post).route_collection_path }
264
+ end
265
+ ```
266
+
267
+ ##### Allow user input for CSV options (custom template)
268
+
269
+ ```ruby
270
+ ActiveAdmin.register Post do
271
+ active_admin_import validate: false,
272
+ template: 'admin/posts/import',
273
+ template_object: ActiveAdminImport::Model.new(
274
+ hint: "you can configure CSV options",
275
+ csv_options: { col_sep: ";", row_sep: nil, quote_char: nil }
276
+ )
277
+ end
278
+ ```
279
+
280
+ `app/views/admin/posts/import.html.erb`:
281
+
282
+ ```erb
283
+ <p><%= raw(@active_admin_import_model.hint) %></p>
284
+
285
+ <%= semantic_form_for @active_admin_import_model, url: { action: :do_import }, html: { multipart: true } do |f| %>
286
+ <%= f.inputs do %>
287
+ <%= f.input :file, as: :file %>
288
+ <% end %>
289
+
290
+ <%= f.inputs "CSV options", for: [:csv_options, OpenStruct.new(@active_admin_import_model.csv_options)] do |csv| %>
291
+ <% csv.with_options input_html: { style: 'width:40px;' } do |opts| %>
292
+ <%= opts.input :col_sep %>
293
+ <%= opts.input :row_sep %>
294
+ <%= opts.input :quote_char %>
295
+ <% end %>
296
+ <% end %>
297
+
298
+ <%= f.actions do %>
299
+ <%= f.action :submit,
300
+ label: t("active_admin_import.import_btn"),
301
+ button_html: { disable_with: t("active_admin_import.import_btn_disabled") } %>
302
+ <% end %>
303
+ <% end %>
304
+ ```
305
+
306
+ ##### Inspecting the importer in batch callbacks
307
+
308
+ Both `before_batch_import` and `after_batch_import` receive the `Importer` instance:
309
+
310
+ ```ruby
311
+ active_admin_import before_batch_import: ->(importer) {
312
+ importer.file # the uploaded file
313
+ importer.resource # the ActiveRecord class being imported into
314
+ importer.options # the resolved options hash
315
+ importer.headers # CSV headers (mutable)
316
+ importer.csv_lines # parsed CSV rows for the current batch (mutable)
317
+ importer.model # the template_object instance
318
+ }
319
+ ```
81
320
 
82
- [Check various examples](https://github.com/activeadmin-plugins/active_admin_import/wiki)
83
321
 
84
322
  ## Dependencies
85
323
 
@@ -91,16 +329,15 @@ Tool | Description
91
329
  [rchardet]: https://github.com/jmhodges/rchardet
92
330
  [activerecord-import]: https://github.com/zdennis/activerecord-import
93
331
 
94
- [build_badge]: https://travis-ci.org/activeadmin-plugins/active_admin_import.svg?branch=master
95
- [build_link]: https://travis-ci.org/activeadmin-plugins/active_admin_import
96
- [coveralls_badge]: https://coveralls.io/repos/activeadmin-plugins/active_admin_import/badge.svg
97
- [coveralls_link]: https://coveralls.io/github/activeadmin-plugins/active_admin_import
332
+ [build_badge]: https://github.com/activeadmin-plugins/active_admin_import/actions/workflows/test.yml/badge.svg
333
+ [build_link]: https://github.com/activeadmin-plugins/active_admin_import/actions
334
+ [coverage_badge]: https://img.shields.io/endpoint?url=https://activeadmin-plugins.github.io/active_admin_import/badge.json
98
335
  [codeclimate_badge]: https://codeclimate.com/github/activeadmin-plugins/active_admin_import/badges/gpa.svg
99
336
  [codeclimate_link]: https://codeclimate.com/github/activeadmin-plugins/active_admin_import
100
337
  [rubygems_badge]: https://badge.fury.io/rb/active_admin_import.svg
101
338
  [rubygems_link]: https://rubygems.org/gems/active_admin_import
102
- [license_badge]: http://img.shields.io/:license-mit-blue.svg
103
- [license_link]: http://Fivell.mit-license.org
339
+ [license_badge]: https://img.shields.io/:license-mit-blue.svg
340
+ [license_link]: https://Fivell.mit-license.org
104
341
 
105
342
 
106
343
  ## Contributing
data/Rakefile CHANGED
@@ -1,8 +1,5 @@
1
- # frozen_string_literal: true
2
- require 'bundler'
1
+ require "bundler"
3
2
  require 'rake'
4
3
  Bundler.setup
5
4
  Bundler::GemHelper.install_tasks
6
-
7
- # Import all our rake tasks
8
5
  FileList['tasks/**/*.rake'].each { |task| import task }
@@ -7,16 +7,16 @@ Gem::Specification.new do |gem|
7
7
  gem.email = ['fedoronchuk@gmail.com']
8
8
  gem.description = 'The most efficient way to import for Active Admin'
9
9
  gem.summary = 'ActiveAdmin import based on activerecord-import gem.'
10
- gem.homepage = 'http://github.com/Fivell/active_admin_import'
10
+ gem.homepage = 'https://github.com/activeadmin-plugins/active_admin_import'
11
11
  gem.license = 'MIT'
12
+ gem.required_ruby_version = '>= 3.1.0'
12
13
  gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
13
14
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
15
  gem.name = 'active_admin_import'
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = ActiveAdminImport::VERSION
18
- gem.add_runtime_dependency 'activerecord-import', '>= 0.27'
18
+ gem.add_runtime_dependency 'activerecord-import', '>= 2.0'
19
19
  gem.add_runtime_dependency 'rchardet', '>= 1.6'
20
20
  gem.add_runtime_dependency 'rubyzip', '>= 1.2'
21
- gem.add_dependency 'activeadmin', '>= 1.0.0'
21
+ gem.add_dependency 'activeadmin', '>= 3.0', '< 4.0'
22
22
  end
@@ -25,6 +25,22 @@ module ActiveAdminImport
25
25
  # +plural_resource_label+:: pluralized resource label value (default config.plural_resource_label)
26
26
  #
27
27
  module DSL
28
+ CONTEXT_METHOD = :active_admin_import_context
29
+
30
+ def self.prepare_import_model(template_object, controller, params: nil)
31
+ model = template_object.is_a?(Proc) ? template_object.call : template_object
32
+ if params
33
+ params_key = ActiveModel::Naming.param_key(model.class)
34
+ model.assign_attributes(params[params_key].try(:deep_symbolize_keys) || {})
35
+ end
36
+ return model unless controller.respond_to?(CONTEXT_METHOD, true)
37
+ context = controller.send(CONTEXT_METHOD)
38
+ return model unless context.is_a?(Hash)
39
+ context = context.merge(file: model.file) if model.respond_to?(:file) && !context.key?(:file)
40
+ model.assign_attributes(context)
41
+ model
42
+ end
43
+
28
44
  DEFAULT_RESULT_PROC = lambda do |result, options|
29
45
  model_name = options[:resource_label].downcase
30
46
  plural_model_name = options[:plural_resource_label].downcase
@@ -54,11 +70,10 @@ module ActiveAdminImport
54
70
  options.assert_valid_keys(*Options::VALID_OPTIONS)
55
71
 
56
72
  options = Options.options_for(config, options)
57
- params_key = ActiveModel::Naming.param_key(options[:template_object])
58
73
 
59
74
  collection_action :import, method: :get do
60
75
  authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
61
- @active_admin_import_model = options[:template_object]
76
+ @active_admin_import_model = ActiveAdminImport::DSL.prepare_import_model(options[:template_object], self)
62
77
  render template: options[:template]
63
78
  end
64
79
 
@@ -75,8 +90,9 @@ module ActiveAdminImport
75
90
  authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
76
91
  _params = params.respond_to?(:to_unsafe_h) ? params.to_unsafe_h : params
77
92
  params = ActiveSupport::HashWithIndifferentAccess.new _params
78
- @active_admin_import_model = options[:template_object]
79
- @active_admin_import_model.assign_attributes(params[params_key].try(:deep_symbolize_keys) || {})
93
+ @active_admin_import_model = ActiveAdminImport::DSL.prepare_import_model(
94
+ options[:template_object], self, params: params
95
+ )
80
96
  # go back to form
81
97
  return render template: options[:template] unless @active_admin_import_model.valid?
82
98
  @importer = Importer.new(
@@ -88,12 +104,13 @@ module ActiveAdminImport
88
104
  result = @importer.import
89
105
 
90
106
  if block_given?
91
- instance_eval(&block)
107
+ instance_exec result, options, &block
92
108
  else
93
109
  instance_exec result, options, &DEFAULT_RESULT_PROC
94
110
  end
95
111
  rescue ActiveRecord::Import::MissingColumnError,
96
112
  NoMethodError,
113
+ ArgumentError,
97
114
  ActiveRecord::StatementInvalid,
98
115
  CSV::MalformedCSVError,
99
116
  ActiveAdminImport::Exception => e
@@ -33,11 +33,21 @@ module ActiveAdminImport
33
33
  limit = options[:limit] || failed.count
34
34
  failed.first(limit).map do |record|
35
35
  errors = record.errors
36
- failed_values = errors.keys.map do |key|
36
+ failed_values = attribute_names_for(errors).map do |key|
37
37
  key == :base ? nil : record.public_send(key)
38
38
  end
39
39
  errors.full_messages.zip(failed_values).map { |ms| ms.compact.join(' - ') }.join(', ')
40
40
  end.join(' ; ')
41
41
  end
42
+
43
+ private
44
+
45
+ def attribute_names_for(errors)
46
+ if Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
47
+ errors.attribute_names
48
+ else
49
+ errors.keys
50
+ end
51
+ end
42
52
  end
43
53
  end
@@ -18,7 +18,8 @@ module ActiveAdminImport
18
18
  :headers_rewrites,
19
19
  :batch_size,
20
20
  :batch_transaction,
21
- :csv_options
21
+ :csv_options,
22
+ :result_class
22
23
  ].freeze
23
24
 
24
25
  def initialize(resource, model, options)
@@ -29,7 +30,7 @@ module ActiveAdminImport
29
30
  end
30
31
 
31
32
  def import_result
32
- @import_result ||= ImportResult.new
33
+ @import_result ||= (options[:result_class] || ImportResult).new
33
34
  end
34
35
 
35
36
  def file
@@ -37,7 +37,7 @@ module ActiveAdminImport
37
37
  validate :file_contents_present, if: ->(me) { me.file.present? }
38
38
 
39
39
  before_validation :unzip_file, if: ->(me) { me.archive? && me.allow_archive? }
40
- before_validation :encode_file, if: ->(me) { me.force_encoding? && me.file.present? }
40
+ after_validation :encode_file, if: ->(me) { me.errors.empty? && me.force_encoding? && me.file.present? }
41
41
 
42
42
  attr_reader :attributes
43
43
 
@@ -48,6 +48,7 @@ module ActiveAdminImport
48
48
  end
49
49
 
50
50
  def assign_attributes(args = {}, new_record = false)
51
+ args[:file] = nil unless args.key?(:file)
51
52
  @attributes.merge!(args)
52
53
  @new_record = new_record
53
54
  args.keys.each do |key|
@@ -103,6 +104,8 @@ module ActiveAdminImport
103
104
 
104
105
  def encode_file
105
106
  data = File.read(file_path)
107
+ return if data.empty?
108
+
106
109
  File.open(file_path, 'w') do |f|
107
110
  f.write(encode(data))
108
111
  end