Na versão 2.7 do Ruby foi incluído o método Enumerable#filter_map que, como o nome diz, funciona como uma mistura de filter
com map
. De acordo com a documentação oficial:
Returns a new array containing the truthy results (everything except false or nil) of running the block for every element in enum.
Em tradução livre: "retorna um array novo contendo os resultados truthy (exceto false
ou nil
) depois de rodar o bloco para cada elemento do enum". Vamos demonstrar como isso acontece com exemplos simples rodando no IRB:
numbers = [2, 5, 6, 8, 9, 11]
numbers.select { |x| x.odd? }.map { |x| x * 2 }
# => [10, 18, 22]
numbers.map { |x| x * 2 if x.odd? }.compact
# => [10, 18, 22]
Se utilizarmos o filter_map
, podemos obter o mesmo resultado:
numbers = [2, 5, 6, 8, 9, 11]
numbers.filter_map { |x| x * 2 if x.odd? }
# => [10, 18, 22]
Agora vamos mostrar um exemplo de uso do método um pouco mais próximo da realidade. Vamos considerar que nossa aplicação possui uma classe que pode receber uma lista de e-mails e criar um User
para cada um deles. No entanto, cada e-mail deve ser validado antes do usuário ser criado. Para isso, temos um método email_hashes
que itera pela lista e cria um array de hashes com os e-mails válidos. Esse array depois é utilizado para criar os usuários no método call
.
class UserGenerator
EMAIL_REGEXP = URI::MailTo::EMAIL_REGEXP
def initialize(emails)
@emails = emails
end
def call
email_hashes.map { |hash| User.create(hash) }
end
def email_hashes
@emails.map do |email|
next unless email =~ EMAIL_REGEXP
{ email: email }
end
end.compact
end
Note como o compact
é utilizado no final de email_hashes
para eliminar os resultados nil
desse processamento. Agora se implementarmos o método com filter_map
, ele poderia ficar assim:
def email_hashes
@emails.filter_map do |email|
next unless email =~ EMAIL_REGEXP
{ email: email }
end
end
O método filter_map
é bastante útil, pois oferece uma forma simples e direta para resolver o problema de mapear somente elementos específicos de um array.