ruby 50 lines · 1 tab

Bulk operations with ActiveRecord import

Alex Kumar Jan 2026
1 tab
require 'activerecord-import'

class BulkImportPostsService
  BATCH_SIZE = 1000

  def initialize(csv_file_path)
    @csv_file_path = csv_file_path
  end

  def call
    posts = []
    imported_count = 0

    CSV.foreach(@csv_file_path, headers: true) do |row|
      posts << Post.new(
        title: row['title'],
        body: row['body'],
        author_id: row['author_id'],
        published_at: row['published_at']
      )

      if posts.size >= BATCH_SIZE
        imported_count += import_batch(posts)
        posts = []
      end
    end

    # Import remaining posts
    imported_count += import_batch(posts) if posts.any?

    Result.success(imported_count: imported_count)
  rescue StandardError => e
    Rails.logger.error("Bulk import failed: #{e.message}")
    Result.failure(error: e.message)
  end

  private

  def import_batch(posts)
    Post.import(
      posts,
      validate: false,
      on_duplicate_key_update: {
        conflict_target: [:id],
        columns: [:title, :body, :updated_at]
      }
    )
    posts.size
  end
end
1 file · ruby Explain with highlit

Inserting thousands of records one-by-one is prohibitively slow due to the overhead of individual INSERT statements. The activerecord-import gem provides bulk insert capabilities that compile multiple records into a single multi-row INSERT, dramatically improving throughput. I use this for data imports, batch processing, or seeding test environments. The gem supports validations, callbacks (optionally), and conflict resolution strategies like on_duplicate_key_update. For maximum performance, I disable validations and callbacks when data is already trusted. The trade-off is that bulk operations bypass some ActiveRecord conveniences like after_create callbacks, so I handle those concerns separately if needed. Monitoring insertion rates helps identify when bulk operations are worth the complexity.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Bulk operations with ActiveRecord import — share card
Link copied