Ruby do Zero: Hashes — Chaves, Valores, Padrões e Manipulação Idiomática

Capítulo 10

Tempo estimado de leitura: 7 minutos

+ Exercício

O que é um Hash em Ruby

Um Hash é uma coleção de pares chave → valor. Diferente de um array (indexado por posição), o hash é indexado por uma chave, que costuma ser um símbolo (ex.: :host, :port) por ser leve, imutável e muito comum em configurações e dados estruturados.

config = { host: "localhost", port: 5432, ssl: false }

Você acessa valores usando a chave:

config[:host] # => "localhost"

Símbolos como chaves (padrão idiomático)

Em Ruby, é comum usar a sintaxe abreviada de símbolos:

user = { name: "Ana", role: "admin" } # equivalente a { :name => "Ana", :role => "admin" }

Boa prática: escolha um padrão e mantenha consistência. Se você usar símbolos como chaves, evite misturar com strings (ex.: "name") no mesmo hash, pois :name e "name" são chaves diferentes.

h = { name: "Ana" } h["name"] # => nil (chave diferente)

Acesso a valores: [] vs fetch (acesso seguro)

Usando [] (retorna nil quando não existe)

O operador [] é direto, mas quando a chave não existe ele retorna nil, o que pode mascarar erros.

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

config = { host: "localhost" } config[:port] # => nil

Isso pode virar um problema se você assumir que sempre haverá valor:

config[:port] + 1 # TypeError (nil + 1)

Usando fetch (fallback e/ou erro explícito)

fetch é a forma idiomática para exigir a presença da chave ou definir um fallback.

1) fetch com fallback (valor padrão pontual)

config = { host: "localhost" } port = config.fetch(:port, 5432) # => 5432

2) fetch com bloco (fallback calculado)

env = :dev config = { host: "localhost" } port = config.fetch(:port) { env == :prod ? 5432 : 15432 }

3) fetch sem fallback (falha rápido com KeyError)

config = { host: "localhost" } config.fetch(:port) # KeyError: key not found: :port

Esse comportamento é útil quando a ausência da chave é um erro de programação ou de configuração.

Tratamento de erro quando apropriado

Em cenários como leitura de configurações, você pode capturar o erro e gerar uma mensagem clara:

begin config = { host: "localhost" } port = config.fetch(:port) rescue KeyError => e raise "Configuração inválida: porta ausente (#{e.message})" end

Valores padrão do Hash (default)

Você pode definir um valor padrão para chaves ausentes. Isso é útil, mas exige cuidado.

Default simples

h = Hash.new(0) h[:a] # => 0 h[:a] += 1 h # => { :a => 1 }

Esse padrão é excelente para contagens.

Cuidado: default com objeto mutável compartilhado

Se você usar um array como default, ele será o mesmo objeto para todas as chaves ausentes.

h = Hash.new([]) h[:a] << 1 h[:b] << 2 h[:a] # => [1, 2] (surpresa!)

Forma correta: use bloco para criar um novo objeto por chave.

h = Hash.new { |hash, key| hash[key] = [] } h[:a] << 1 h[:b] << 2 h # => { :a => [1], :b => [2] }

Iteração: each_pair (e padrões comuns)

Para percorrer chaves e valores, use each_pair (ou each, que é equivalente). each_pair deixa explícito que você quer pares.

config = { host: "localhost", port: 5432, ssl: false } config.each_pair do |key, value| puts "#{key} => #{value}" end

Você também pode iterar só chaves ou só valores:

config.keys   # => [:host, :port, :ssl] config.values # => ["localhost", 5432, false]

Verificação de existência: key? e value?

key? (ou has_key?)

Use key? quando você precisa distinguir entre “chave ausente” e “chave presente com valor nil”.

h = { token: nil } h[:token] # => nil h.key?(:token) # => true h.key?(:missing) # => false

value? (ou has_value?)

value? verifica se algum valor existe no hash. Pode ser útil, mas costuma ser menos eficiente (precisa procurar nos valores).

h = { a: 1, b: 2 } h.value?(2) # => true

Merge: combinando hashes de forma idiomática

merge combina hashes e, quando há conflito de chave, o valor do hash da direita vence.

defaults = { host: "localhost", port: 5432, ssl: false } override = { ssl: true } config = defaults.merge(override) # => { host: "localhost", port: 5432, ssl: true }

Merge com bloco (resolver conflitos)

Quando você quer controlar como lidar com conflitos:

a = { retries: 2, tags: ["core"] } b = { retries: 5, tags: ["urgent"] } merged = a.merge(b) do |key, old_val, new_val| case key when :retries then [old_val, new_val].max when :tags then old_val + new_val else new_val end end # => { retries: 5, tags: ["core", "urgent"] }

Boa prática: prefira merge para criar um novo hash e manter imutabilidade local. Use merge! apenas quando a mutação for intencional e clara.

Transformação: map, transform_keys e transform_values

Hashes frequentemente precisam ser transformados: normalizar chaves, ajustar valores, preparar dados para saída.

transform_keys (normalizar chaves)

raw = { "Host" => "localhost", "PORT" => "5432" } normalized = raw.transform_keys { |k| k.downcase.to_sym } # => { host: "localhost", port: "5432" }

transform_values (converter valores)

normalized = { host: "localhost", port: "5432" } typed = normalized.transform_values do |v| v.match?(/\A\d+\z/) ? v.to_i : v end # => { host: "localhost", port: 5432 }

map para gerar outro formato

map em hash retorna um array. É útil para gerar linhas, logs ou estruturas derivadas.

config = { host: "localhost", port: 5432 } lines = config.map { |k, v| "#{k}=#{v}" } # => ["host=localhost", "port=5432"]

Cenário prático 1: Configurações com defaults, override e validação

Objetivo: construir uma configuração final com valores padrão, permitir override e garantir que chaves obrigatórias existam.

Passo a passo

1) Defina defaults

defaults = { host: "localhost", port: 5432, ssl: false, timeout: 5 }

2) Receba overrides (ex.: do usuário)

overrides = { ssl: true, timeout: 10 }

3) Faça merge

config = defaults.merge(overrides)

4) Exija chaves obrigatórias com fetch

host = config.fetch(:host) port = config.fetch(:port)

5) Use fallback pontual quando fizer sentido

retries = config.fetch(:retries, 3)

6) Valide tipos/intervalos (exemplo simples)

timeout = config.fetch(:timeout, 5) raise "timeout deve ser > 0" unless timeout.is_a?(Integer) && timeout > 0

Cenário prático 2: Contagens por categoria (Hash.new(0))

Objetivo: contar ocorrências por categoria de forma idiomática.

items = [ { category: :food, name: "banana" }, { category: :tech, name: "mouse" }, { category: :food, name: "arroz" } ] counts = Hash.new(0) items.each do |item| cat = item.fetch(:category) counts[cat] += 1 end counts # => { :food => 2, :tech => 1 }

Boa prática: use fetch(:category) se a categoria for obrigatória; assim você descobre dados inválidos cedo.

Cenário prático 3: Agrupamentos (Hash com default por bloco)

Objetivo: agrupar registros por uma chave (ex.: status, categoria, mês).

orders = [ { id: 1, status: :paid }, { id: 2, status: :pending }, { id: 3, status: :paid } ] grouped = Hash.new { |h, k| h[k] = [] } orders.each do |order| status = order.fetch(:status) grouped[status] << order end grouped # => { :paid => [{...}, {...}], :pending => [{...}] }

Boas práticas para lidar com chaves ausentes

  • Use fetch para chaves obrigatórias (falha rápido) ou com fallback quando houver um padrão aceitável.
  • Use key? quando precisar diferenciar “ausente” de “presente com nil”.
  • Defina defaults com cuidado: para objetos mutáveis (arrays/hashes), prefira Hash.new { |h, k| h[k] = ... }.
  • Evite misturar tipos de chave (símbolos e strings) no mesmo hash; normalize com transform_keys quando necessário.
  • Prefira merge a merge! para evitar efeitos colaterais, a menos que a mutação seja intencional.

Tabela rápida: métodos essenciais de Hash

MétodoUsoQuando usar
h[:k]Acesso simplesQuando nil é aceitável para ausente
h.fetch(:k)Acesso estritoQuando a chave é obrigatória
h.fetch(:k, v)Acesso com fallbackQuando há um padrão aceitável
h.key?(:k)Checar existência de chaveDistinguir ausente vs presente com nil
h.value?(v)Checar existência de valorConsultas pontuais em valores
h.each_pairIterar paresProcessar chaves e valores
h.merge(other)Combinar hashesDefaults + overrides, composição
h.transform_keysTransformar chavesNormalização (string → símbolo)
h.transform_valuesTransformar valoresConversões e ajustes de tipos

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

Ao construir uma configuração final a partir de defaults e overrides, como garantir que chaves obrigatórias não passem despercebidas quando estiverem ausentes?

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

Você errou! Tente novamente.

O operador [] retorna nil para chaves ausentes e pode mascarar problemas. Para chaves obrigatórias, fetch sem fallback falha rápido com KeyError, deixando claro que a configuração/dado está inválido.

Próximo capitúlo

Ruby do Zero: Enumerables e Coleções — map, select, group_by e sort

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

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.