Rails 6 adds Enumerable#index_with


Rails 6 has added index_with to Enumerable. This allows creating a hash from an enumerable with the value from a passed block or a default argument.

Before

In one of our applications, we need to perform some params filtering, after which we pass it to ElasticSearch queries.

We receive unfiltered terms from Searchkit which are then filtered as below by sanitize_terms method.

def sanitized_terms(raw_term)
  new_term = {}
  %i{first_name last_name dob status}.each do |term_key|
    new_term[term_key] = sanitize_single_term(raw_term[term_key])
  end
  new_term
end

def sanitize_single_term(term)
  #   Scrub malicious data on the term here
  #   ...
  term
end

# Usage

raw_term = {first_name: "Jane",
            last_name: "Smith",
            dob: "10-10-1990",
            status: "active",
            malicious_term: "Hax",
            hurtful_attack: "attax"}

sanitized_terms(raw_term)
# =>  {:first_name=>"Jane",
# :last_name=>"Smith", 
# :dob=>"10-10-1990", 
# :status=>"active"}  

As you can see the code in sanitized_terms gets verbose, since we first initialize the hash, mutate it, and then return its value.

Enumerable#index_with

This kind of task is not rare, we need to perform it at various places. For example:

flash_type_classes = {}
%i{alert error notice}.
each{|type| flash_type_classes[type] = "alert alert-#{type}"}
flash_type_classes 
# => {:alert=>"alert alert-alert", 
# :error=>"alert alert-error", 
# :notice=>"alert alert-notice"}

Enter Enumerable#index_with. It provides us with a convenience method to perform this operation in a simplified form.

%i{alert error notice}.
index_with{|type|  "alert alert-#{type}"}
# => {:alert=>"alert alert-alert", 
# :error=>"alert alert-error", 
# :notice=>"alert alert-notice"}

index_with creates a new hash object, using the key as the current element it is enumerating. Value is determined from the output of the passed block.

This helps us simplify our initial example in a much easier way:

# Before 

def sanitized_terms(raw_term)
  new_term = {}
  %i{first_name last_name dob status}.each do |term_key|
    new_term[term_key] = sanitize_single_term(raw_term[term_key])
  end
  new_term
end

# After

def sanitized_terms(raw_term)
%i{first_name last_name dob status}.
index_with{|term_key| sanitize_single_term(raw_term[term_key])}
end

sanitized_terms(raw_term)
# =>  {:first_name=>"Jane",
# :last_name=>"Smith", 
# :dob=>"10-10-1990", 
# :status=>"active"}

index_with also allows to create a hash easily with default values:

%i{first_name last_name}.index_with(nil)
# => {:first_name=>nil, :last_name=>nil}
%i{dob_entered agreement_accepted}.index_with(false)
# => {:dob_entered=>false, :agreement_accepted=>false}