Skip to main content

Importers

CM Admin importers process files uploaded through an importable action. Use CmAdmin::BaseImporter for standard CSV and Excel row processing, or provide a standalone importer when you need complete control over parsing and execution.

Configure the import action​

Add importable inside the model's cm_admin configuration:

cm_admin do
importable class_name: 'UserImporter',
importer_type: :default_importer,
sample_file_path: '/user_import.csv'
end
OptionRequiredDescription
class_nameYesImporter class initialized by CM Admin.
importer_typeYesUse :default_importer for a CmAdmin::BaseImporter subclass or :custom_importer for a standalone importer.
sample_file_pathNoPublic path to a sample file shown on the import page.
route_typeNo:collection by default; use :member to import against one record.
display_ifNoLambda controlling whether the import action is visible.
page_configurationNoImport page settings such as title, description, and submit_button_text.

The uploaded file is stored as a FileImport and processed asynchronously by FileImportProcessorJob. The importer class must be autoloadable and accept file_import: in its initializer.

Use CmAdmin::BaseImporter​

The base importer provides file download, CSV/TSV/XLS/XLSX parsing, header validation, blank-row skipping, value trimming, per-row error capture, counters, and import status calculation.

Create an importer and implement process_row:

class UserImporter < CmAdmin::BaseImporter
def self.required_headers
%w[email name]
end

private

def process_row(row, _line_number)
User.create!(email: row[:email], name: row[:name])
end

def identifier_for(row)
row[:email]
end
end
  • required_headers — Optional. Lists headers that must exist in the first row. Header matching is exact.
  • process_row(row, line_number) — Runs once for each non-blank row. row supports string and symbol keys. Return a truthy value for success; raise an error or return nil/false for failure.
  • identifier_for(row) — Optional. Returns the value shown beside a failed row; the default is -.

Without transactions, the importer continues after a failed row. Successful rows remain committed and the final status is :partial_success when at least one other row succeeds.

Run the whole import in a transaction​

Transactions are disabled by default. Opt in by overriding use_transaction?:

class UserImporter < CmAdmin::BaseImporter
def self.required_headers
%w[email name]
end

def self.use_transaction?
true
end

private

def process_row(row, _line_number)
User.create!(email: row[:email], name: row[:name])
end

def identifier_for(row)
row[:email]
end
end

When enabled, all rows run inside one ActiveRecord::Base.transaction. The first failed row stops processing and rolls back database changes made earlier in that transaction. The failed row is still included in the import error report.

Use a transaction only when the import must be atomic. Keep it disabled for partial imports or when processing large files where a long-running transaction is undesirable. Transactions only cover database work participating in the same Active Record transaction; files, API calls, jobs, and other external side effects are not rolled back.

Keep a fully custom importer​

Use :custom_importer to retain the previous standalone approach. CM Admin does not require inheritance and does not control parsing or transactions:

cm_admin do
importable class_name: 'UserImporter', importer_type: :custom_importer, sample_file_path: '/user_import.csv'
end

The custom class must satisfy this interface:

class UserImporter
InvalidRow = Struct.new(:line_number, :identifier, :errors)

attr_reader :invalid_rows, :success_count, :row_count

def initialize(file_import:)
@file_import = file_import
Current.user = file_import.added_by
@invalid_rows = []
@success_count = 0
@row_count = 0
end

def run!
# Download and process @file_import.import_file here.
rescue StandardError => e
@invalid_rows << InvalidRow.new(1, '-', e.message)
end

def import_status
return :success if invalid_rows.empty?
# use partial_success with success_count and row_count if required.

:failed
end
end

The processor expects:

  • initialize(file_import:) — Receives the persisted FileImport containing the uploaded import_file attachment.
  • run! — Performs the import.
  • invalid_rows — Returns objects exposing line_number, identifier, and errors.
  • import_status — Optional. Return :success, :partial_success, or :failed. Without it, CM Admin returns :success when invalid_rows is empty and :failed otherwise.

A custom importer is responsible for file parsing, header validation, error handling, cleanup, transactions, and status semantics. Use it when the base importer's row-oriented workflow does not fit the import.