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}