diff --git a/README.md b/README.md index 9114066..6274d43 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Tool | Description :timestamps |bool, tells activerecord-import to not add timestamps (if false) even if record timestamps is disabled in ActiveRecord::Base :template |custom template rendering :template_object |object passing to view +: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`). :resource_class |resource class name :resource_label |resource label value :plural_resource_label |pluralized resource label value (default config.plural_resource_label) @@ -77,6 +78,37 @@ Tool | Description +#### Custom ImportResult + +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`: + +```ruby +class ImportResultWithIds < ActiveAdminImport::ImportResult + attr_reader :ids + + def initialize + super + @ids = [] + end + + def add(batch_result, qty) + super + @ids.concat(Array(batch_result.ids)) + end +end + +ActiveAdmin.register Author do + active_admin_import result_class: ImportResultWithIds do |result, options| + EnqueueAuthorsJob.perform_later(result.ids) if result.imported? + instance_exec(result, options, &ActiveAdminImport::DSL::DEFAULT_RESULT_PROC) + end +end +``` + +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. + +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. + #### Wiki [Check various examples](https://github.com/activeadmin-plugins/active_admin_import/wiki) diff --git a/lib/active_admin_import/dsl.rb b/lib/active_admin_import/dsl.rb index eb6349a..42d7595 100644 --- a/lib/active_admin_import/dsl.rb +++ b/lib/active_admin_import/dsl.rb @@ -104,7 +104,7 @@ def active_admin_import(options = {}, &block) result = @importer.import if block_given? - instance_eval(&block) + instance_exec result, options, &block else instance_exec result, options, &DEFAULT_RESULT_PROC end diff --git a/lib/active_admin_import/importer.rb b/lib/active_admin_import/importer.rb index 7c99b78..d37c9ab 100644 --- a/lib/active_admin_import/importer.rb +++ b/lib/active_admin_import/importer.rb @@ -18,7 +18,8 @@ class Importer :headers_rewrites, :batch_size, :batch_transaction, - :csv_options + :csv_options, + :result_class ].freeze def initialize(resource, model, options) @@ -29,7 +30,7 @@ def initialize(resource, model, options) end def import_result - @import_result ||= ImportResult.new + @import_result ||= (options[:result_class] || ImportResult).new end def file diff --git a/lib/active_admin_import/options.rb b/lib/active_admin_import/options.rb index 3ed1ce8..0e85505 100644 --- a/lib/active_admin_import/options.rb +++ b/lib/active_admin_import/options.rb @@ -16,6 +16,7 @@ module Options :ignore, :template, :template_object, + :result_class, :resource_class, :resource_label, :plural_resource_label, diff --git a/spec/import_spec.rb b/spec/import_spec.rb index e859716..2d94115 100644 --- a/spec/import_spec.rb +++ b/spec/import_spec.rb @@ -689,4 +689,46 @@ def active_admin_import_context end end end + + # PG-only: activerecord-import populates `result.ids` reliably on PostgreSQL + # via RETURNING. On MySQL/SQLite the array is not populated by default, so + # the assertion would not be meaningful there. The :result_class option + # itself works on every adapter — this spec just demonstrates the canonical + # PR #191 use case (collecting inserted ids) on the adapter that supports it. + if ENV['DB'] == 'postgres' + context 'with custom result_class (PostgreSQL)' do + # Subclass that captures inserted ids alongside the standard counters. + # Lives in user-land so the gem itself stays free of adapter-specific + # quirks around RETURNING. This is the example documented in the README. + class ImportResultWithIds < ActiveAdminImport::ImportResult + attr_reader :ids + + def initialize + super + @ids = [] + end + + def add(batch_result, qty) + super + @ids.concat(Array(batch_result.ids)) + end + end + + before do + add_author_resource(result_class: ImportResultWithIds) do |result, _options| + # Expose the captured ids on the flash so the test asserts via the + # rendered page rather than closure capture. + flash[:notice] = "Imported ids: [#{result.ids.sort.join(',')}]" + end + visit '/admin/authors/import' + upload_file!(:authors) + end + + it 'collects the ids of inserted records via the custom subclass' do + expect(Author.count).to eq(2) + expected = "Imported ids: [#{Author.pluck(:id).sort.join(',')}]" + expect(page).to have_content(expected) + end + end + end end