Hash Transformations using index_by vs index_with

Rails provides expressive, concise tools for working with collections. Two often-overlooked but potent methods are index_by and index_with.

These are perfect for transforming arrays into hashes in a clean and declarative way—no need for manual loops or verbose hash-building logic.

Let’s explore both methods with practical examples.

index_by: Turn a collection into a hash, keyed by a custom value

index_by iterates over a collection and returns a hash where:

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" }
]

indexed = users.index_by { |u| u[:id] }

# Result:
# {
#   1 => { id: 1, name: "Alice" },
#   2 => { id: 2, name: "Bob" }
# }

This is incredibly useful when turning a list into a quick lookup hash by ID, email, or any unique key.

Example 2:

1
2
3
4
files = ["/tmp/test.txt", "/var/log/app.log"]

files.index_by { |path| File.basename(path) }
# => { "test.txt" => "/tmp/test.txt", "app.log" => "/var/log/app.log" }

index_with: Build a hash from existing keys

While index_by starts with values and builds keys, index_with starts with keys and generates the values using a block.

Example 1:

1
2
3
4
names = ["Alice", "Bob"]

lengths = names.index_with { |name| name.length }
# => { "Alice" => 5, "Bob" => 3 }

Example 2:

1
2
%w[red green blue].index_with(&:upcase)
# => { "red" => "RED", "green" => "GREEN", "blue" => "BLUE" }

Example 3:

Let’s say you want to count how many times each word appears in a sentence:

1
2
3
4
5
6
sentence = "the quick brown fox jumps over the lazy dog"
words = sentence.split

# Using `index_with`:
words.uniq.index_with { |w| words.count(w) }
# => { "the"=>2, "quick"=>1, "brown"=>1, "fox"=>1, "jumps"=>1, "over"=>1, "lazy"=>1, "dog"=>1 }

Example 4:

You can also use index_by with ActiveRecord to build lookup hashes from database queries:

1
2
3
4
5
User.all.index_by(&:email)
# => {
#   "alice@example.com" => #<User id: 1, ...>,
#   "bob@example.com" => #<User id: 2, ...>
# }

This is a great way to reduce N+1 queries or organize user data by email, ID, etc., without repeated .find or .where calls.

Which one to use?

Method Use case
index_by When you have objects and want to key them by something inside
index_with When you have keys and want to assign a value to each one