Enumerable#index_by For Better Hash Building In Rails

Use Rails' Enumerable#index_by over Ruby's Enumerable#each_with_object

Ruby hashes are awesome! They allow us to efficiently access data by a key. A common task in data processing is to build hashes using Enumerable#each_with_object so we can quickly access related data during processing steps

In this example, we need to process a large amount of photo data by invoking process_photo, which needs a photo from our data and an instance from our Photographer model

-- CODE language-ruby line-numbers --# Bad, we will perform photo_data.count database requests
photo_data.each do |photo|
 process_photo(photo, Photographer.find_by(key: photo.key))
end

-- CODE language-ruby line-numbers --# Better, we perform a single database request
all_keys = photo_data.map(&:key).uniq
photographers_by_key = Photographer
                      .where(key: all_keys)
                      .each_with_index({}) do |photographer, accumulator|
 accumulator[photographer.key] = photographer
end
photo_data.each do |photo|
 process_photo(photo, photographers_by_key[photo.key])
end

Thanks to a recent addition to rubocop-rails, I learned that we can use Rails’ ActiveSupport method Enumerable#index_by to make this manipulation even easier

-- CODE language-ruby line-numbers --# Even Better, Enumerable#index_by simplifies the construction of our hash
all_keys = photo_data.map(&:key).uniq
photographers_by_key = Photographer.where(key: all_keys).index_by(&:key)
photo_data.each do |photo|
 process_photo(photo, photographers_by_key[photo.key])
end

TLDR: use Enumerable#index_by over Enumerable#each_with_object

-- CODE language-ruby line-numbers --# Good
photographers_by_key = Photographer
                      .where(key: all_keys)
                      .each_with_index({}) do |photographer, accumulator|
accumulator[photographer.key] = photographer
end

-- CODE language-ruby line-numbers --# Better
photographers_by_key = Photographer.where(key: all_keys).index_by(&:key)