ruby 53 lines · 8 steps

Bulk CSV imports with upsert_all in Rails

A Rails API endpoint parses an uploaded CSV and bulk-upserts products in a single query, keyed by SKU.

Explained by highlit
1require "csv"
2 
3class Api::V1::ProductImportsController < Api::V1::BaseController
4 MAX_ROWS = 10_000
5 
6 def create
7 csv_file = params.require(:file)
8 rows = parse_csv(csv_file)
9 
10 if rows.empty?
11 return render json: { error: "No valid rows found" }, status: :unprocessable_entity
12 end
13 
14 if rows.size > MAX_ROWS
15 return render json: { error: "Too many rows (max #{MAX_ROWS})" }, status: :unprocessable_entity
16 end
17 
18 result = Product.upsert_all(
19 rows,
20 unique_by: :sku,
21 update_only: %i[name price_cents stock currency],
22 returning: %w[id sku]
23 )
24 
25 render json: {
26 imported: result.length,
27 skus: result.rows.flatten
28 }, status: :created
29 rescue CSV::MalformedCSVError => e
30 render json: { error: "Malformed CSV: #{e.message}" }, status: :unprocessable_entity
31 end
32 
33 private
34 
35 def parse_csv(file)
36 now = Time.current
37 
38 CSV.foreach(file.path, headers: true, header_converters: :symbol).filter_map do |row|
39 sku = row[:sku]&.strip
40 next if sku.blank?
41 
42 {
43 sku: sku,
44 name: row[:name]&.strip,
45 price_cents: (row[:price].to_f * 100).round,
46 stock: row[:stock].to_i,
47 currency: row[:currency].presence || "USD",
48 created_at: now,
49 updated_at: now
50 }
51 end
52 end
53end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1upsert_all writes many rows in one query, sidestepping model callbacks and per-row round trips.
  2. 2Guard bulk operations with size and emptiness checks before touching the database.
  3. 3Normalizing and filtering rows during parsing keeps invalid data out of the upsert entirely.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Bulk CSV imports with upsert_all in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code