Ruby do Zero: Iteradores e Código Expressivo com Blocos

Capítulo 14

Tempo estimado de leitura: 8 minutos

+ Exercício

Iteradores idiomáticos para código mais expressivo

Quando você já domina each, map e select, o próximo passo é escrever iterações que carregam mais intenção no próprio código. Em Ruby, isso costuma significar: (1) escolher o iterador que “diz” o que você quer fazer, (2) usar blocos curtos e legíveis, e (3) extrair regras para métodos com nomes claros quando a lógica cresce.

Quando usar cada iterador (visão prática)

  • each_with_index: quando você precisa do elemento e também da posição (índice) para formatar, numerar, validar ou cruzar com outra estrutura.
  • each_with_object(obj): quando você quer construir/acumular um objeto (hash, array, string, set) sem depender de variável externa e sem retornar um array intermediário.
  • inject/reduce: quando você quer reduzir uma coleção a um único valor (soma, produto, hash agregado, string final, estatística). O acumulador é explícito e o retorno é o valor final.

each_with_index: índice sem “gambiarras”

Evite manter um contador manual fora do bloco quando o índice é parte do problema. each_with_index entrega o elemento e o índice de forma direta.

Exemplo: numerar itens para exibição

Estilo imperativo (contador manual):

itens = ["Arroz", "Feijão", "Macarrão"]

linha = 1
itens.each do |item|
  puts "#{linha}. #{item}"
  linha += 1
end

Estilo idiomático com each_with_index:

itens = ["Arroz", "Feijão", "Macarrão"]

itens.each_with_index do |item, index|
  puts "#{index + 1}. #{item}"
end

Ganho de clareza: não existe estado externo (linha) para manter consistente; o índice vem do próprio iterador.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

Passo a passo: validando posições específicas

Imagine que você precisa validar que nenhum item em posição par (2, 4, 6...) esteja vazio.

  1. Use each_with_index para obter a posição.
  2. Converta o índice (0-based) para posição humana (1-based) quando necessário.
  3. Faça a regra ficar explícita no bloco.
campos = ["Ana", "", "Carlos", ""]

campos.each_with_index do |valor, index|
  posicao = index + 1
  proxima_se_par = (posicao % 2).zero?

  if proxima_se_par && valor.strip.empty?
    raise "Campo na posição #{posicao} não pode ser vazio"
  end
end

each_with_object: acumular sem variável externa

Um padrão comum é construir um hash/array a partir de uma coleção. Você pode fazer isso com each e uma variável externa, mas each_with_object torna o “objeto acumulador” parte do iterador, reduzindo efeitos colaterais e deixando a intenção mais evidente.

Exemplo: indexar usuários por e-mail

Imperativo (variável externa):

usuarios = [
  { nome: "Ana", email: "ana@ex.com" },
  { nome: "Bia", email: "bia@ex.com" }
]

por_email = {}
usuarios.each do |u|
  por_email[u[:email]] = u
end

Idiomaticamente com each_with_object:

usuarios = [
  { nome: "Ana", email: "ana@ex.com" },
  { nome: "Bia", email: "bia@ex.com" }
]

por_email = usuarios.each_with_object({}) do |u, acc|
  acc[u[:email]] = u
end

Ganho de clareza: o acumulador (acc) é explícito, e o método retorna o próprio objeto acumulado (não um array de retornos do bloco).

Exemplo: construir uma lista filtrada e transformada em um único passo

Às vezes você quer filtrar e transformar, mas sem criar arrays intermediários (ou sem encadear muitos métodos). each_with_object é útil quando a regra fica mais clara assim.

entradas = ["  ana ", "", "bia", "  ", "carlos"]

nomes = entradas.each_with_object([]) do |texto, acc|
  nome = texto.strip
  next if nome.empty?

  acc << nome.capitalize
end

# nomes => ["Ana", "Bia", "Carlos"]

inject/reduce: reduzindo para um único valor

inject (também conhecido como reduce) percorre a coleção carregando um acumulador. A cada elemento, você devolve o novo valor do acumulador. No final, o retorno é o acumulador final.

Modelo mental do reduce

ParteSignificado
Valor inicialO acumulador começa com esse valor (ex.: 0 para soma, 1 para produto, {} para hash).
|acc, item|acc é o acumulador atual; item é o elemento da vez.
Retorno do blocoVira o próximo acc.

Exemplo: soma (comparando estilos)

Imperativo:

valores = [10, 20, 30]

total = 0
valores.each do |v|
  total += v
end

Com reduce:

valores = [10, 20, 30]

total = valores.reduce(0) do |acc, v|
  acc + v
end

Com atalho de símbolo (quando for simples):

total = valores.reduce(0, :+)

Passo a passo: total de carrinho com regra

Vamos calcular o total de um carrinho, aplicando desconto de 10% apenas em itens acima de 100.

  1. Defina o acumulador inicial como 0.0 (evita divisão inteira e deixa o tipo claro).
  2. Para cada item, calcule o subtotal com a regra.
  3. Some no acumulador e retorne o novo acumulador.
itens = [
  { nome: "Teclado", preco: 120.0 },
  { nome: "Mouse", preco: 80.0 },
  { nome: "Monitor", preco: 900.0 }
]

total = itens.reduce(0.0) do |acc, item|
  preco = item[:preco]
  subtotal = preco > 100 ? (preco * 0.9) : preco
  acc + subtotal
end

reduce para construir hashes (com cuidado)

É possível usar reduce para construir um hash, mas muitas vezes each_with_object fica mais legível. Se usar reduce, deixe o valor inicial explícito e retorne o acumulador.

pares = [[:a, 1], [:b, 2], [:c, 3]]

hash = pares.reduce({}) do |acc, (chave, valor)|
  acc[chave] = valor
  acc
end

Note a linha acc no final: ela garante que o bloco devolve o acumulador, não o valor atribuído.

Padrões de legibilidade em blocos

1) Nomes claros para variáveis de bloco

Blocos com |x| e |y| funcionam, mas custam leitura. Prefira nomes que expliquem o papel do valor.

# Menos legível
pedidos.each do |p|
  puts p[:id]
end

# Mais legível
pedidos.each do |pedido|
  puts pedido[:id]
end

Para acumuladores, use nomes como acc, total, resultado, por_email, contagem, dependendo do contexto.

2) Evitar blocos longos (regra prática)

Se o bloco começa a ter muitas variáveis temporárias, múltiplos if e comentários para explicar, é um sinal de que a lógica merece um método com nome.

Bloco longo (difícil de escanear):

linhas.each_with_object([]) do |linha, acc|
  texto = linha.strip
  next if texto.empty?

  colunas = texto.split(",")
  next if colunas.size < 3

  nome = colunas[0].strip
  email = colunas[1].strip.downcase
  idade = colunas[2].to_i

  next if nome.empty? || email.empty?
  next if idade < 18

  acc << { nome: nome, email: email, idade: idade }
end

3) Extração para métodos: bloco curto, intenção alta

Extraia regras para métodos com nomes que descrevem o “porquê”, não o “como”. O iterador fica responsável apenas por orquestrar.

def parse_linha_csv(linha)
  texto = linha.strip
  return nil if texto.empty?

  colunas = texto.split(",")
  return nil if colunas.size < 3

  nome = colunas[0].strip
  email = colunas[1].strip.downcase
  idade = colunas[2].to_i

  return nil if nome.empty? || email.empty?
  return nil if idade < 18

  { nome: nome, email: email, idade: idade }
end

usuarios = linhas.each_with_object([]) do |linha, acc|
  usuario = parse_linha_csv(linha)
  acc << usuario if usuario
end

Agora o bloco tem uma única responsabilidade: “tentar parsear e acumular quando válido”. A regra de parsing/validação está encapsulada e pode ser testada isoladamente.

Reescrevendo loops imperativos para estilo iterativo/funcional

Padrão 1: construir um hash de contagem

Imperativo:

palavras = %w[ruby ruby bloco iterador ruby]

contagem = {}
palavras.each do |palavra|
  contagem[palavra] ||= 0
  contagem[palavra] += 1
end

Com each_with_object (mesma ideia, menos estado externo):

palavras = %w[ruby ruby bloco iterador ruby]

contagem = palavras.each_with_object(Hash.new(0)) do |palavra, acc|
  acc[palavra] += 1
end

Padrão 2: encontrar o “melhor” elemento (máximo por critério)

Às vezes você vê um loop que mantém “o melhor até agora”. Dá para expressar com reduce deixando o critério explícito.

Imperativo:

produtos = [
  { nome: "A", preco: 10 },
  { nome: "B", preco: 25 },
  { nome: "C", preco: 20 }
]

mais_caro = nil
produtos.each do |p|
  if mais_caro.nil? || p[:preco] > mais_caro[:preco]
    mais_caro = p
  end
end

Com reduce:

mais_caro = produtos.reduce do |melhor, atual|
  atual[:preco] > melhor[:preco] ? atual : melhor
end

Ganho de clareza: a variável melhor comunica a intenção; o bloco descreve a regra de comparação.

Padrão 3: transformar e acumular em string final

Imperativo:

nomes = ["ana", "bia", "carlos"]

saida = ""
nomes.each_with_index do |nome, i|
  saida += "- "
  saida += (i + 1).to_s
  saida += ": "
  saida += nome.capitalize
  saida += "\n"
end

Com reduce (acumulando texto):

nomes = ["ana", "bia", "carlos"]

saida = nomes.each_with_index.reduce("") do |acc, (nome, i)|
  acc + "- #{i + 1}: #{nome.capitalize}\n"
end

Observação: para grandes volumes de texto, acumular com String pode ser custoso; nesse caso, prefira acumular em array e depois join. A escolha do iterador deve equilibrar clareza e custo.

Checklist rápido para blocos expressivos

  • O iterador escolhido descreve a intenção? (each_with_index, each_with_object, reduce)
  • Os nomes do bloco explicam o papel dos valores? (pedido, usuario, acc, total)
  • O bloco cabe “na tela” sem rolagem mental? Se não, extraia para método.
  • O bloco retorna o que você espera? Em reduce, o retorno do bloco vira o próximo acumulador.
  • Evite misturar muitas responsabilidades no mesmo bloco (parse + validar + persistir + logar).

Agora responda o exercício sobre o conteúdo:

Ao reescrever um loop para construir um hash a partir de uma coleção, qual alternativa descreve melhor quando e por que usar each_with_object em vez de each?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

each_with_object recebe um objeto acumulador e o expõe no bloco, permitindo construir/atualizar um hash ou array sem estado externo. Isso aumenta a clareza e evita criar coleções intermediárias desnecessárias.

Próximo capitúlo

Ruby do Zero: Organização de Código — require, load e Estrutura de Pastas

Arrow Right Icon
Capa do Ebook gratuito Ruby do Zero: Fundamentos, Coleções, Blocos e Organização de Código
67%

Ruby do Zero: Fundamentos, Coleções, Blocos e Organização de Código

Novo curso

21 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.