CSV

Added in v1.9 · Updated March 13, 2026 · Modules
ruby stdlib csv parsing data

The CSV module provides utilities for reading and writing CSV (Comma-Separated Values) data in Ruby. It handles various CSV formats including custom delimiters, quoting rules, and encoding options.

Overview

CSV is a common format for spreadsheets, data exports, and data interchange. Ruby’s CSV module makes it easy to:

  • Parse: Read CSV data into arrays, hashes, or table objects
  • Generate: Create CSV output from Ruby data structures
  • Transform: Filter, map, and process CSV data
  • Stream: Process large files row by row

Loading CSV

require "csv"

Reading CSV Data

CSV.parse

CSV.parse converts a CSV string into an array of arrays.

require "csv"

csv_string = "name,age,city\nAlice,30,NYC\nBob,25,LA"

rows = CSV.parse(csv_string)
rows[0]  # => ["name", "age", "city"]
rows[1]  # => ["Alice", "30", "NYC"]

With headers:

rows = CSV.parse(csv_string, headers: true)
rows.first["name"]  # => "Alice"
rows.first["age"]   # => "30"

CSV.read

CSV.read reads directly from a file.

require "csv"

# Read entire file into array
data = CSV.read("data.csv")

# Read with headers
data = CSV.read("data.csv", headers: true)

CSV.foreach

Process large files row by row without loading everything into memory:

require "csv"

CSV.foreach("large_file.csv") do |row|
  # Process each row
  puts row.join(", ")
end

With headers and conversion:

CSV.foreach("data.csv", headers: true, converters: :numeric) do |row|
  puts "#{row["name"]} is #{row["age"]} years old"
end

Generating CSV

CSV.generate

Create CSV strings programmatically:

require "csv"

csv_string = CSV.generate do |csv|
  csv << ["name", "age", "city"]
  csv << ["Alice", 30, "NYC"]
  csv << ["Bob", 25, "LA"]
end

puts csv_string
# name,age,city
# Alice,30,NYC
# Bob,25,LA

CSV.generate_line

Quickly generate a single CSV row:

require "csv"

CSV.generate_line(["Alice", 30, "NYC"])
# => "Alice,30,NYC\n"

# Custom separator
CSV.generate_line(["a,b", "c"], quote_char: "\"")
# => "\"a,b\",c\n"

CSV.table

Read CSV into a table with column names as symbols:

require "csv"

table = CSV.table("data.csv")
table[0][:name]  # => "Alice"
table[0][:age]   # => 30

Common Options

OptionDefaultDescription
col_sep","Column separator
row_sep"\r\n" or "\n"Row separator
quote_char"\"Quote character
headersfalseParse first row as headers
skip_blanksfalseSkip empty rows
encoding"UTF-8"Character encoding

Custom Delimiters

# Tab-separated values
CSV.parse(tsv_string, col_sep: "\t")

# Semicolon-separated
CSV.parse(str, col_sep: ";")

# Read pipe-delimited file
CSV.read("data.txt", col_sep: "|")

Handling Quotes

# Force quoting of all fields
CSV.generate_line(["a", "b", "c"], force_quotes: true)
# => "\"a\",\"b\",\"c\"\n"

# Use single quotes
CSV.generate_line(["it's", "fine"], quote_char: "'")
# => "'it''s','fine'\n"

Working with Headers

Header Options

# Array of symbols as headers
CSV.parse(csv_string, headers: [:name, :age, :city])

# True uses first row as headers
CSV.parse(csv_string, headers: true)
# First row is removed from data

# Header converters
CSV.parse(csv_string, headers: true, header_converters: :symbol)
# Headers become symbols, spaces -> underscores

Accessing Header Data

data = CSV.parse(csv_string, headers: true)

# Get headers
data.headers  # => ["name", "age", "city"]

# Find column index
data.headers.index("age")  # => 1

Data Conversion

Built-in Converters

# Convert numeric fields
CSV.parse(csv_string, converters: :numeric)
# [["Alice", 30, "NYC"], ["Bob", 25, "LA"]]

# Convert dates
CSV.parse(csv_string, converters: :date)

# Multiple converters
CSV.parse(csv_string, converters: [:numeric, :date])

Custom Converters

CSV::Converters[:uppercase] = ->(field) { field.upcase }

CSV.parse("hello,world", converters: :uppercase)
# => [["HELLO", "WORLD"]]

Practical Examples

Processing Survey Data

require "csv"

results = []
CSV.foreach("survey.csv", headers: true, converters: :numeric) do |row|
  if row["satisfaction"] >= 4
    results << {
      name: row["name"],
      score: row["satisfaction"]
    }
  end
end

puts "High satisfaction: #{results.length}"

Export Database to CSV

require "csv"

CSV.open("export.csv", "wb") do |csv|
  csv << ["id", "name", "email", "created_at"]
  
  User.find_each do |user|
    csv << [user.id, user.name, user.email, user.created_at]
  end
end

Transform CSV Format

require "csv"

# Convert tab-separated to comma-separated
input = "name\tage\nAlice\t30"

output = CSV.generate do |csv|
  CSV.parse(input, col_sep: "\t").each do |row|
    csv << row
  end
end

Handle Large Files

require "csv"

# Count rows without loading entire file
count = 0
CSV.foreach("large.csv", headers: true) { count += 1 }
puts "Total rows: #{count}"

# Filter and write to new file
CSV.foreach("input.csv", headers: true) do |row|
  if row["status"] == "active"
    CSV.open("filtered.csv", "a") do |output|
      output << row
    end
  end
end

CSV::Row and CSV::Field

Working with Rows

require "csv"

row = CSV::Row.new(["name", "age"], ["Alice", 30])

row[0]          # => "Alice"
row["name"]     # => "Alice" (with headers)
row.fields      # => ["Alice", 30]
row.to_a        # => ["Alice", 30]
row.to_h        # => {"name" => "Alice", "age" => 30}

Field Manipulation

row = CSV::Row.new(["name", "age"], ["Alice", 30], headers: ["name", "age"])

# Check if field exists
row["name"]    # => "Alice"
row["missing"] # => nil

# Delete field
row.delete("age")
row.to_h       # => {"name" => "Alice"}

Ruby Version History

  • Ruby 1.9: Added built-in CSV library (based on FasterCSV)
  • Ruby 2.0: Improved encoding support
  • Ruby 2.6: Added skip_blanks option
  • Ruby 3.0: Fixed various parsing edge cases

See Also

  • JSON — for JSON parsing and generation
  • YAML — for YAML parsing and generation
  • File — for reading/writing files that contain CSV data