active_admin_import 5.1.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 +4 -4
- data/.github/workflows/test.yml +150 -31
- data/Gemfile +18 -12
- data/README.md +252 -15
- data/Rakefile +1 -4
- data/active_admin_import.gemspec +4 -4
- data/lib/active_admin_import/dsl.rb +21 -13
- data/lib/active_admin_import/importer.rb +3 -2
- data/lib/active_admin_import/model.rb +2 -1
- data/lib/active_admin_import/options.rb +1 -0
- data/lib/active_admin_import/version.rb +1 -1
- data/spec/fixtures/files/author_invalid_format.txt +2 -0
- data/spec/fixtures/files/post_comments.csv +3 -0
- data/spec/import_spec.rb +144 -1
- data/spec/spec_helper.rb +13 -17
- data/spec/support/admin.rb +16 -0
- data/spec/support/rails_template.rb +49 -18
- data/tasks/test.rake +18 -4
- metadata +18 -40
- data/CHANGELOG.md +0 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdc784c6f8a845b491b0dc93e44b073b548bccc182bb3cf6c26cb9f80e59b587
|
|
4
|
+
data.tar.gz: 798237101d7d4403ed8fb1d1d4ed466b4b78bc44740e20137887d2c8b6464e3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '06639b5514e0c5af1f2cfd70f66f7de10b798fc22f9153b4870c0859b3f95fd372cf73f406b15b7df8b3e4d6fc350a911680248e09d15412479c55f092869a0d'
|
|
7
|
+
data.tar.gz: 1b49c36c911a6eae50d653eafa4063a139b5d901b81264b213ece4bb995158bf7685f62facce2d5d1a69b76fe2735483296a26f9f697f6fad802d562ca3deef5
|
data/.github/workflows/test.yml
CHANGED
|
@@ -1,43 +1,162 @@
|
|
|
1
|
-
name:
|
|
2
|
-
|
|
1
|
+
name: CI
|
|
3
2
|
on:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pages: write
|
|
10
|
+
id-token: write
|
|
6
11
|
|
|
7
12
|
jobs:
|
|
8
|
-
|
|
13
|
+
test:
|
|
14
|
+
name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }} / SQLite
|
|
9
15
|
runs-on: ubuntu-latest
|
|
10
|
-
|
|
11
16
|
strategy:
|
|
17
|
+
fail-fast: false
|
|
12
18
|
matrix:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- '3.0'
|
|
17
|
-
rails_version:
|
|
18
|
-
- '5.2.6'
|
|
19
|
-
- '6.0.4'
|
|
20
|
-
- '6.1.4'
|
|
21
|
-
- '7.0.0'
|
|
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
22
|
exclude:
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
- ruby_version: '2.6'
|
|
26
|
-
rails_version: '7.0.0'
|
|
27
|
-
|
|
28
|
-
name: Ruby ${{ matrix.ruby_version }} / Rails ${{ matrix.rails_version }}
|
|
29
|
-
|
|
23
|
+
- rails: '8.0.0'
|
|
24
|
+
activeadmin: '3.2.0'
|
|
30
25
|
env:
|
|
31
|
-
RAILS: ${{ matrix.
|
|
32
|
-
|
|
26
|
+
RAILS: ${{ matrix.rails }}
|
|
27
|
+
AA: ${{ matrix.activeadmin }}
|
|
33
28
|
steps:
|
|
34
|
-
- uses: actions/checkout@
|
|
35
|
-
|
|
36
|
-
- name: Setup Ruby
|
|
37
|
-
uses: ruby/setup-ruby@v1
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: ruby/setup-ruby@v1
|
|
38
31
|
with:
|
|
39
|
-
ruby-version: ${{ matrix.
|
|
32
|
+
ruby-version: ${{ matrix.ruby }}
|
|
40
33
|
bundler-cache: true
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
43
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 '
|
|
20
|
-
gem '
|
|
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
|
-
[![Build Status
|
|
4
|
-
|
|
5
|
-
[![Code Climate
|
|
6
|
-
[![Gem Version
|
|
7
|
-
[![License
|
|
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
|
|
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
|
-
####
|
|
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
|
|
|
@@ -93,14 +331,13 @@ Tool | Description
|
|
|
93
331
|
|
|
94
332
|
[build_badge]: https://github.com/activeadmin-plugins/active_admin_import/actions/workflows/test.yml/badge.svg
|
|
95
333
|
[build_link]: https://github.com/activeadmin-plugins/active_admin_import/actions
|
|
96
|
-
[
|
|
97
|
-
[coveralls_link]: https://coveralls.io/github/activeadmin-plugins/active_admin_import
|
|
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]:
|
|
103
|
-
[license_link]:
|
|
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
data/active_admin_import.gemspec
CHANGED
|
@@ -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 = '
|
|
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
|
|
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', '>=
|
|
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
|
|
@@ -57,11 +73,7 @@ module ActiveAdminImport
|
|
|
57
73
|
|
|
58
74
|
collection_action :import, method: :get do
|
|
59
75
|
authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
|
|
60
|
-
@active_admin_import_model =
|
|
61
|
-
options[:template_object].call
|
|
62
|
-
else
|
|
63
|
-
options[:template_object]
|
|
64
|
-
end
|
|
76
|
+
@active_admin_import_model = ActiveAdminImport::DSL.prepare_import_model(options[:template_object], self)
|
|
65
77
|
render template: options[:template]
|
|
66
78
|
end
|
|
67
79
|
|
|
@@ -78,13 +90,9 @@ module ActiveAdminImport
|
|
|
78
90
|
authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
|
|
79
91
|
_params = params.respond_to?(:to_unsafe_h) ? params.to_unsafe_h : params
|
|
80
92
|
params = ActiveSupport::HashWithIndifferentAccess.new _params
|
|
81
|
-
@active_admin_import_model =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
options[:template_object]
|
|
85
|
-
end
|
|
86
|
-
params_key = ActiveModel::Naming.param_key(@active_admin_import_model.class)
|
|
87
|
-
@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
|
+
)
|
|
88
96
|
# go back to form
|
|
89
97
|
return render template: options[:template] unless @active_admin_import_model.valid?
|
|
90
98
|
@importer = Importer.new(
|
|
@@ -96,7 +104,7 @@ module ActiveAdminImport
|
|
|
96
104
|
result = @importer.import
|
|
97
105
|
|
|
98
106
|
if block_given?
|
|
99
|
-
|
|
107
|
+
instance_exec result, options, &block
|
|
100
108
|
else
|
|
101
109
|
instance_exec result, options, &DEFAULT_RESULT_PROC
|
|
102
110
|
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
|
-
|
|
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|
|
data/spec/import_spec.rb
CHANGED
|
@@ -27,7 +27,7 @@ describe 'import', type: :feature do
|
|
|
27
27
|
zip_file = File.expand_path("./spec/fixtures/files/#{name}.zip")
|
|
28
28
|
|
|
29
29
|
begin
|
|
30
|
-
Zip::File.open(zip_file,
|
|
30
|
+
Zip::File.open(zip_file, create: true) do |z|
|
|
31
31
|
z.add "#{name}.csv", File.expand_path("./spec/fixtures/files/#{name}.csv")
|
|
32
32
|
end
|
|
33
33
|
instance_eval &block
|
|
@@ -588,4 +588,147 @@ describe 'import', type: :feature do
|
|
|
588
588
|
expect { add_author_resource(options) }.to raise_error(ArgumentError)
|
|
589
589
|
end
|
|
590
590
|
end
|
|
591
|
+
|
|
592
|
+
context 'when submitting empty form after validation error' do
|
|
593
|
+
let(:options) { {} }
|
|
594
|
+
|
|
595
|
+
before do
|
|
596
|
+
add_author_resource(options)
|
|
597
|
+
visit '/admin/authors/import'
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
it 'should NOT reuse cached file from previous submission' do
|
|
601
|
+
expect do
|
|
602
|
+
upload_file!(:author_broken_header)
|
|
603
|
+
expect(page).to have_content("can't write unknown attribute")
|
|
604
|
+
end.not_to change { Author.count }
|
|
605
|
+
|
|
606
|
+
# Second submission without selecting a file
|
|
607
|
+
expect do
|
|
608
|
+
find_button('Import').click
|
|
609
|
+
expect(page).to have_content(I18n.t('active_admin_import.no_file_error'))
|
|
610
|
+
end.not_to change { Author.count }
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
context 'when importing file with invalid format and auto force_encoding' do
|
|
615
|
+
let(:options) { { template_object: ActiveAdminImport::Model.new(force_encoding: :auto) } }
|
|
616
|
+
|
|
617
|
+
before do
|
|
618
|
+
add_author_resource(options)
|
|
619
|
+
visit '/admin/authors/import'
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
it 'should reject invalid file format before encoding' do
|
|
623
|
+
expect do
|
|
624
|
+
upload_file!(:author_invalid_format, 'txt')
|
|
625
|
+
expect(page).to have_content I18n.t('active_admin_import.file_format_error')
|
|
626
|
+
end.not_to change { Author.count }
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
context 'with active_admin_import_context defined on the controller' do
|
|
631
|
+
before { Author.create!(name: 'John', last_name: 'Doe') }
|
|
632
|
+
|
|
633
|
+
let(:author) { Author.take }
|
|
634
|
+
|
|
635
|
+
context 'when context returns request-derived attributes' do
|
|
636
|
+
before do
|
|
637
|
+
author_id = author.id
|
|
638
|
+
add_post_resource(
|
|
639
|
+
template_object: ActiveAdminImport::Model.new(author_id: author_id),
|
|
640
|
+
before_batch_import: lambda do |importer|
|
|
641
|
+
ip = importer.model.request_ip
|
|
642
|
+
a_id = importer.model.author_id
|
|
643
|
+
importer.csv_lines.map! { |row| row << ip << a_id }
|
|
644
|
+
importer.headers.merge!(:'Request Ip' => :request_ip, :'Author Id' => :author_id)
|
|
645
|
+
end,
|
|
646
|
+
controller_block: proc do
|
|
647
|
+
def active_admin_import_context
|
|
648
|
+
{ request_ip: request.remote_ip }
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
)
|
|
652
|
+
visit '/admin/posts/import'
|
|
653
|
+
upload_file!(:posts_for_author)
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
it 'merges the context into the import model so callbacks see it' do
|
|
657
|
+
expect(page).to have_content 'Successfully imported 2 posts'
|
|
658
|
+
expect(Post.count).to eq(2)
|
|
659
|
+
Post.all.each do |post|
|
|
660
|
+
expect(post.request_ip).to eq('127.0.0.1')
|
|
661
|
+
expect(post.author_id).to eq(author.id)
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
context 'when context returns parent id for a nested belongs_to resource' do
|
|
667
|
+
let(:post) { Post.create!(title: 'A post', body: 'body', author: author) }
|
|
668
|
+
|
|
669
|
+
before do
|
|
670
|
+
add_nested_post_comment_resource(
|
|
671
|
+
before_batch_import: lambda do |importer|
|
|
672
|
+
importer.csv_lines.map! { |row| row << importer.model.post_id }
|
|
673
|
+
importer.headers.merge!(:'Post Id' => :post_id)
|
|
674
|
+
end,
|
|
675
|
+
controller_block: proc do
|
|
676
|
+
def active_admin_import_context
|
|
677
|
+
{ post_id: parent.id }
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
)
|
|
681
|
+
visit "/admin/posts/#{post.id}/post_comments/import"
|
|
682
|
+
upload_file!(:post_comments)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
it 'automatically assigns the parent post_id to every imported comment' do
|
|
686
|
+
expect(page).to have_content 'Successfully imported 2 post comments'
|
|
687
|
+
expect(PostComment.count).to eq(2)
|
|
688
|
+
expect(PostComment.where(post_id: post.id).count).to eq(2)
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
# PG-only: activerecord-import populates `result.ids` reliably on PostgreSQL
|
|
694
|
+
# via RETURNING. On MySQL/SQLite the array is not populated by default, so
|
|
695
|
+
# the assertion would not be meaningful there. The :result_class option
|
|
696
|
+
# itself works on every adapter — this spec just demonstrates the canonical
|
|
697
|
+
# PR #191 use case (collecting inserted ids) on the adapter that supports it.
|
|
698
|
+
if ENV['DB'] == 'postgres'
|
|
699
|
+
context 'with custom result_class (PostgreSQL)' do
|
|
700
|
+
# Subclass that captures inserted ids alongside the standard counters.
|
|
701
|
+
# Lives in user-land so the gem itself stays free of adapter-specific
|
|
702
|
+
# quirks around RETURNING. This is the example documented in the README.
|
|
703
|
+
class ImportResultWithIds < ActiveAdminImport::ImportResult
|
|
704
|
+
attr_reader :ids
|
|
705
|
+
|
|
706
|
+
def initialize
|
|
707
|
+
super
|
|
708
|
+
@ids = []
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def add(batch_result, qty)
|
|
712
|
+
super
|
|
713
|
+
@ids.concat(Array(batch_result.ids))
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
before do
|
|
718
|
+
add_author_resource(result_class: ImportResultWithIds) do |result, _options|
|
|
719
|
+
# Expose the captured ids on the flash so the test asserts via the
|
|
720
|
+
# rendered page rather than closure capture.
|
|
721
|
+
flash[:notice] = "Imported ids: [#{result.ids.sort.join(',')}]"
|
|
722
|
+
end
|
|
723
|
+
visit '/admin/authors/import'
|
|
724
|
+
upload_file!(:authors)
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
it 'collects the ids of inserted records via the custom subclass' do
|
|
728
|
+
expect(Author.count).to eq(2)
|
|
729
|
+
expected = "Imported ids: [#{Author.pluck(:id).sort.join(',')}]"
|
|
730
|
+
expect(page).to have_content(expected)
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
end
|
|
591
734
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
require 'simplecov'
|
|
2
|
+
SimpleCov.start do
|
|
3
|
+
add_filter '/spec/'
|
|
4
|
+
end
|
|
4
5
|
|
|
5
6
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
6
7
|
$LOAD_PATH << File.expand_path('../support', __FILE__)
|
|
@@ -10,22 +11,18 @@ require 'bundler'
|
|
|
10
11
|
Bundler.setup
|
|
11
12
|
|
|
12
13
|
ENV['RAILS_ENV'] = 'test'
|
|
13
|
-
# Ensure the Active Admin load path is happy
|
|
14
14
|
require 'rails'
|
|
15
15
|
ENV['RAILS'] = Rails.version
|
|
16
|
-
ENV['
|
|
17
|
-
|
|
16
|
+
ENV['DB'] ||= 'sqlite'
|
|
17
|
+
ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{ENV['RAILS']}-#{ENV['DB']}", __FILE__)
|
|
18
18
|
system 'rake setup' unless File.exist?(ENV['RAILS_ROOT'])
|
|
19
19
|
|
|
20
20
|
require 'active_model'
|
|
21
|
-
# require ActiveRecord to ensure that Ransack loads correctly
|
|
22
21
|
require 'active_record'
|
|
23
22
|
require 'action_view'
|
|
24
23
|
require 'active_admin'
|
|
25
24
|
ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + '/app/admin']
|
|
26
25
|
require ENV['RAILS_ROOT'] + '/config/environment.rb'
|
|
27
|
-
# Disabling authentication in specs so that we don't have to worry about
|
|
28
|
-
# it allover the place
|
|
29
26
|
ActiveAdmin.application.authentication_method = false
|
|
30
27
|
ActiveAdmin.application.current_user_method = false
|
|
31
28
|
|
|
@@ -33,21 +30,20 @@ require 'rspec/rails'
|
|
|
33
30
|
require 'support/admin'
|
|
34
31
|
require 'capybara/rails'
|
|
35
32
|
require 'capybara/rspec'
|
|
36
|
-
require 'capybara/
|
|
33
|
+
require 'capybara/cuprite'
|
|
37
34
|
|
|
38
|
-
Capybara.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
debug: true,
|
|
42
|
-
phantomjs_options: ['--debug=no', '--load-images=no'])
|
|
35
|
+
Capybara.server = :webrick
|
|
36
|
+
Capybara.register_driver :cuprite do |app|
|
|
37
|
+
Capybara::Cuprite::Driver.new(app, headless: true, window_size: [1280, 800])
|
|
43
38
|
end
|
|
44
|
-
|
|
45
|
-
Capybara.
|
|
39
|
+
Capybara.javascript_driver = :cuprite
|
|
40
|
+
Capybara.default_max_wait_time = 5
|
|
46
41
|
|
|
47
42
|
RSpec.configure do |config|
|
|
48
43
|
config.use_transactional_fixtures = false
|
|
49
44
|
|
|
50
45
|
config.before(:suite) do
|
|
46
|
+
ActiveRecord::Migration.maintain_test_schema!
|
|
51
47
|
DatabaseCleaner.strategy = :truncation
|
|
52
48
|
DatabaseCleaner.clean_with(:truncation)
|
|
53
49
|
end
|
data/spec/support/admin.rb
CHANGED
|
@@ -8,8 +8,24 @@ def add_author_resource(options = {}, &block)
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def add_post_resource(options = {}, &block)
|
|
11
|
+
cb = options.delete(:controller_block)
|
|
11
12
|
ActiveAdmin.register Post do
|
|
12
13
|
config.filters = false
|
|
14
|
+
controller(&cb) if cb
|
|
15
|
+
active_admin_import(options, &block)
|
|
16
|
+
end
|
|
17
|
+
Rails.application.reload_routes!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_nested_post_comment_resource(options = {}, &block)
|
|
21
|
+
cb = options.delete(:controller_block)
|
|
22
|
+
ActiveAdmin.register Post do
|
|
23
|
+
config.filters = false
|
|
24
|
+
end
|
|
25
|
+
ActiveAdmin.register PostComment do
|
|
26
|
+
config.filters = false
|
|
27
|
+
belongs_to :post
|
|
28
|
+
controller(&cb) if cb
|
|
13
29
|
active_admin_import(options, &block)
|
|
14
30
|
end
|
|
15
31
|
Rails.application.reload_routes!
|
|
@@ -1,29 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
create_file "app/assets/config/manifest.js", skip: true
|
|
2
|
+
|
|
3
|
+
db = ENV['DB'] || 'sqlite'
|
|
4
|
+
case db
|
|
5
|
+
when 'mysql'
|
|
6
|
+
remove_file 'config/database.yml'
|
|
7
|
+
create_file 'config/database.yml', <<~YAML
|
|
8
|
+
default: &default
|
|
9
|
+
adapter: mysql2
|
|
10
|
+
encoding: utf8mb4
|
|
11
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
12
|
+
host: <%= ENV.fetch("DB_HOST", "127.0.0.1") %>
|
|
13
|
+
port: <%= ENV.fetch("DB_PORT", 3306) %>
|
|
14
|
+
username: <%= ENV.fetch("DB_USERNAME", "root") %>
|
|
15
|
+
password: <%= ENV.fetch("DB_PASSWORD", "root") %>
|
|
16
|
+
|
|
17
|
+
test:
|
|
18
|
+
<<: *default
|
|
19
|
+
database: active_admin_import_test
|
|
20
|
+
YAML
|
|
21
|
+
when 'postgres', 'postgresql'
|
|
22
|
+
remove_file 'config/database.yml'
|
|
23
|
+
create_file 'config/database.yml', <<~YAML
|
|
24
|
+
default: &default
|
|
25
|
+
adapter: postgresql
|
|
26
|
+
encoding: unicode
|
|
27
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
28
|
+
host: <%= ENV.fetch("DB_HOST", "127.0.0.1") %>
|
|
29
|
+
port: <%= ENV.fetch("DB_PORT", 5432) %>
|
|
30
|
+
username: <%= ENV.fetch("DB_USERNAME", "postgres") %>
|
|
31
|
+
password: <%= ENV.fetch("DB_PASSWORD", "postgres") %>
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
<<: *default
|
|
35
|
+
database: active_admin_import_test
|
|
36
|
+
YAML
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
generate :model, 'author name:string{10}:uniq last_name:string birthday:date --force'
|
|
40
|
+
generate :model, 'post title:string:uniq body:text request_ip:string author:references --force'
|
|
41
|
+
generate :model, 'post_comment body:text post:references --force'
|
|
3
42
|
|
|
4
|
-
generate :model, 'author name:string{10}:uniq last_name:string birthday:date'
|
|
5
|
-
generate :model, 'post title:string:uniq body:text author:references'
|
|
6
|
-
|
|
7
|
-
# Add validation
|
|
8
43
|
inject_into_file 'app/models/author.rb', " validates_presence_of :name\n validates_uniqueness_of :last_name\n", before: 'end'
|
|
9
|
-
inject_into_file 'app/models/post.rb', " validates_presence_of :author\n", before: 'end'
|
|
10
|
-
|
|
11
|
-
# Configure default_url_options in test environment
|
|
12
|
-
inject_into_file 'config/environments/test.rb', " config.action_mailer.default_url_options = { :host => 'example.com' }\n", after: "config.cache_classes = true\n"
|
|
44
|
+
inject_into_file 'app/models/post.rb', " validates_presence_of :author\n has_many :post_comments\n", before: 'end'
|
|
13
45
|
|
|
14
|
-
# Add our local Active Admin to the load path
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
46
|
+
# Add our local Active Admin to the load path (Rails 7.1+)
|
|
47
|
+
gsub_file "config/environment.rb",
|
|
48
|
+
'require_relative "application"',
|
|
49
|
+
"require_relative \"application\"\n$LOAD_PATH.unshift('#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))}')\nrequire \"active_admin\"\n"
|
|
18
50
|
|
|
19
51
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
20
52
|
|
|
21
53
|
generate :'active_admin:install --skip-users'
|
|
22
54
|
generate :'formtastic:install'
|
|
23
55
|
|
|
24
|
-
run 'rm -
|
|
25
|
-
run 'rm -r spec'
|
|
26
|
-
|
|
56
|
+
run 'rm -rf test'
|
|
27
57
|
route "root :to => 'admin/dashboard#index'"
|
|
58
|
+
rake 'db:create db:migrate'
|
|
28
59
|
|
|
29
|
-
|
|
60
|
+
run 'rm -f Gemfile Gemfile.lock'
|
data/tasks/test.rake
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
desc 'Creates a test rails app for the specs to run against'
|
|
1
|
+
desc "Creates a test rails app for the specs to run against"
|
|
3
2
|
task :setup do
|
|
4
3
|
require 'rails/version'
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
db = ENV['DB'] || 'sqlite'
|
|
6
|
+
rails_db = case db
|
|
7
|
+
when 'mysql' then 'mysql'
|
|
8
|
+
when 'postgres', 'postgresql' then 'postgresql'
|
|
9
|
+
else 'sqlite3'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rails_new_opts = %W(
|
|
13
|
+
--skip-turbolinks
|
|
14
|
+
--skip-spring
|
|
15
|
+
--skip-bootsnap
|
|
16
|
+
-d #{rails_db}
|
|
17
|
+
-m
|
|
18
|
+
spec/support/rails_template.rb
|
|
19
|
+
)
|
|
20
|
+
system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING}-#{db} #{rails_new_opts.join(' ')}"
|
|
7
21
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_admin_import
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Igor Fedoronchuk
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord-import
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0
|
|
18
|
+
version: '2.0'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0
|
|
25
|
+
version: '2.0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: rchardet
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +57,20 @@ dependencies:
|
|
|
58
57
|
requirements:
|
|
59
58
|
- - ">="
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
60
|
+
version: '3.0'
|
|
61
|
+
- - "<"
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '4.0'
|
|
62
64
|
type: :runtime
|
|
63
65
|
prerelease: false
|
|
64
66
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
67
|
requirements:
|
|
66
68
|
- - ">="
|
|
67
69
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
70
|
+
version: '3.0'
|
|
71
|
+
- - "<"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '4.0'
|
|
69
74
|
description: The most efficient way to import for Active Admin
|
|
70
75
|
email:
|
|
71
76
|
- fedoronchuk@gmail.com
|
|
@@ -76,7 +81,6 @@ files:
|
|
|
76
81
|
- ".github/workflows/test.yml"
|
|
77
82
|
- ".gitignore"
|
|
78
83
|
- ".rubocop.yml"
|
|
79
|
-
- CHANGELOG.md
|
|
80
84
|
- Gemfile
|
|
81
85
|
- LICENSE
|
|
82
86
|
- README.md
|
|
@@ -108,6 +112,7 @@ files:
|
|
|
108
112
|
- spec/fixtures/files/author.csv
|
|
109
113
|
- spec/fixtures/files/author_broken_header.csv
|
|
110
114
|
- spec/fixtures/files/author_invalid.csv
|
|
115
|
+
- spec/fixtures/files/author_invalid_format.txt
|
|
111
116
|
- spec/fixtures/files/authors.csv
|
|
112
117
|
- spec/fixtures/files/authors_bom.csv
|
|
113
118
|
- spec/fixtures/files/authors_invalid_db.csv
|
|
@@ -120,6 +125,7 @@ files:
|
|
|
120
125
|
- spec/fixtures/files/authors_with_tabs.tsv
|
|
121
126
|
- spec/fixtures/files/empty.csv
|
|
122
127
|
- spec/fixtures/files/only_headers.csv
|
|
128
|
+
- spec/fixtures/files/post_comments.csv
|
|
123
129
|
- spec/fixtures/files/posts.csv
|
|
124
130
|
- spec/fixtures/files/posts_for_author.csv
|
|
125
131
|
- spec/fixtures/files/posts_for_author_no_headers.csv
|
|
@@ -131,11 +137,10 @@ files:
|
|
|
131
137
|
- spec/support/admin.rb
|
|
132
138
|
- spec/support/rails_template.rb
|
|
133
139
|
- tasks/test.rake
|
|
134
|
-
homepage:
|
|
140
|
+
homepage: https://github.com/activeadmin-plugins/active_admin_import
|
|
135
141
|
licenses:
|
|
136
142
|
- MIT
|
|
137
143
|
metadata: {}
|
|
138
|
-
post_install_message:
|
|
139
144
|
rdoc_options: []
|
|
140
145
|
require_paths:
|
|
141
146
|
- lib
|
|
@@ -143,41 +148,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
143
148
|
requirements:
|
|
144
149
|
- - ">="
|
|
145
150
|
- !ruby/object:Gem::Version
|
|
146
|
-
version:
|
|
151
|
+
version: 3.1.0
|
|
147
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
153
|
requirements:
|
|
149
154
|
- - ">="
|
|
150
155
|
- !ruby/object:Gem::Version
|
|
151
156
|
version: '0'
|
|
152
157
|
requirements: []
|
|
153
|
-
|
|
154
|
-
rubygems_version: 2.7.6.2
|
|
155
|
-
signing_key:
|
|
158
|
+
rubygems_version: 3.7.1
|
|
156
159
|
specification_version: 4
|
|
157
160
|
summary: ActiveAdmin import based on activerecord-import gem.
|
|
158
|
-
test_files:
|
|
159
|
-
- spec/fixtures/files/author.csv
|
|
160
|
-
- spec/fixtures/files/author_broken_header.csv
|
|
161
|
-
- spec/fixtures/files/author_invalid.csv
|
|
162
|
-
- spec/fixtures/files/authors.csv
|
|
163
|
-
- spec/fixtures/files/authors_bom.csv
|
|
164
|
-
- spec/fixtures/files/authors_invalid_db.csv
|
|
165
|
-
- spec/fixtures/files/authors_invalid_model.csv
|
|
166
|
-
- spec/fixtures/files/authors_no_headers.csv
|
|
167
|
-
- spec/fixtures/files/authors_values_exceeded_headers.csv
|
|
168
|
-
- spec/fixtures/files/authors_win1251_win_endline.csv
|
|
169
|
-
- spec/fixtures/files/authors_with_ids.csv
|
|
170
|
-
- spec/fixtures/files/authors_with_semicolons.csv
|
|
171
|
-
- spec/fixtures/files/authors_with_tabs.tsv
|
|
172
|
-
- spec/fixtures/files/empty.csv
|
|
173
|
-
- spec/fixtures/files/only_headers.csv
|
|
174
|
-
- spec/fixtures/files/posts.csv
|
|
175
|
-
- spec/fixtures/files/posts_for_author.csv
|
|
176
|
-
- spec/fixtures/files/posts_for_author_no_headers.csv
|
|
177
|
-
- spec/import_result_spec.rb
|
|
178
|
-
- spec/import_spec.rb
|
|
179
|
-
- spec/model_spec.rb
|
|
180
|
-
- spec/spec_helper.rb
|
|
181
|
-
- spec/support/active_model_lint.rb
|
|
182
|
-
- spec/support/admin.rb
|
|
183
|
-
- spec/support/rails_template.rb
|
|
161
|
+
test_files: []
|
data/CHANGELOG.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
## [5.0.0] - 2021-11-16
|
|
3
|
-
- Ruby 3 compatibility added #190 | @clinejj
|
|
4
|
-
- Support for a non UTF-8 file when zip uploading #185| @naokirin
|
|
5
|
-
- Rails 6 supported #183 | @pnghai
|
|
6
|
-
- Drop ruby 2.4 support #192 | @Fivell
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## [4.2.0] - 2020-02-05
|
|
10
|
-
- generic exception for import added #175 | @linqueta
|
|
11
|
-
|
|
12
|
-
## [4.1.2] - 2019-12-16
|
|
13
|
-
- allow application/octet-stream content-type #172 | @dmitry-sinina
|
|
14
|
-
- Allow activerecord-import >= 0.27 #171 | @sagium
|
|
15
|
-
|
|
16
|
-
## [4.1.1] - 2019-09-20
|
|
17
|
-
- Fix column slicing #168 | @doredesign
|
|
18
|
-
- Handle errors on base #163
|
|
19
|
-
|
|
20
|
-
## [4.1.0] - 2019-01-15
|
|
21
|
-
- Upgrade dependencies: `activerecord-import` to >=0.27.1 | @jkowens
|
|
22
|
-
|
|
23
|
-
## [4.0.0] - 2018-07-19
|
|
24
|
-
- Adde German translation | @morris-frank
|
|
25
|
-
- Remove support for Ruby 2.1 and Rails 4
|
|
26
|
-
|
|
27
|
-
## [3.1.0] - 2018-04-10
|
|
28
|
-
- Lower dependency of ActiveAdmin to >= 1.0.0.pre2
|
|
29
|
-
- Add possibility to skip columns/values in CSV (batch_slice_columns method)
|
|
30
|
-
|
|
31
|
-
[Unreleased]: https://github.com/activeadmin-plugins/active_admin_import/compare/v4.0.0...HEAD
|
|
32
|
-
[4.0.0]: https://github.com/activeadmin-plugins/active_admin_import/compare/v3.1.0...v4.0.0
|
|
33
|
-
[3.1.0]: https://github.com/activeadmin-plugins/active_admin_import/compare/3.0.0...v3.1.0
|