activerecord-import 2.2.0 → 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/CHANGELOG.md +124 -0
- data/README.markdown +99 -32
- data/lib/activerecord-import/active_record/adapters/mysql2_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/active_record/adapters/trilogy_proxy_adapter.rb +8 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +58 -0
- data/lib/activerecord-import/adapters/active_record_proxy_adapter.rb +19 -0
- data/lib/activerecord-import/adapters/mysql2_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +70 -13
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +10 -52
- data/lib/activerecord-import/adapters/postgresql_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +7 -3
- data/lib/activerecord-import/adapters/sqlite3_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/adapters/trilogy_proxy_adapter.rb +9 -0
- data/lib/activerecord-import/aliases.rb +42 -0
- data/lib/activerecord-import/base.rb +2 -46
- data/lib/activerecord-import/core.rb +62 -0
- data/lib/activerecord-import/import.rb +612 -369
- data/lib/activerecord-import/synchronize.rb +28 -16
- data/lib/activerecord-import/version.rb +1 -1
- data/lib/activerecord-import/without_aliases.rb +17 -0
- metadata +19 -211
- data/.github/workflows/test.yaml +0 -165
- data/.gitignore +0 -37
- data/.rubocop.yml +0 -118
- data/.rubocop_todo.yml +0 -30
- data/Brewfile +0 -5
- data/Dockerfile +0 -23
- data/Gemfile +0 -63
- data/Rakefile +0 -75
- data/activerecord-import.gemspec +0 -28
- data/benchmarks/README +0 -32
- data/benchmarks/benchmark.rb +0 -72
- data/benchmarks/lib/base.rb +0 -143
- data/benchmarks/lib/cli_parser.rb +0 -111
- data/benchmarks/lib/float.rb +0 -17
- data/benchmarks/lib/mysql2_benchmark.rb +0 -21
- data/benchmarks/lib/output_to_csv.rb +0 -21
- data/benchmarks/lib/output_to_html.rb +0 -66
- data/benchmarks/models/test_innodb.rb +0 -5
- data/benchmarks/models/test_memory.rb +0 -5
- data/benchmarks/models/test_myisam.rb +0 -5
- data/benchmarks/schema/mysql2_schema.rb +0 -18
- data/docker-compose.yml +0 -34
- data/gemfiles/5.2.gemfile +0 -4
- data/gemfiles/6.0.gemfile +0 -4
- data/gemfiles/6.1.gemfile +0 -4
- data/gemfiles/7.0.gemfile +0 -4
- data/gemfiles/7.1.gemfile +0 -3
- data/gemfiles/7.2.gemfile +0 -3
- data/gemfiles/8.0.gemfile +0 -3
- data/test/adapters/janus_mysql2.rb +0 -3
- data/test/adapters/janus_trilogy.rb +0 -11
- data/test/adapters/jdbcmysql.rb +0 -3
- data/test/adapters/jdbcpostgresql.rb +0 -3
- data/test/adapters/jdbcsqlite3.rb +0 -3
- data/test/adapters/makara_postgis.rb +0 -3
- data/test/adapters/mysql2.rb +0 -3
- data/test/adapters/mysql2_makara.rb +0 -3
- data/test/adapters/mysql2_proxy.rb +0 -3
- data/test/adapters/mysql2spatial.rb +0 -3
- data/test/adapters/postgis.rb +0 -3
- data/test/adapters/postgresql.rb +0 -3
- data/test/adapters/postgresql_makara.rb +0 -3
- data/test/adapters/postgresql_proxy.rb +0 -3
- data/test/adapters/seamless_database_pool.rb +0 -3
- data/test/adapters/spatialite.rb +0 -3
- data/test/adapters/sqlite3.rb +0 -3
- data/test/adapters/trilogy.rb +0 -11
- data/test/database.yml.sample +0 -73
- data/test/github/database.yml +0 -85
- data/test/import_test.rb +0 -970
- data/test/janus_mysql2/import_test.rb +0 -8
- data/test/janus_trilogy/import_test.rb +0 -7
- data/test/jdbcmysql/import_test.rb +0 -7
- data/test/jdbcpostgresql/import_test.rb +0 -6
- data/test/jdbcsqlite3/import_test.rb +0 -6
- data/test/makara_postgis/import_test.rb +0 -10
- data/test/models/account.rb +0 -5
- data/test/models/alarm.rb +0 -4
- data/test/models/animal.rb +0 -8
- data/test/models/author.rb +0 -9
- data/test/models/bike_maker.rb +0 -10
- data/test/models/book.rb +0 -18
- data/test/models/car.rb +0 -5
- data/test/models/card.rb +0 -5
- data/test/models/chapter.rb +0 -6
- data/test/models/composite_book.rb +0 -19
- data/test/models/composite_chapter.rb +0 -12
- data/test/models/customer.rb +0 -18
- data/test/models/deck.rb +0 -8
- data/test/models/dictionary.rb +0 -6
- data/test/models/discount.rb +0 -5
- data/test/models/end_note.rb +0 -6
- data/test/models/group.rb +0 -5
- data/test/models/order.rb +0 -17
- data/test/models/playing_card.rb +0 -4
- data/test/models/promotion.rb +0 -5
- data/test/models/question.rb +0 -5
- data/test/models/rule.rb +0 -5
- data/test/models/tag.rb +0 -12
- data/test/models/tag_alias.rb +0 -11
- data/test/models/topic.rb +0 -31
- data/test/models/user.rb +0 -5
- data/test/models/user_token.rb +0 -6
- data/test/models/vendor.rb +0 -9
- data/test/models/widget.rb +0 -33
- data/test/mysql2/import_test.rb +0 -7
- data/test/mysql2_makara/import_test.rb +0 -8
- data/test/mysql2_proxy/import_test.rb +0 -6
- data/test/mysqlspatial2/import_test.rb +0 -8
- data/test/postgis/import_test.rb +0 -10
- data/test/postgresql/import_test.rb +0 -6
- data/test/postgresql_proxy/import_test.rb +0 -6
- data/test/schema/generic_schema.rb +0 -230
- data/test/schema/jdbcpostgresql_schema.rb +0 -3
- data/test/schema/mysql2_schema.rb +0 -21
- data/test/schema/postgis_schema.rb +0 -3
- data/test/schema/postgresql_schema.rb +0 -94
- data/test/schema/sqlite3_schema.rb +0 -15
- data/test/schema/version.rb +0 -12
- data/test/sqlite3/import_test.rb +0 -6
- data/test/support/active_support/test_case_extensions.rb +0 -73
- data/test/support/assertions.rb +0 -75
- data/test/support/factories.rb +0 -66
- data/test/support/generate.rb +0 -31
- data/test/support/mysql/import_examples.rb +0 -97
- data/test/support/postgresql/import_examples.rb +0 -642
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +0 -45
- data/test/support/shared_examples/on_duplicate_key_update.rb +0 -437
- data/test/support/shared_examples/recursive_import.rb +0 -352
- data/test/support/sqlite3/import_examples.rb +0 -232
- data/test/synchronize_test.rb +0 -43
- data/test/test_helper.rb +0 -91
- data/test/trilogy/import_test.rb +0 -7
- data/test/value_sets_bytes_parser_test.rb +0 -106
- data/test/value_sets_records_parser_test.rb +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44eca8fc38325819cfc2eeb8c9a6151182f5de96135af6ad3ae1cc145c5f7740
|
|
4
|
+
data.tar.gz: 9f6ef423efde4813da41b8eb5ba218d4166bb3a86beba83d72b4bbf9e70af27f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f534b0b2a47c29484b8957109794ab8924c758993e93ecd871104f97ce8402856999c40d1e845ff7a2857fb92e12291a0d93259a9bdd17d4b60f81cb20ac5e42
|
|
7
|
+
data.tar.gz: f9e8de71d80edd76c978a1d85807b6a94247da8366dfda54a4c1e2c01ed25c31767d836c2ec487d39377379492dd896f320a986c1a158cfda7f810e7b3ba3a80
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
+
## Changes in 3.0.0
|
|
2
|
+
|
|
3
|
+
### Breaking Changes
|
|
4
|
+
|
|
5
|
+
* Drop support for Ruby < 3.1 and ActiveRecord < 6.1. via #918 and #943.
|
|
6
|
+
* MariaDB 10.5+ now imports with `INSERT ... RETURNING`, as PostgreSQL does. Imports return
|
|
7
|
+
`ids` and `results`, imported models get their ids and are marked persisted, and
|
|
8
|
+
`recursive: true` imports associations (it was ignored on MariaDB before). MariaDB doesn't
|
|
9
|
+
report affected rows for `INSERT ... RETURNING`, so `affected_rows` is now `nil` on MariaDB
|
|
10
|
+
10.5+ unless `no_returning: true` is passed. As on PostgreSQL, `on_duplicate_key_ignore` is
|
|
11
|
+
ignored for recursive imports. MySQL and MariaDB before 10.5 are unchanged. via #926.
|
|
12
|
+
* When only some rows have a value for a column the database fills in (a uuid or other primary
|
|
13
|
+
key with a default function, or a column with a default function), the other rows now get the
|
|
14
|
+
database's default instead of `NULL` on PostgreSQL and MySQL/MariaDB, and assigned primary keys
|
|
15
|
+
are no longer dropped when the first model doesn't have one. SQLite can't use `DEFAULT` in
|
|
16
|
+
`VALUES`, so such a mixed import now raises `ArgumentError` there. Fixes #702, #777, #805 and
|
|
17
|
+
#827 via #929.
|
|
18
|
+
* `on_duplicate_key_update: :all` and `columns: :all` no longer update the timestamps recorded on
|
|
19
|
+
create (`created_at`, `created_on`) when import records timestamps. via #927.
|
|
20
|
+
* Recursive imports now also import existing records that were assigned to a new parent, so they're
|
|
21
|
+
linked to it as `save` would do. Without `on_duplicate_key_update` that raises a duplicate key
|
|
22
|
+
error, where the records used to be silently left unlinked. Fixes #804 via #939.
|
|
23
|
+
* Importing with `synchronize` now keeps the synchronized models' `previous_changes`, rather than
|
|
24
|
+
clearing them. Fixes #738 via #933.
|
|
25
|
+
|
|
26
|
+
### New Features
|
|
27
|
+
|
|
28
|
+
* Add `require "activerecord-import/without_aliases"` to load the library without the `import` and
|
|
29
|
+
`import!` aliases, for gems such as `elasticsearch-model` that define their own `import`. Use
|
|
30
|
+
`bulk_import`/`bulk_import!` instead. Fixes #592 via #915.
|
|
31
|
+
* `import` accepts array-like arguments such as an `ActiveRecord::Relation` or a `Set`, and invalid
|
|
32
|
+
arguments now raise an error saying what was received. Fixes #785 via #935.
|
|
33
|
+
* Annotations from the current scope (`Model.annotate(...)`) are added to the INSERT statements as
|
|
34
|
+
SQL comments. Fixes #872 via #937.
|
|
35
|
+
* When activerecord-import is required after the database connection is established, import now
|
|
36
|
+
loads the adapter's import support itself instead of inserting one row at a time. Adapters
|
|
37
|
+
without bulk import support log a warning when import falls back to row-by-row inserts.
|
|
38
|
+
via #925 and #941.
|
|
39
|
+
* Validate imported models with `valid?`, so `around` validate callbacks are supported. via #919.
|
|
40
|
+
|
|
41
|
+
### Fixes
|
|
42
|
+
|
|
43
|
+
* Report `affected_rows` with the mysql2 adapter. It was always `nil` with mysql2, and only
|
|
44
|
+
reported with trilogy. via #944.
|
|
45
|
+
* Clear the query cache after MySQL imports on ActiveRecord < 7.1. via #926.
|
|
46
|
+
* Don't import the associations of models that failed validation. Fixes #857. Thanks to
|
|
47
|
+
@jacob-carlborg-apoex via #858.
|
|
48
|
+
* Fix `NoMethodError` in `returning_selections` when there's no model. Thanks to @mirelon via #892.
|
|
49
|
+
* Don't mark models that failed validation as persisted when `track_validation_failures` is set.
|
|
50
|
+
Fixes #888. Thanks to @OskarEichler via #903.
|
|
51
|
+
* Leave models unchanged when `all_or_none` aborts an import. Thanks to @OskarEichler via #904.
|
|
52
|
+
* Include the owner's foreign key and polymorphic type when importing models with explicit columns
|
|
53
|
+
through an association. Thanks to @OskarEichler via #905.
|
|
54
|
+
* Support string hash keys in association imports. Thanks to @OskarEichler via #909.
|
|
55
|
+
* Use the canonical polymorphic name (e.g. for STI owners) in association imports. Thanks to
|
|
56
|
+
@OskarEichler via #910.
|
|
57
|
+
* Raise `ArgumentError` for rows with fewer values than columns, before running any SQL. Thanks
|
|
58
|
+
to @OskarEichler via #912.
|
|
59
|
+
* Quote PostgreSQL `RETURNING` column names and generated conflict targets. Thanks to
|
|
60
|
+
@OskarEichler via #907 and #908.
|
|
61
|
+
* Skip uniqueness validations on associated records unless `validate_uniqueness: true`. Fixes #841
|
|
62
|
+
via #916.
|
|
63
|
+
* Use each model's own class for `options[:model]` in recursive imports. via #920.
|
|
64
|
+
* Leave columns the database populates (e.g. a uuid with a default function) out of the INSERT
|
|
65
|
+
when no imported model assigns them, as `save` does. via #921.
|
|
66
|
+
* Don't follow an attribute alias when its name is also a column. via #922.
|
|
67
|
+
* Don't modify the options hash passed to `import!`. via #924.
|
|
68
|
+
* Don't send a `RETURNING` clause on PostgreSQL with `no_returning: true`. Fixes #826 via #930.
|
|
69
|
+
* Support `returning: :id` with composite primary keys that include an `id` column. Fixes #842
|
|
70
|
+
via #931.
|
|
71
|
+
* Use the association's `primary_key` for foreign keys in recursive imports. Fixes #878 via #932.
|
|
72
|
+
* Serialize values of STI subclass models with the subclass's attribute types, e.g. for enums
|
|
73
|
+
defined only on the subclass. Fixes #789 via #934.
|
|
74
|
+
* Fix `NoMethodError` when filling in composite `belongs_to` foreign keys. via #938.
|
|
75
|
+
|
|
76
|
+
### Performance
|
|
77
|
+
|
|
78
|
+
* Match synchronized records in linear rather than quadratic time. Thanks to @OskarEichler via #902.
|
|
79
|
+
* Collect returned ids and results across batches without recopying them. Thanks to @OskarEichler
|
|
80
|
+
via #906.
|
|
81
|
+
|
|
82
|
+
### Development
|
|
83
|
+
|
|
84
|
+
* Test against MariaDB 10.11 and 11.8 in CI. via #926.
|
|
85
|
+
* Run the composite primary key tests on ActiveRecord 7.1, and speed up the JRuby CI job.
|
|
86
|
+
via #923, #928 and #945.
|
|
87
|
+
* Run the database tests locally against Docker without a hand-written `test/database.yml`.
|
|
88
|
+
via #945.
|
|
89
|
+
* Document the memory use of large imports and what gets logged. via #940.
|
|
90
|
+
* Split `import_helper` into smaller steps. via #947.
|
|
91
|
+
|
|
92
|
+
## Changes in 2.3.0
|
|
93
|
+
|
|
94
|
+
### New Features
|
|
95
|
+
|
|
96
|
+
* Add Support for trilogy_proxy from [active_record_proxy_adapters](https://rubygems.org/gems/active_record_proxy_adapters) by @mateuscruz via #887.
|
|
97
|
+
* Add Support for sqlite3_proxy from [active_record_proxy_adapters](https://rubygems.org/gems/active_record_proxy_adapters) by @mateuscruz via #887.
|
|
98
|
+
* Add affected rows support for the MySQL adapter. `ActiveRecord::Import::Result` now carries an
|
|
99
|
+
`affected_rows` member, which reports the number of rows the server actually reported as
|
|
100
|
+
affected when the adapter exposes it (`nil` otherwise). This is most useful with
|
|
101
|
+
`on_duplicate_key_update`, where the count distinguishes inserted from updated rows.
|
|
102
|
+
Thanks to @ehasrouni via #882 and #885.
|
|
103
|
+
|
|
104
|
+
### Fixes
|
|
105
|
+
|
|
106
|
+
* Fix `NoMethodError: undefined method 'validation_context='` when importing with validations
|
|
107
|
+
against Rails main (8.2.0.alpha). Rails removed ActiveModel's private `validation_context=`
|
|
108
|
+
writer in rails/rails@0f9d1270834a6407a59637650bf910d8ae826169 in favor of a validation
|
|
109
|
+
context object reachable through `context_for_validation`. `ActiveRecord::Import::Validator`
|
|
110
|
+
now sets and restores the validation context through whichever API the loaded ActiveModel
|
|
111
|
+
provides. Thanks to @yu-yaba via #900.
|
|
112
|
+
* Fix adapter-specific import support (e.g. `on_duplicate_key_update`) never loading for
|
|
113
|
+
a connection established via `connects_to`/`ActiveRecord::ConnectionAdapters::
|
|
114
|
+
ConnectionHandler#establish_connection` instead of `ActiveRecord::Base.establish_connection`
|
|
115
|
+
directly - most commonly hit with a Rails multi-database secondary connection. The lazy
|
|
116
|
+
adapter loader now hooks `ConnectionHandler#establish_connection` instead of
|
|
117
|
+
`ActiveRecord::Base.establish_connection`, so it fires for every connection regardless of
|
|
118
|
+
how it was established. Thanks to @mateuscruz via #886.
|
|
119
|
+
* Fix `active_record_proxy_adapters` support, which was not loading the import adapter for
|
|
120
|
+
proxied connections. Thanks to @mateuscruz via #880.
|
|
121
|
+
* Use `with_connection` instead of `ActiveRecord::Base.connection`, which is deprecated as of
|
|
122
|
+
ActiveRecord 7.2. Thanks to @nerdrew via #875 and #876.
|
|
123
|
+
* Reduce the size of the published gem by excluding test files. Thanks to @yuri-zubov via #893.
|
|
124
|
+
|
|
1
125
|
## Changes in 2.2.0
|
|
2
126
|
|
|
3
127
|
### New Features
|
data/README.markdown
CHANGED
|
@@ -37,6 +37,7 @@ The gem provides the following high-level features:
|
|
|
37
37
|
* [ActiveRecord Models](#activerecord-models)
|
|
38
38
|
* [Batching](#batching)
|
|
39
39
|
* [Recursive](#recursive)
|
|
40
|
+
* [Annotating the SQL](#annotating-the-sql)
|
|
40
41
|
* [Options](#options)
|
|
41
42
|
* [Duplicate Key Ignore](#duplicate-key-ignore)
|
|
42
43
|
* [Duplicate Key Update](#duplicate-key-update)
|
|
@@ -44,6 +45,7 @@ The gem provides the following high-level features:
|
|
|
44
45
|
* [Counter Cache](#counter-cache)
|
|
45
46
|
* [ActiveRecord Timestamps](#activerecord-timestamps)
|
|
46
47
|
* [Callbacks](#callbacks)
|
|
48
|
+
* [Logging](#logging)
|
|
47
49
|
* [Supported Adapters](#supported-adapters)
|
|
48
50
|
* [Additional Adapters](#additional-adapters)
|
|
49
51
|
* [Requiring](#requiring)
|
|
@@ -121,20 +123,20 @@ Book.import values
|
|
|
121
123
|
The `import` method can take an array of column names and an array of hash objects. The column names are used to determine what fields of data should be imported. The following example will only import books with the `title` field:
|
|
122
124
|
|
|
123
125
|
```ruby
|
|
124
|
-
|
|
126
|
+
values = [
|
|
125
127
|
{ title: "Book 1", author: "George Orwell" },
|
|
126
128
|
{ title: "Book 2", author: "Bob Jones" }
|
|
127
129
|
]
|
|
128
130
|
columns = [ :title ]
|
|
129
131
|
|
|
130
132
|
# without validations
|
|
131
|
-
Book.import columns,
|
|
133
|
+
Book.import columns, values, validate: false
|
|
132
134
|
|
|
133
135
|
# with validations
|
|
134
|
-
Book.import columns,
|
|
136
|
+
Book.import columns, values, validate: true
|
|
135
137
|
|
|
136
138
|
# when not specified :validate defaults to true
|
|
137
|
-
Book.import columns,
|
|
139
|
+
Book.import columns, values
|
|
138
140
|
|
|
139
141
|
# result in table books
|
|
140
142
|
# title | author
|
|
@@ -231,6 +233,16 @@ columns = [ :title ]
|
|
|
231
233
|
Book.import columns, books, batch_size: 2
|
|
232
234
|
```
|
|
233
235
|
|
|
236
|
+
`batch_size` only controls how many rows go into each INSERT statement. The whole import is still prepared up front: every record or row passed to `import`, and the SQL for all of them, is held in memory until the import finishes. For very large imports, load and import the data in chunks, so only one chunk is in memory at a time:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
Source.find_in_batches(batch_size: 1_000) do |sources|
|
|
240
|
+
Book.import sources.map { |source| Book.new(title: source.title, author: source.author) }
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Each `import` call validates, inserts and returns its own result, so options like `all_or_none` apply to each chunk rather than to all of the data.
|
|
245
|
+
|
|
234
246
|
If your import is particularly large or slow (possibly due to [callbacks](#callbacks)) whilst batch importing, you might want a way to report back on progress. This is supported by passing a callable as the `batch_progress` option. e.g:
|
|
235
247
|
|
|
236
248
|
```ruby
|
|
@@ -246,7 +258,7 @@ Book.import columns, books, batch_size: 2, batch_progress: my_proc
|
|
|
246
258
|
#### Recursive
|
|
247
259
|
|
|
248
260
|
> **Note**
|
|
249
|
-
> This only works with PostgreSQL and ActiveRecord objects. This won't work with hashes or arrays as recursive inputs.
|
|
261
|
+
> This only works with PostgreSQL and MariaDB 10.5+, and with ActiveRecord objects. This won't work with hashes or arrays as recursive inputs.
|
|
250
262
|
|
|
251
263
|
Assume that Books <code>has_many</code> Reviews.
|
|
252
264
|
|
|
@@ -260,12 +272,25 @@ end
|
|
|
260
272
|
Book.import books, recursive: true
|
|
261
273
|
```
|
|
262
274
|
|
|
275
|
+
#### Annotating the SQL
|
|
276
|
+
|
|
277
|
+
To add SQL comments to an import, import through a relation annotated with ActiveRecord's [`annotate`](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-annotate), or within its `scoping` block. The comments are added to every INSERT statement, including when the import is batched or split into several statements.
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
Book.annotate("nightly import").import books
|
|
281
|
+
# INSERT INTO "books" (...) VALUES (...) /* nightly import */
|
|
282
|
+
|
|
283
|
+
Book.annotate("nightly import").scoping do
|
|
284
|
+
Book.import books
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
263
288
|
### Options
|
|
264
289
|
|
|
265
290
|
Key | Options | Default | Description
|
|
266
291
|
------------------------- | --------------------- | ------------------ | -----------
|
|
267
292
|
:validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
|
|
268
|
-
:validate_uniqueness | `true`/`false` | `false` | Whether or not to run ActiveRecord uniqueness validations. Beware this will incur an sql query per-record (N+1 queries). (requires `>= v0.27.0`).
|
|
293
|
+
:validate_uniqueness | `true`/`false` | `false` | Whether or not to run ActiveRecord uniqueness validations, including on associated records that are validated with the model. Beware this will incur an sql query per-record (N+1 queries). (requires `>= v0.27.0`).
|
|
269
294
|
:validate_with_context | `Symbol` |`:create`/`:update` | Allows passing an ActiveModel validation context for each model. Default is `:create` for new records and `:update` for existing ones.
|
|
270
295
|
:track_validation_failures| `true`/`false` | `false` | When this is set to true, `failed_instances` will be an array of arrays, with each inner array having the form `[:index_in_dataset, :object_with_errors]`
|
|
271
296
|
:on_duplicate_key_ignore | `true`/`false` | `false` | Allows skipping records with duplicate keys. See [here](#duplicate-key-ignore) for more details.
|
|
@@ -273,9 +298,9 @@ Key | Options | Default | Descrip
|
|
|
273
298
|
:on_duplicate_key_update | :all, `Array`, `Hash` | N/A | Allows upsert logic to be used. See [here](#duplicate-key-update) for more details.
|
|
274
299
|
:synchronize | `Array` | N/A | An array of ActiveRecord instances. This synchronizes existing instances in memory with updates from the import.
|
|
275
300
|
:timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
|
|
276
|
-
:recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
|
|
301
|
+
:recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL and MariaDB 10.5+ only).
|
|
277
302
|
:recursive_on_duplicate_key_update | `Hash` | N/A | Allows upsert logic to be used for recursive associations. The hash key is the association name and the value has the same options as `:on_duplicate_key_update`. See [here](#duplicate-key-update) for more details.
|
|
278
|
-
:batch_size | `Integer` | total # of records | Max number of records to insert per
|
|
303
|
+
:batch_size | `Integer` | total # of records | Max number of records to insert per INSERT statement. See [Batching](#batching).
|
|
279
304
|
:raise_error | `true`/`false` | `false` | Raises an exception at the first invalid record. This means there will not be a result object returned. The `import!` method is a shortcut for this.
|
|
280
305
|
:all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
|
|
281
306
|
|
|
@@ -296,7 +321,7 @@ book.reload.title # => "Book1" (stayed the same)
|
|
|
296
321
|
book.reload.author # => "George Orwell" (stayed the same)
|
|
297
322
|
```
|
|
298
323
|
|
|
299
|
-
The option `:on_duplicate_key_ignore` is bypassed when `:recursive` is enabled for [PostgreSQL imports](https://
|
|
324
|
+
The option `:on_duplicate_key_ignore` is bypassed when `:recursive` is enabled for [PostgreSQL and MariaDB 10.5+ imports](https://rubydoc.info/gems/activerecord-import/ActiveRecord/Base#label-Options) (`:recursive` is not supported with other databases).
|
|
300
325
|
|
|
301
326
|
#### Duplicate Key Update
|
|
302
327
|
|
|
@@ -416,7 +441,7 @@ Book.import books, validate_uniqueness: true
|
|
|
416
441
|
|
|
417
442
|
### Return Info
|
|
418
443
|
|
|
419
|
-
The `import` method returns a `Result` object that responds to `failed_instances` and `num_inserts`. Additionally, for users of Postgres
|
|
444
|
+
The `import` method returns a `Result` object that responds to `failed_instances` and `num_inserts`. Additionally, for users of Postgres and MariaDB 10.5+, there will be two arrays `ids` and `results` that can be accessed. These are populated with `INSERT ... RETURNING`, which also sets the ids of imported models. On MariaDB this means `affected_rows` isn't available, since MariaDB doesn't report it for `INSERT ... RETURNING`; pass `no_returning: true` to get `affected_rows` instead of `ids`.
|
|
420
445
|
|
|
421
446
|
```ruby
|
|
422
447
|
articles = [
|
|
@@ -434,11 +459,11 @@ demo.num_inserts
|
|
|
434
459
|
=> 1,
|
|
435
460
|
|
|
436
461
|
demo.ids
|
|
437
|
-
=> ["1", "2"] # for Postgres
|
|
462
|
+
=> ["1", "2"] # for Postgres and MariaDB 10.5+
|
|
438
463
|
=> [] # for other DBs
|
|
439
464
|
|
|
440
465
|
demo.results
|
|
441
|
-
=> ["First Article", "Second Article"] # for Postgres
|
|
466
|
+
=> ["First Article", "Second Article"] # for Postgres and MariaDB 10.5+
|
|
442
467
|
=> [] # for other DBs
|
|
443
468
|
```
|
|
444
469
|
|
|
@@ -499,18 +524,29 @@ end
|
|
|
499
524
|
Book.import valid_books, validate: false
|
|
500
525
|
```
|
|
501
526
|
|
|
527
|
+
### Logging
|
|
528
|
+
|
|
529
|
+
Imports are logged like ActiveRecord's own queries, named `<Model> Create Many`. The imported values are part of the SQL rather than bind parameters, so a logged INSERT includes every value it imports. That can make for very long log lines, and ActiveRecord's `filter_attributes` doesn't apply to them, so the values of sensitive columns appear in the log too. ActiveRecord's own `insert_all` and `upsert_all` log the same way.
|
|
530
|
+
|
|
531
|
+
If that's a concern, silence the logger around imports. For example, with Rails' logger:
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
ActiveRecord::Base.logger.silence { Book.import books }
|
|
535
|
+
```
|
|
536
|
+
|
|
502
537
|
### Supported Adapters
|
|
503
538
|
|
|
504
539
|
The following database adapters are currently supported:
|
|
505
540
|
|
|
506
541
|
* MySQL - supports core import functionality plus on duplicate key update support (included in activerecord-import 0.1.0 and higher)
|
|
507
542
|
* MySQL2 - supports core import functionality plus on duplicate key update support (included in activerecord-import 0.2.0 and higher)
|
|
543
|
+
* MariaDB - supported through the MySQL2 and Trilogy adapters, with the same functionality as MySQL. MariaDB 10.5+ also supports `ids`, `returning` and recursive imports.
|
|
508
544
|
* PostgreSQL - supports core import functionality (included in activerecord-import 0.1.0 and higher)
|
|
509
545
|
* SQLite3 - supports core import functionality (included in activerecord-import 0.1.0 and higher)
|
|
510
|
-
* Oracle - supports core import functionality through DML trigger (available as an external gem: [activerecord-import-oracle_enhanced](https://github.com/keeguon/activerecord-import-oracle_enhanced)
|
|
511
|
-
* SQL Server - supports core import functionality (available as an external gem: [activerecord-import-sqlserver](https://github.com/keeguon/activerecord-import-sqlserver)
|
|
546
|
+
* Oracle - supports core import functionality through DML trigger (available as an external gem: [activerecord-import-oracle_enhanced](https://github.com/keeguon/activerecord-import-oracle_enhanced))
|
|
547
|
+
* SQL Server - supports core import functionality (available as an external gem: [activerecord-import-sqlserver](https://github.com/keeguon/activerecord-import-sqlserver))
|
|
512
548
|
|
|
513
|
-
If your adapter isn't listed here, please consider creating an external gem as described in the README to provide support. If you do, feel free to update this
|
|
549
|
+
If your adapter isn't listed here, please consider creating an external gem as described in the README to provide support. If you do, feel free to update this README to include a link to the new adapter's repository!
|
|
514
550
|
|
|
515
551
|
To test which features are supported by your adapter, use the following methods on a model class:
|
|
516
552
|
* `supports_import?(*args)`
|
|
@@ -550,12 +586,15 @@ You may want to manually load activerecord-import for one reason or another. Fir
|
|
|
550
586
|
gem 'activerecord-import', require: false
|
|
551
587
|
```
|
|
552
588
|
|
|
553
|
-
This will allow you to load up activerecord-import in the file or files where you are using it and only load the parts you need
|
|
554
|
-
If you are doing this within Rails and ActiveRecord has established a database connection (such as within a controller), you will need to do extra initialization work:
|
|
589
|
+
This will allow you to load up activerecord-import in the file or files where you are using it and only load the parts you need:
|
|
555
590
|
|
|
556
591
|
```ruby
|
|
557
592
|
require 'activerecord-import/base'
|
|
558
|
-
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
This works even if ActiveRecord has already established a database connection (such as within a controller): the import support for the connection's adapter (postgresql, mysql2, sqlite3, etc) is loaded the first time you import. If your adapter goes by a name activerecord-import doesn't recognize, load its support yourself:
|
|
596
|
+
|
|
597
|
+
```ruby
|
|
559
598
|
require 'activerecord-import/active_record/adapters/postgresql_adapter'
|
|
560
599
|
```
|
|
561
600
|
|
|
@@ -599,6 +638,16 @@ When rubygems pushes the `lib` folder onto the load path a `require` will now fi
|
|
|
599
638
|
|
|
600
639
|
Activerecord-Import adds the `.import` method onto `ActiveRecord::Base`. There are other gems, such as `elasticsearch-rails`, that do the same thing. In conflicts such as this, there is an aliased method named `.bulk_import` that can be used interchangeably.
|
|
601
640
|
|
|
641
|
+
Some gems, such as `elasticsearch-model`, won't define their own `.import` on a model if one already exists. To load activerecord-import without the `import` and `import!` aliases, so only `bulk_import` and `bulk_import!` are defined, require it like so:
|
|
642
|
+
|
|
643
|
+
```ruby
|
|
644
|
+
gem 'activerecord-import', require: 'activerecord-import/without_aliases'
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
This takes precedence even if `activerecord-import` or `activerecord-import/base` is also required, for example by another gem, regardless of the order they're required in. To confirm the aliases aren't defined, check that `ActiveRecord::Base.respond_to?(:import)` returns `false` in a console.
|
|
648
|
+
|
|
649
|
+
If you're loading activerecord-import manually (see [Manually Loading](#manually-loading)), require `activerecord-import/without_aliases` in place of `activerecord-import/base`.
|
|
650
|
+
|
|
602
651
|
If you are using the `apartment` gem, there is a weird triple interaction between that gem, `activerecord-import`, and `activerecord` involving caching of the `sequence_name` of a model. This can be worked around by explicitly setting this value within the model. For example:
|
|
603
652
|
|
|
604
653
|
```ruby
|
|
@@ -624,7 +673,7 @@ See https://github.com/zdennis/activerecord-import/issues/233 for further discus
|
|
|
624
673
|
|
|
625
674
|
### More Information
|
|
626
675
|
|
|
627
|
-
For more information on Activerecord-Import please see its
|
|
676
|
+
For more information on Activerecord-Import please see its docs: https://rubydoc.info/gems/activerecord-import/ActiveRecord/Base
|
|
628
677
|
|
|
629
678
|
To document new information, please add to the README instead of the wiki. See https://github.com/zdennis/activerecord-import/issues/397 for discussion.
|
|
630
679
|
|
|
@@ -632,22 +681,36 @@ To document new information, please add to the README instead of the wiki. See h
|
|
|
632
681
|
|
|
633
682
|
#### Running Tests
|
|
634
683
|
|
|
635
|
-
The
|
|
684
|
+
The tests run against each database adapter (`bundle exec rake -T` lists them) and against multiple ActiveRecord versions, chosen with `AR_VERSION`. The SQLite tests need no setup. For MySQL, MariaDB and PostgreSQL, the quickest way to get databases is Docker (see [Docker Setup](#docker-setup)):
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
docker compose up -d
|
|
688
|
+
rm -f Gemfile.lock
|
|
689
|
+
AR_VERSION=8.1 bundle install
|
|
690
|
+
AR_VERSION=8.1 bundle exec rake test:sqlite3 test:postgresql test:mysql2 test:trilogy
|
|
691
|
+
```
|
|
636
692
|
|
|
637
|
-
|
|
638
|
-
* modify `test/database.yml` for your database settings
|
|
639
|
-
* create databases as needed
|
|
693
|
+
Without a `test/database.yml`, the tests use `test/database.default.yml`, which connects to the Docker databases by default and can be pointed elsewhere with environment variables:
|
|
640
694
|
|
|
641
|
-
|
|
695
|
+
| Variable | Default | |
|
|
696
|
+
| --- | --- | --- |
|
|
697
|
+
| `DB_HOST` | `127.0.0.1` | Host for all databases |
|
|
698
|
+
| `DB_DATABASE` | `activerecord_import_test` | Database name |
|
|
699
|
+
| `MYSQL_HOST`, `PG_HOST` | `DB_HOST` | Host for MySQL/MariaDB or PostgreSQL |
|
|
700
|
+
| `MYSQL_PORT` | `13306` | MySQL/MariaDB port; Docker's MariaDB is on `13307` |
|
|
701
|
+
| `PG_PORT` | `15432` | PostgreSQL port |
|
|
702
|
+
| `MYSQL_USERNAME`, `MYSQL_PASSWORD` | `root`, empty | |
|
|
703
|
+
| `PG_USERNAME`, `PG_PASSWORD` | `postgres`, empty | |
|
|
642
704
|
|
|
643
|
-
|
|
705
|
+
For example, to run the MySQL tests against Docker's MariaDB, or against a MySQL server you run yourself:
|
|
644
706
|
|
|
645
707
|
```bash
|
|
646
|
-
|
|
647
|
-
AR_VERSION=
|
|
648
|
-
AR_VERSION=7.0 bundle exec rake test:postgresql test:sqlite3 test:mysql2
|
|
708
|
+
MYSQL_PORT=13307 AR_VERSION=8.1 bundle exec rake test:mysql2
|
|
709
|
+
MYSQL_PORT=3306 MYSQL_PASSWORD=secret AR_VERSION=8.1 bundle exec rake test:mysql2
|
|
649
710
|
```
|
|
650
711
|
|
|
712
|
+
The PostgreSQL tests need the `hstore`, `pgcrypto` and `uuid-ossp` extensions, which they create themselves when connecting as a superuser, and `test:postgis` also needs PostGIS. If you'd rather keep your settings in a file, copy `test/database.yml.sample` to `test/database.yml` and edit it; when that file exists it is used instead (it can also use ERB).
|
|
713
|
+
|
|
651
714
|
Once you have pushed up your changes, you can find your CI results [here](https://github.com/zdennis/activerecord-import/actions).
|
|
652
715
|
|
|
653
716
|
#### Docker Setup
|
|
@@ -657,11 +720,15 @@ Before you begin, make sure you have [Docker](https://www.docker.com/products/do
|
|
|
657
720
|
```bash
|
|
658
721
|
brew install docker && brew install docker-compose
|
|
659
722
|
```
|
|
660
|
-
##### Steps
|
|
661
723
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
724
|
+
`docker compose up -d` starts MySQL 8.0, MariaDB 11.8 and PostgreSQL with PostGIS (the image CI uses), listening on localhost ports 13306, 13307 and 15432 so they don't collide with databases you may already run. To use other ports, set `MYSQL_PORT`, `MARIADB_PORT` or `PG_PORT` when starting them, and the same variables (with `MYSQL_PORT` set to the MariaDB port to test MariaDB) when running the tests. `docker compose down -v` stops them and deletes their data.
|
|
725
|
+
|
|
726
|
+
To run the tests inside Docker instead of with a local Ruby:
|
|
727
|
+
|
|
728
|
+
1. In your terminal run `docker compose --profile app up -d --build`
|
|
729
|
+
1. Then run `docker compose exec app bash`
|
|
730
|
+
1. In that shell run `bundle install` (the image's gems are for `AR_VERSION=8.1`; to test another version, `rm Gemfile.lock` and run `AR_VERSION=7.2 bundle install`, then set `AR_VERSION` when running the tests too)
|
|
731
|
+
1. Run the mysql2 tests with `bundle exec rake test:mysql2` (set `MYSQL_HOST=mariadb` to use MariaDB)
|
|
665
732
|
|
|
666
733
|
## Issue Triage [](https://www.codetriage.com/zdennis/activerecord-import)
|
|
667
734
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/mysql2_proxy_adapter"
|
|
4
|
+
require "activerecord-import/adapters/mysql2_proxy_adapter"
|
|
5
|
+
|
|
6
|
+
ActiveSupport.on_load(:active_record_mysql2proxyadapter) do |klass|
|
|
7
|
+
klass.include(ActiveRecord::Import::Mysql2ProxyAdapter)
|
|
8
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/postgresql_proxy_adapter"
|
|
4
|
+
require "activerecord-import/adapters/postgresql_proxy_adapter"
|
|
5
|
+
|
|
6
|
+
ActiveSupport.on_load(:active_record_postgresqlproxyadapter) do |klass|
|
|
7
|
+
klass.include(ActiveRecord::Import::PostgreSQLProxyAdapter)
|
|
8
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/sqlite3_proxy_adapter"
|
|
4
|
+
require "activerecord-import/adapters/sqlite3_proxy_adapter"
|
|
5
|
+
|
|
6
|
+
ActiveSupport.on_load(:active_record_sqlite3proxyadapter) do |klass|
|
|
7
|
+
klass.include(ActiveRecord::Import::SQLite3ProxyAdapter)
|
|
8
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/trilogy_proxy_adapter"
|
|
4
|
+
require "activerecord-import/adapters/trilogy_proxy_adapter"
|
|
5
|
+
|
|
6
|
+
ActiveSupport.on_load(:active_record_trilogyproxyadapter) do |klass|
|
|
7
|
+
klass.include(ActiveRecord::Import::TrilogyProxyAdapter)
|
|
8
|
+
end
|
|
@@ -62,6 +62,59 @@ module ActiveRecord::Import::AbstractAdapter
|
|
|
62
62
|
post_sql_statements
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Returns the selections for an INSERT ... RETURNING clause: the primary key
|
|
66
|
+
# and any +:returning+ columns. Known columns are quoted, anything else (such
|
|
67
|
+
# as raw SQL) is returned unchanged.
|
|
68
|
+
def returning_selections(options)
|
|
69
|
+
selections = []
|
|
70
|
+
column_names = Array(options[:model]&.column_names)
|
|
71
|
+
|
|
72
|
+
selections += Array(options[:primary_key]) if options[:primary_key].present?
|
|
73
|
+
selections += Array(options[:returning]) if options[:returning].present?
|
|
74
|
+
|
|
75
|
+
selections.map do |selection|
|
|
76
|
+
column_names.include?(selection.to_s) ? quote_column_name(selection) : selection
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Splits the values returned by an INSERT ... RETURNING into the ids and the
|
|
81
|
+
# +:returning+ results. +returned_values+ is a Hash with the returned
|
|
82
|
+
# +:values+, and the +:columns+ when more than one column was returned.
|
|
83
|
+
def ids_and_results_from_returned_values( returned_values, options ) # :nodoc:
|
|
84
|
+
if options[:returning].blank?
|
|
85
|
+
[Array(returned_values[:values]), []]
|
|
86
|
+
elsif options[:primary_key].blank?
|
|
87
|
+
options[:returning_columns] ||= returned_values[:columns]
|
|
88
|
+
[[], Array(returned_values[:values])]
|
|
89
|
+
else
|
|
90
|
+
# split primary key and returning columns
|
|
91
|
+
ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
|
|
92
|
+
[ids, results]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def split_ids_and_results( selections, options )
|
|
97
|
+
ids = []
|
|
98
|
+
returning_values = []
|
|
99
|
+
|
|
100
|
+
columns = Array(selections[:columns])
|
|
101
|
+
values = Array(selections[:values])
|
|
102
|
+
id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
|
|
103
|
+
returning_columns = columns.reject.with_index { |_, index| id_indexes.include?(index) }
|
|
104
|
+
returning_indexes = returning_columns.map { |column| columns.index(column) }
|
|
105
|
+
|
|
106
|
+
values.each do |value|
|
|
107
|
+
value_array = Array(value)
|
|
108
|
+
ids << id_indexes.map { |index| value_array[index] }
|
|
109
|
+
returning_values << returning_indexes.map { |index| value_array[index] }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
ids.map!(&:first) if id_indexes.size == 1
|
|
113
|
+
returning_values.map!(&:first) if returning_columns.size == 1
|
|
114
|
+
|
|
115
|
+
[ids, returning_values, returning_columns]
|
|
116
|
+
end
|
|
117
|
+
|
|
65
118
|
def increment_locking_column!(table_name, results, locking_column)
|
|
66
119
|
if locking_column.present?
|
|
67
120
|
results << "\"#{locking_column}\"=#{table_name}.\"#{locking_column}\"+1"
|
|
@@ -71,5 +124,10 @@ module ActiveRecord::Import::AbstractAdapter
|
|
|
71
124
|
def supports_on_duplicate_key_update?
|
|
72
125
|
false
|
|
73
126
|
end
|
|
127
|
+
|
|
128
|
+
# Whether a value in an INSERT's VALUES can be DEFAULT, to use the column's default
|
|
129
|
+
def supports_default_keyword_in_values?
|
|
130
|
+
true
|
|
131
|
+
end
|
|
74
132
|
end
|
|
75
133
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord::Import::ActiveRecordProxyAdapter
|
|
4
|
+
def insert_many(*args, **kwargs, &block)
|
|
5
|
+
sticking_to_primary { super(*args, **kwargs, &block) }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def insert(*args, **kwargs, &block)
|
|
9
|
+
sticking_to_primary { super(*args, **kwargs, &block) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def sticking_to_primary(&block)
|
|
13
|
+
ActiveRecord::Base.connected_to(role: writing_role, &block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def writing_role
|
|
17
|
+
ActiveRecord.try(:writing_role) || ActiveRecord::Base.try(:writing_role) || :writing
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "activerecord-import/adapters/mysql2_adapter"
|
|
4
|
+
require "activerecord-import/adapters/active_record_proxy_adapter"
|
|
5
|
+
|
|
6
|
+
module ActiveRecord::Import::Mysql2ProxyAdapter
|
|
7
|
+
include ActiveRecord::Import::Mysql2Adapter
|
|
8
|
+
include ActiveRecord::Import::ActiveRecordProxyAdapter
|
|
9
|
+
end
|