activerecord-import
repository·master·Indexed 26 days ago
https://github.com/zdennis/activerecord-importA high-performance bulk insertion library for ActiveRecord that optimizes SQL statement generation and avoids N+1 insert problems by following associations. It supports bulk imports via Model.import and Model.import!, providing advanced conflict handling (upserts) for PostgreSQL, MySQL, and SQLite3, as well as memory synchronization for ActiveRecord instances.
What's inside activerecord-import
- Activerecord-Import is a library designed for bulk inserting data into a database using ActiveRecord. It optimizes performance by following ActiveRecord associations and generating the minimal number of SQL insert statements required, effectively avoiding the N+1 insert problem. For example, instead of performing millions of individual inserts for nested associations (e.g., Publishers -> Books -> Reviews), it can reduce the operation to just a few SQL statements (one per table).
Key features of Activerecord-Import
masterThe library provides several high-level features for efficient data ingestion:
- Raw columns and arrays of values: The fastest method for bulk insertion.
- Model objects: Works with instantiated model objects (faster than standard ActiveRecord saves).
- Validations: Supports performing model validations during the import process.
- On duplicate key updates: Supports performing updates when a duplicate key is encountered (requires MySQL, SQLite 3.24.0+, or Postgres 9.5+).
Install and Load activerecord-import
masterUsing Bundler (Recommended)
Add to your
Gemfile:gem 'activerecord-import'Manual Loading
If you use
require: falsein your Gemfile, you must manually require the necessary components. If you are in a Rails environment with an established connection, you may need to load the specific adapter:require 'activerecord-import/base' require 'activerecord-import/active_record/adapters/postgresql_adapter' # or mysql2, sqlite3, etc.If you are not using a bundler-managed environment, require it after
active_recordis loaded:require 'active_record' require 'activerecord-import'Handle duplicate keys with upsert (on_duplicate_key_update)
masterAllows you to specify fields to update if a conflict occurs. Support varies by database:
- MySQL: Uses
ON DUPLICATE KEY UPDATE. - PostgreSQL (9.5+): Requires specifying a
conflict_target(the columns where the conflict occurs). - SQLite (3.24.0+): Models support after PostgreSQL.
PostgreSQL Specifics
For PostgreSQL, you can provide a
conflict_target(the column/index that triggers the conflict) and thecolumnsto update. You can also useindex_predicatefor partial indexes orconstraint_namefor unique constraints.# MySQL: Basic update Book.import [book], on_duplicate_key_update: [:title] # PostgreSQL: Basic update (conflict target must be primary key) Book.import [book], on_duplicate_key_update: [:title] # PostgreSQL: Explicit target and columns Book.import [book], on_duplicate_key_update: {conflict_target: [:id], columns: [:title]} # PostgreSQL: Using a value from another column Book.import [book], on_duplicate_key_update: { conflict_target: [:id], columns: {author: :title} } # PostgreSQL: Using partial indexes Book.import [book], on_duplicate_key_update: { conflict_target: [:id], index_predicate: "published_at IS NOT NULL", columns: [:author] } # PostgreSQL: Using constraints Book.import [book], on_duplicate_key_update: { constraint_name: :for_upsert, columns: [:published_at] }- MySQL: Uses
Handle ActiveRecord Callbacks
masterStandard ActiveRecord callbacks (like
before_create,after_save, etc.) are NOT called during animportbecause it is a mass-import operation.If you need callbacks to run, you must manually trigger them on your objects before importing, or loop through and validate/run callbacks manually.
# Manually running callbacks before import books.each do |book| book.run_callbacks(:save) { false } book.run_callbacks(:create) { false } end Book.import(books)Handle duplicate keys with on_duplicate_key_ignore
masterSupported by MySQL, SQLite, and PostgreSQL (9.5+). This option allows you to skip records if a primary or unique key constraint is violated.
Note: This cannot be used with
recursive: trueon PostgreSQL.book = Book.create! title: "Book1", author: "George Orwell" book.title = "Updated Book Title" # Skips the update because the title already exists Book.import [book], on_duplicate_key_ignore: trueConfigure ActiveRecord Timestamps
masterBy default,
timestamps: trueis enabled, andcreated_at/updated_atcolumns are automatically set.If you want to manually specify these columns in specific records, you can still use
timestamps: true; the gem will only update the timestamp if the field isnilin the record. To disable automatic timestamping entirely, usetimestamps: false.Handle Duplicate Key Conflicts in PostgreSQL
masterIf you want to ignore conflicts instead of updating, use the
:on_duplicate_key_ignoreoption.Note: The options
:recursiveand:on_duplicate_key_ignore(or:ignore) are mutually exclusive. If you are using:recursive, you cannot use these ignore options.Run benchmarks for activerecord-import
masterYou can run performance benchmarks for the library using the
benchmark.rbscript.Note: Currently,
mysqlis the only supported database adapter for benchmarking.ruby benchmark.rb [options]Batching imports with batch_size
masterUse the
batch_sizeoption to control how many rows are inserted per SQL statement. This is useful for very large datasets to avoid memory issues or long-running transactions.You can also provide a callable to the
batch_progressoption to monitor progress during large imports.# 2 INSERT statements for 4 records Book.import columns, books, batch_size: 2 # Monitoring progress my_proc = ->(rows_size, num_batches, current_batch_number, batch_duration_in_secs) { # logic to report progress } Book.import columns, books, batch_size: 2, batch_progress: my_procRecursive imports (PostgreSQL only)
masterIf you are using PostgreSQL, you can use
recursive: trueto importhas_manyorhas_oneassociations in a single operation. This does not work with raw hashes or arrays; you must pass ActiveRecord objects.books = [] 10.times do |i| book = Book.new(name: "book #{i}") book.reviews.build(title: "Excellent") books << book end Book.import books, recursive: trueImport ActiveRecord Models
masterYou can pass an array of ActiveRecord model instances directly to
import. The gem will automatically extract attributes based on the model's columns.books = [ Book.new(title: "Book 1", author: "George Orwell"), Book.new(title: "Book 2", author: "Bob Jones") ] # Import models (validations enabled by default) Book.import books