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:
- the key is the value returned by the block, and
- the value is the original element.
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 |