DevTools

Cheatsheet Ruby

Referência completa da linguagem Ruby — elegante, expressiva e orientada a objetos

Voltar às linguagens
Ruby
64 cards encontrados
Categorias:
Versões:

Básico


8 cards
Variáveis e scope
nome = "Ruby"          # local (minúscula)
$global = "todo lado"  # global
@instancia = "objeto"  # instância
@@classe = "classe"    # classe
CONSTANTE = "fixa"     # constante (convenção)

# Atribuição múltipla:
x, y, z = 1, 2, 3
a, b = b, a            # swap elegante

# Atribuição condicional:
@cache ||= calcular()  # só se nil/false
# Equivale: @cache = @cache || calcular()

# Operador de segurança:
nome&.upcase  # nil se nome for nil

# Verificar definida:
defined?(nome)  # "local-variable"

O prefixo define o scope: sem prefixo = local, $ = global, @ = instância, @@ = classe, MAIÚSCULA = constante. ||= para lazy init. &. (safe navigation) evita erro em nil. Swap sem variável temporária é idiomático.

Operadores
# Aritméticos: + - * / % **
2 ** 10   # 1024
10 / 3    # 3 (divisão inteira)
10.0 / 3  # 3.333...

# Comparação:
1 == 1.0       # true (valor)
1.eql?(1.0)    # false (valor + tipo)
1.equal?(1)    # true (mesmo objeto)

# Lógicos: && || !
# Safe navigation:
nome&.upcase   # nil se nome for nil

# Ternário:
status = idade >= 18 ? "adulto" : "menor"

# Ranges:
(1..10)   # inclusivo (1 a 10)
(1...10)  # exclusivo (1 a 9)
(1..).include?(999)  # true (endless)

== compara valor, .eql? valor+tipo, .equal? identidade. &. (safe navigation) evita NoMethodError em nil. .. inclusivo, ... exclusivo. Divisão inteira trunca — usar Float para decimais.

Tipos de dados
42.class           # Integer
3.14.class         # Float
"texto".class      # String
true.class         # TrueClass
nil.class          # NilClass
:simbolo.class     # Symbol
[1, 2].class       # Array
{ a: 1 }.class     # Hash
(1..10).class      # Range
/regex/.class      # Regexp

# Tudo é objeto:
42.even?           # true
42.times { }       # método em número
nil.nil?           # true
nil.is_a?(Object)  # true

# Tipagem dinâmica mas forte:
# "42" + 1  # TypeError! (sem conversão implícita)

Ruby tem tipagem dinâmica mas forte — sem conversões implícitas. .class revela o tipo. Tudo é objeto (números, nil, true). Symbols (:nome) são identificadores imutáveis. is_a? verifica hierarquia de tipos.

Conversões
"42".to_i        # 42
"3.14".to_f      # 3.14
42.to_s          # "42"
42.to_f          # 42.0
"abc".to_i       # 0 (não lança erro!)

# Versões estritas:
Integer("42")    # 42
Integer("abc")   # ArgumentError!
Float("3.14")    # 3.14

# Parsing seguro:
begin
  n = Integer(input)
rescue ArgumentError
  n = 0
end

# Outras:
"1,2,3".split(",").map(&:to_i)  # [1, 2, 3]
[1, 2, 3].join("-")             # "1-2-3"
:nome.to_s                      # "nome"

to_i/to_f nunca lançam erro — retornam 0 em strings inválidas (perigoso!). Integer()/Float() são estritos e lançam ArgumentError. Para parsing seguro, usar Integer() com rescue.

Strings
nome = "Mundo"
saudacao = "Olá, #{nome}!"   # interpolação

# Heredoc (multilinha):
texto = <<~HEREDOC
  Linha 1
  Linha 2
HEREDOC

# Métodos úteis:
"ruby".upcase         # "RUBY"
"RUBY".downcase       # "ruby"
"olá mundo".capitalize # "Olá mundo"
"a,b,c".split(",")    # ["a", "b", "c"]
" abc ".strip         # "abc"
"texto".reverse       # "otxet"
"olá".length          # 3
"banana".gsub("a", "o") # "bonono"
"teste".include?("es") # true

Interpolação #{} é a forma preferida. <<~HEREDOC cria strings multilinha (strip indent). gsub substitui todas as ocorrências. split divide em array. strip remove whitespace. Strings são mutáveis (usar .freeze para imutável).

Ranges e case
# Ranges:
(1..10).to_a       # [1, 2, ..., 10]
(1...10).to_a      # [1, 2, ..., 9]
("a".."f").to_a    # ["a", "b", "c", "d", "e", "f"]

# Verificar pertença:
(1..100).include?(50)  # true
(1..100).cover?(50)    # true (mais rápido)

# Endless/Beginless (Ruby 2.6+):
(18..)   # 18 ao infinito
(..100)  # infinito até 100

# case com ranges:
case idade
when 0..12  then "criança"
when 13..17 then "adolescente"
when 18..   then "adulto"
end

# Iterar:
(1..5).each { |i| print i }  # 12345

.. inclusivo, ... exclusivo. cover? é O(1) (não itera). Endless (18..) e beginless (..100) ranges. case/when com ranges é idiomático. Funciona com números, letras e datas.

Symbols e truthiness
# Symbols: identificadores imutáveis e únicos
:nome.object_id == :nome.object_id   # true
"nome".object_id == "nome".object_id # false

# Usados como hash keys e enums:
pessoa = { nome: "Ana", idade: 25 }
pessoa[:nome]  # "Ana"

# Truthiness em Ruby:
# Só nil e false são falsy!
!!nil      # false
!!false    # false
!!0        # true  (0 é truthy!)
!!""       # true  (string vazia é truthy!)
!![]       # true  (array vazio é truthy!)

# Verificar nil:
valor.nil?       # true/false
valor.nil? || valor.empty?  # para strings/arrays

Symbols são únicos em memória — ideais para keys e enums. Em Ruby, só nil e false são falsy — 0, "" e [] são truthy (diferente de JS/Python). .nil? verifica nil explicitamente.

Comentários e documentação
# Comentário de linha

=begin
Comentário multi-linha
(pouco usado)
=end

# Documentação (RDoc/YARD):
# Calcula o dobro de um número.
#
# @param n [Integer] o número
# @return [Integer] o dobro
def dobro(n)
  n * 2
end

# Anotações:
# TODO: implementar caching
# FIXME: bug com valores negativos
# NOTE: requer Ruby 3.0+

# Gerar docs:
# yard doc
# rdoc lib/

# para comentários de linha. =begin/=end para multi-linha (raro). YARD é o padrão de documentação (@param, @return). Anotações: TODO, FIXME, NOTE. yard doc gera HTML.

Controlo de Fluxo


8 cards
If / Unless
if idade >= 18
  puts "Adulto"
elsif idade >= 12
  puts "Adolescente"
else
  puts "Criança"
end

# unless (if negativo):
unless lista.empty?
  processar(lista)
end

# Modifier form (uma linha):
puts "Adulto" if idade >= 18
puts "Vazio" unless lista.any?

# if como expressão:
resultado = if x > 0
  "positivo"
else
  "negativo"
end

unless é o oposto de if — mais legível para condições negativas. A forma modifier (código if condição) é idiomática para uma linha. if é expressão (retorna valor). Sem parênteses na condição. Chaves substituídas por end.

Break, Next e Redo
[1, 2, 3, 4, 5].each do |n|
  next if n.even?    # salta pares (continue)
  break if n > 4     # para o ciclo
  puts n             # 1, 3
end

# redo: repete iteração atual
attempts = 0
3.times do |i|
  attempts += 1
  redo if i == 1 && attempts < 5
end

# break com valor:
resultado = [1, 2, 3].map do |n|
  break "parou" if n > 2
  n * 2
end
# resultado = "parou"

# retry: recomeça o begin inteiro

next salta para próxima iteração (continue). break termina o ciclo. redo repete a iteração atual sem avançar. break pode retornar valor. retry recomeça o bloco inteiro. Controlos flexíveis sobre iteração.

Case / When
case dia
when 1..5
  puts "Dia útil"
when 6, 7
  puts "Fim de semana"
else
  puts "Inválido"
end

# Com classes (=== operator):
case objeto
when String   then "É texto"
when Integer  then "É número"
when Array    then "É lista"
end

# Como expressão:
status = case codigo
when 200 then :ok
when 404 then :not_found
when 500 then :erro
end

# Com regex:
case input
when /\d+/ then "número"
when /\w+/ then "palavra"
end

case/when usa === para comparação — funciona com ranges, classes, regexps. É expressão que retorna valor. then permite uma linha. Mais poderoso que switch de C. Sem break (não faz fallthrough).

Ternário e ||=
# Ternário:
status = idade >= 18 ? "adulto" : "menor"

# || para defaults:
nome = input || "Anónimo"

# ||= (atribuição condicional):
@cache ||= calcular_dados()
# Só calcula se @cache for nil/false

# &&= (atribuição se truthy):
valor &&= valor.strip

# Safe navigation (&.):
cidade = usuario&.endereco&.cidade
# nil se qualquer nível for nil

# Combinado:
total = pedido&.itens&.sum(&:preco) || 0

||= é ubíquo para lazy initialization e caching. &. encadeia chamadas sem NoMethodError em nil. Ternário para condições simples. &&= só atribui se truthy. Estes operadores eliminam verificações nil verbosas.

Ciclos
# while:
n = 10
while n > 0
  n -= 1
end

# until (while negativo):
until fila.empty?
  processar(fila.pop)
end

# loop infinito:
loop do
  break if condicao
end

# Modifier:
n -= 1 while n > 0
processar until fila.empty?

# begin..end while (executa 1x):
begin
  input = gets
end while input.nil?

until executa enquanto a condição for falsa. loop do cria ciclo infinito (pare com break). Modifier form é idiomática para ciclos curtos. Ruby prefere iteradores (each, times) sobre loops manuais.

Pattern Matching (Ruby 3+)
# case/in (Ruby 3.0+):
case dados
in { nome: String => n, idade: Integer => i }
  puts "#{n} tem #{i} anos"
in [1, 2, *resto]
  puts "Lista com resto: #{resto}"
in { tipo: "admin" }
  puts "Administrador"
else
  puts "Não corresponde"
end

# Destructuring:
case ponto
in [x, y] if x == y
  puts "Diagonal"
end

# One-line pattern (Ruby 3.0+):
{ nome: "Ana" } => { nome: }
puts nome  # "Ana"

Pattern matching (Ruby 3.0+) decompõe estruturas complexas. in verifica padrões com binding de variáveis. Suporta guards (if), splats (*) e pin (^). One-line com =>. Poderoso para parsing de dados e APIs.

Iteradores
# times:
5.times { |i| puts i }     # 0 1 2 3 4

# each:
[1, 2, 3].each { |n| puts n }

# upto / downto:
1.upto(5) { |i| print i }    # 12345
10.downto(1) { |i| print i } # 10987654321

# step:
(0..20).step(5) { |i| print i }  # 0 5 10 15 20

# each_with_index:
["a", "b", "c"].each_with_index do |v, i|
  puts "#{i}: #{v}"
end

# each_with_object:
hash = [1, 2, 3].each_with_object({}) do |n, h|
  h[n] = n ** 2
end

Iteradores são a forma idiomática — substituem for loops. times, upto, downto, step para contagens. each_with_index dá índice. each_with_object acumula em coleção. Blocos {} para uma linha, do..end para múltiplas.

Begin / Rescue
begin
  resultado = operacao_arriscada
rescue ArgumentError => e
  puts "Argumento inválido: #{e.message}"
rescue StandardError => e
  puts "Erro: #{e.class} - #{e.message}"
else
  puts "Sucesso: #{resultado}"
ensure
  puts "Sempre executa (cleanup)"
end

# Raise:
raise ArgumentError, "Idade inválida" if idade < 0

# Retry:
begin
  conectar
rescue TimeoutError
  retry  # tenta novamente
end

# Modifier:
valor = Integer(input) rescue 0

begin/rescue é o try/catch de Ruby. else executa só sem exceção. ensure sempre executa (finally). raise lança. retry recomeça. Hierarquia: Exception > StandardError > RuntimeError. Modifier rescue para uma linha.

Coleções


8 cards
Arrays
nums = [1, 2, 3, 4, 5]
nums << 6         # append (push)
nums.pop          # remove último
nums.shift        # remove primeiro
nums.unshift(0)   # adiciona no início

nums[0]           # primeiro
nums[-1]          # último
nums[1..3]        # slice [2, 3, 4]
nums.length       # tamanho
nums.include?(3)  # true

# Literais especiais:
%w[um dois três]   # ["um", "dois", "três"]
%i[um dois três]   # [:um, :dois, :três]

# Heterogéneo:
misto = [1, "dois", :tres, [4], nil]

Arrays são dinâmicos e heterogéneos. << é o operador idiomático de append. Índices negativos contam do fim. %w[] cria arrays de strings sem aspas. %i[] cria arrays de symbols. Slice com ranges. Mutáveis por default.

Ordenação e pesquisa
nums = [5, 2, 8, 1, 9]

nums.sort              # [1, 2, 5, 8, 9]
nums.sort.reverse      # [9, 8, 5, 2, 1]
nums.sort_by { |n| -n } # descendente

# Pesquisa:
nums.find { |n| n > 4 }     # 5 (primeiro)
nums.any? { |n| n > 8 }     # true
nums.all? { |n| n > 0 }     # true
nums.none? { |n| n > 10 }   # true
nums.count { |n| n.even? }  # 2

# min/max:
nums.min      # 1
nums.max      # 9
nums.minmax   # [1, 9]

# bsearch (array ordenado):
[1, 3, 5, 7, 9].bsearch { |n| n >= 5 }  # 5

sort retorna novo array ordenado. sort_by ordena por chave customizada. find retorna primeiro match. any?/all?/none? verificam condições. bsearch é O(log n) em arrays ordenados.

Hashes
pessoa = { nome: "Ana", idade: 25, cidade: "Lisboa" }

pessoa[:nome]              # "Ana"
pessoa.fetch(:email, "N/A") # "N/A" (default)
pessoa[:email] = "ana@mail.com"
pessoa.delete(:cidade)

pessoa.keys     # [:nome, :idade, :email]
pessoa.values   # ["Ana", 25, "ana@mail.com"]
pessoa.each { |k, v| puts "#{k}: #{v}" }

# Hash com default:
contador = Hash.new(0)
contador[:a] += 1   # 1 (sem KeyError)

# Hash com bloco default:
grupos = Hash.new { |h, k| h[k] = [] }
grupos[:frutas] << "maçã"

Hashes mapeiam chaves para valores. fetch com default evita nil silencioso. Hash.new(0) para contadores. Hash.new { } para agrupamentos. Keys tipicamente symbols. dig para navegação aninhada segura.

Group, Zip e Tally
pessoas = [
  { nome: "Ana", idade: 25 },
  { nome: "Rui", idade: 30 },
  { nome: "Mia", idade: 25 }
]

# group_by:
por_idade = pessoas.group_by { |p| p[:idade] }
# {25=>[Ana,Mia], 30=>[Rui]}

# zip: combina arrays
[1, 2, 3].zip(["a", "b", "c"])
# [[1,"a"], [2,"b"], [3,"c"]]

# partition: divide em dois
pares, impares = (1..6).partition(&:even?)

# tally (Ruby 2.7+): contagem
"abracadabra".chars.tally
# {"a"=>5, "b"=>2, "r"=>2, "c"=>1, "d"=>1}

# chunk: agrupa consecutivos
[1, 1, 2, 3, 3].chunk_while { |a, b| a == b }.to_a
# [[1,1], [2], [3,3]]

group_by agrupa por chave num Hash de Arrays. zip combina posição a posição. partition divide em dois. tally conta ocorrências. chunk_while agrupa elementos consecutivos. Todos retornam novos objetos.

Map e Select
nums = [1, 2, 3, 4, 5, 6]

# map: transforma cada elemento
dobros = nums.map { |n| n * 2 }     # [2,4,6,8,10,12]

# select/reject: filtra
pares = nums.select { |n| n.even? }  # [2, 4, 6]
impares = nums.reject { |n| n.even? } # [1, 3, 5]

# Shorthand com &:
strings = nums.map(&:to_s)   # ["1", "2", ...]

# Encadeamento:
resultado = nums
  .select { |n| n > 2 }
  .map { |n| n * 10 }
  # [30, 40, 50, 60]

# filter_map (Ruby 2.7+):
positivos = [-1, 2, -3, 4].filter_map do |n|
  n * 2 if n > 0
end  # [4, 8]

map transforma, select filtra (mantém truthy), reject filtra (mantém falsy). &:método é shorthand idiomático. filter_map combina select+map num passo. Encadeamento cria pipelines declarativos.

Lazy e infinito
# Lazy: processa só o necessário
resultado = (1..Float::INFINITY).lazy
  .select { |n| n.even? }
  .map { |n| n ** 2 }
  .first(5)
# [4, 16, 36, 64, 100] — sem loop infinito!

# Sem lazy: tentaria processar infinito
# Com lazy: avalia elemento a elemento

# Benchmark: 10M elementos
(1..10_000_000).lazy
  .select(&:even?)
  .map { |n| n * 2 }
  .first(10)
# Instantâneo! (só processa 20 elementos)

# Enumerator infinito:
fib = Enumerator.new do |y|
  a, b = 0, 1
  loop { y << a; a, b = b, a + b }
end
fib.first(10)  # [0,1,1,2,3,5,8,13,21,34]

lazy adia processamento — essencial para sequências infinitas ou grandes. Avalia elemento a elemento (não materializa array). Enumerator.new cria geradores infinitos. Ideal para pipelines com first(n).

Reduce e accumulate
nums = [1, 2, 3, 4, 5]

# reduce/inject: acumula num valor
soma = nums.reduce(0) { |acc, n| acc + n }  # 15
soma = nums.reduce(:+)                       # 15 (shorthand)
produto = nums.reduce(1, :*)                 # 120

# each_with_object: acumula em coleção
hash = nums.each_with_object({}) do |n, h|
  h[n] = n ** 2
end
# {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}

# Agrupar com reduce:
palavras = %w[ana rui ana mia]
freq = palavras.reduce(Hash.new(0)) do |h, p|
  h[p] += 1
  h
end  # {"ana"=>2, "rui"=>1, "mia"=>1}

reduce (alias inject) acumula elementos num único valor. Primeiro arg é valor inicial. &:símbolo para operações binárias. each_with_object evita variável externa. Base para soma, produto, contagem, agrupamento.

Set e operações
require "set"

a = Set[1, 2, 3, 4]
b = Set[3, 4, 5, 6]

a | b    # união: {1,2,3,4,5,6}
a & b    # interseção: {3,4}
a - b    # diferença: {1,2}
a ^ b    # simétrica: {1,2,5,6}

a.include?(3)     # true (O(1)!)
a.add(5)
a.delete(1)
a.subset?(Set[1, 2, 3, 4, 5])

# Arrays também têm operações:
[1, 2, 3] | [3, 4, 5]   # [1,2,3,4,5]
[1, 2, 3] & [3, 4, 5]   # [3]
[1, 2, 3] - [3]          # [1, 2]

# uniq:
[1, 2, 2, 3, 3, 3].uniq  # [1, 2, 3]

Set tem include? O(1) (vs O(n) de Array). Operações: | união, & interseção, - diferença, ^ simétrica. Arrays também suportam |, &, -. uniq remove duplicados.

Métodos e Blocos


8 cards
Métodos
def saudar(nome, prefixo = "Olá")
  "#{prefixo}, #{nome}!"
end

saudar("Ana")            # "Olá, Ana!"
saudar("Ana", "Bom dia") # "Bom dia, Ana!"

# Keyword arguments:
def conectar(host:, porta: 3306, ssl: false)
  # ...
end
conectar(host: "localhost", ssl: true)

# Retorno implícito (última expressão):
def dobro(n)
  n * 2  # sem return!
end

# Endless method (Ruby 3.0+):
def triplo(n) = n * 3

Retorno implícito — a última expressão é retornada. Keyword args (nome:) tornam chamadas auto-documentadas. Endless methods (def x = expr) para uma linha. Parâmetros default eliminam sobrecargas. Sem parênteses na chamada é idiomático.

Method chaining e tap
# Method chaining:
resultado = "Olá Mundo"
  .downcase
  .gsub("mundo", "ruby")
  .capitalize
# "Olá ruby"

# tap: efeito lateral sem quebrar chain
[3, 1, 2]
  .tap { |a| puts "Original: #{a}" }
  .sort
  .tap { |a| puts "Ordenado: #{a}" }
  .first

# then (Ruby 2.6+):
5.then { |n| n * 2 }.then { |n| n + 1 }
# 11

# dig (navegação segura):
dados = { user: { address: { city: "Lisboa" } } }
dados.dig(:user, :address, :city)  # "Lisboa"
dados.dig(:user, :phone, :number)  # nil (sem erro!)

Method chaining cria pipelines legíveis. tap permite inspeção/logging sem interromper. then transforma valor no pipeline (como |> de Elixir). dig navega hashes aninhados sem NoMethodError. Tudo retorna self ou novo objeto.

Blocos e yield
# yield: executa o bloco passado
def medir_tempo
  inicio = Time.now
  resultado = yield
  puts "Demorou: #{Time.now - inicio}s"
  resultado
end

medir_tempo { operacao_lenta() }

# Bloco com parâmetros:
def repetir(n)
  n.times { |i| yield(i) }
end
repetir(3) { |i| puts "Iteração #{i}" }

# block_given?:
def talvez
  yield if block_given?
end

# Blocos {} vs do..end:
[1, 2].each { |n| puts n }    # uma linha
[1, 2].each do |n|            # múltiplas
  puts n
  puts n * 2
end

Blocos são a feature mais idiomática de Ruby — funções anónimas passadas implicitamente. yield executa o bloco. block_given? verifica. {} para uma linha, do..end para múltiplas. Usados em each, map, open, etc.

Métodos de classe
class Matematica
  # Método de classe (self.):
  def self.pi
    3.14159
  end

  # Alternativa com class << self:
  class << self
    def raiz(n)
      Math.sqrt(n)
    end
  end
end

Matematica.pi       # 3.14159
Matematica.raiz(16) # 4.0

# Singleton method num objeto:
obj = Object.new
def obj.gritar
  "SOU ESPECIAL"
end
obj.gritar  # "SOU ESPECIAL"

# Singleton pattern:
require "singleton"
class DB
  include Singleton
end
DB.instance  # sempre o mesmo

Métodos de classe (self.nome) são chamados na classe. class << self abre a singleton class. Singleton methods existem só num objeto. include Singleton da stdlib garante instância única. Útil para factories e utilitários.

Procs e Lambdas
# Proc: bloco como objeto
dobro = Proc.new { |n| n * 2 }
dobro.call(5)   # 10
dobro.(5)       # 10 (shorthand)

# Lambda: proc estrito
triplo = ->(n) { n * 3 }
triplo.call(5)  # 15

# Diferenças:
# 1. Lambda verifica aridade (nº args)
# 2. return em lambda sai da lambda
#    return em proc sai do método

# & para converter bloco<->proc:
[1, 2, 3].map(&:to_s)   # ["1", "2", "3"]
# Equivale: map { |n| n.to_s }

# Proc como parâmetro:
def aplicar(proc, valor)
  proc.call(valor)
end

Proc e lambda são blocos como objetos. Lambda é mais estrito (verifica args, return local). &:método converte símbolo em proc — shorthand idiomático. ->(args) { } é a sintaxe moderna de lambda. First-class citizens.

Currying e composição
# Currying:
soma = ->(a, b, c) { a + b + c }
curried = soma.curry
curried[1][2][3]  # 6

add5 = curried[5]
add5[3][2]  # 10

# Composição (Ruby 2.6+):
dobro = ->(n) { n * 2 }
incremento = ->(n) { n + 1 }

composto = dobro << incremento  # dobro(incremento(n))
composto.(5)  # 12

composto2 = dobro >> incremento # incremento(dobro(n))
composto2.(5)  # 11

# Aplicação prática:
pipeline = ->(s) { s.strip } >> ->(s) { s.downcase }
pipeline.("  OLÁ  ")  # "olá"

curry transforma função N-args em cadeia de funções 1-arg. << e >> compõem funções (right-to-left / left-to-right). Técnicas funcionais para código declarativo. Lambdas são first-class e composáveis.

Splats e Destructuring
# Splat (*): args variáveis
def soma(*nums)
  nums.reduce(0, :+)
end
soma(1, 2, 3, 4, 5)  # 15

# Double splat (**): kwargs variáveis
def config(**opts)
  opts.each { |k, v| puts "#{k}=#{v}" }
end
config(host: "localhost", port: 80)

# Block arg (&):
def com_bloco(&bloco)
  bloco.call
end

# Destructuring:
def processar((nome, idade))
  "#{nome} tem #{idade}"
end
processar(["Ana", 25])

# Splat em atribuição:
primeiro, *resto = [1, 2, 3, 4, 5]
# primeiro = 1, resto = [2, 3, 4, 5]

* coleta args extras num Array. ** coleta kwargs num Hash. & coleta bloco como Proc. Destructuring extrai elementos de arrays. Splat em atribuição divide arrays. Juntos criam APIs flexíveis e expressivas.

Method missing e respond_to
class Flexivel
  def method_missing(nome, *args, &bloco)
    "Chamou #{nome} com #{args.inspect}"
  end

  def respond_to_missing?(nome, priv = false)
    true
  end
end

f = Flexivel.new
f.qualquer_coisa(1, 2)  # "Chamou qualquer_coisa com [1, 2]"
f.respond_to?(:abc)     # true

# send: chama método por nome
obj.send(:nome)          # equivale a obj.nome
obj.send("nome=", "novo") # setter dinâmico

# public_send: só métodos públicos
obj.public_send(:nome)

# Usado em: ActiveRecord, Delegator, DSLs

method_missing intercepta métodos inexistentes — base de ActiveRecord e DSLs. Sempre implementar respond_to_missing? junto. send chama por nome/símbolo. public_send respeita visibilidade. Usar com moderação.

Classes e Módulos


8 cards
Classes
class Pessoa
  attr_accessor :nome, :idade  # getters + setters
  attr_reader :email           # só getter

  def initialize(nome, idade)
    @nome = nome
    @idade = idade
    @email = "#{nome}@mail.com"
  end

  def saudacao
    "Olá, sou #{@nome}"
  end
end

p = Pessoa.new("Ana", 25)
p.nome = "Maria"   # setter via attr_accessor
p.saudacao         # "Olá, sou Maria"
p.email            # "ana@mail.com" (read-only)

attr_accessor/attr_reader/attr_writer geram getters/setters automaticamente. initialize é o construtor (chamado por .new). @ define variáveis de instância. Ruby é puramente OO — tudo é objeto.

Structs e Data
# Struct: classe rápida com atributos
Ponto = Struct.new(:x, :y) do
  def distancia
    Math.sqrt(x**2 + y**2)
  end
end

p = Ponto.new(3, 4)
p.x          # 3
p.distancia  # 5.0

# Keyword init:
Config = Struct.new(:host, :port, keyword_init: true)
c = Config.new(host: "localhost", port: 80)

# Data (Ruby 3.2+): imutável
Ponto2 = Data.define(:x, :y)
p2 = Ponto2.new(x: 3, y: 4)
# p2.x = 5  # NoMethodError! (imutável)

# Ambos geram: initialize, getters, ==, to_s, to_h

Struct cria classes simples sem boilerplate. Data (Ruby 3.2+) é imutável — ideal para value objects. Ambos geram initialize, getters, ==, to_s e to_h automaticamente. keyword_init: true para args nomeados.

Herança
class Animal
  def initialize(nome)
    @nome = nome
  end

  def som
    "..."
  end
end

class Cao < Animal
  def som
    "#{@nome}: Au!"
  end
end

class Gato < Animal
  def som
    "#{@nome}: Miau!"
  end
end

Cao.new("Rex").som   # "Rex: Au!"

# super chama o método pai:
class Filhote < Cao
  def som
    super + " (baixinho)"
  end
end

Ruby suporta herança simples (<). super chama o método da classe pai. Métodos sobrescritos sem keyword. Não existe herança múltipla de classes — usar modules (mixins) para composição de comportamento.

Metaprogramação
# define_method: cria métodos dinamicamente
class Modelo
  [:nome, :email, :idade].each do |attr|
    define_method(attr) do
      instance_variable_get("@#{attr}")
    end
    define_method("#{attr}=") do |v|
      instance_variable_set("@#{attr}", v)
    end
  end
end

# instance_eval: muda self
config.instance_eval do
  @host = "localhost"
  @port = 3000
end

# class_eval: avalia na classe
String.class_eval do
  def shout
    "#{upcase}!"
  end
end

define_method cria métodos em runtime. instance_eval executa bloco com self mudado — base de DSLs. class_eval avalia código no contexto da classe. instance_variable_get/set acede variáveis dinamicamente. Poderoso mas usar com moderação.

Modules e Mixins
module Nadador
  def nadar
    "#{@nome} está a nadar"
  end
end

module Voador
  def voar
    "#{@nome} está a voar"
  end
end

class Pato < Animal
  include Nadador   # métodos de instância
  include Voador
end

class Config
  extend Nadador    # métodos de classe
end

# Module como namespace:
module API
  class Client; end
  VERSION = "1.0"
end

API::Client.new

Modules são a alternativa à herança múltipla. include adiciona métodos de instância. extend adiciona métodos de classe. Um módulo pode ser incluído em múltiplas classes. Também servem como namespaces (API::Client).

Refinements
# Refinements: monkey patch com scope
module StringExtensions
  refine String do
    def shout
      "#{upcase}!"
    end
  end
end

# Só ativo neste ficheiro:
using StringExtensions
"olá".shout  # "OLÁ!"

# Monkey patching (global — evitar):
class String
  def titleize
    split.map(&:capitalize).join(" ")
  end
end

# open class (extensão segura):
class Array
  def segundo
    self[1]
  end
end

Refinements permitem modificar classes com scope limitado (using) — evita conflitos globais. Monkey patching modifica globalmente — poderoso mas perigoso. Rails usa extensivamente. Preferir refinements ou módulos para extensões.

Access control
class Conta
  def initialize(saldo)
    @saldo = saldo
  end

  def saldo
    @saldo
  end

  private   # métodos abaixo são privados

  def validar(valor)
    valor > 0
  end

  protected  # acessível entre instâncias

  def comparar(outra)
    @saldo <=> outra.saldo
  end
end

# public (default): acessível de fora
# private: sem receptor explícito (self)
# protected: entre instâncias da mesma classe

public (default): acessível de qualquer lugar. private: só chamável sem receptor explícito. protected: permite chamada entre instâncias da mesma classe. Em Ruby, privacidade é por convenção — não é estrita como Java.

Comparable e Enumerable
# Comparable: implementa <=>
class Temperatura
  include Comparable
  attr_reader :celsius

  def initialize(celsius)
    @celsius = celsius
  end

  def <=>(outra)
    celsius <=> outra.celsius
  end
end

t1 = Temperatura.new(25)
t2 = Temperatura.new(30)
t1 < t2       # true
t1.between?(Temperatura.new(20), Temperatura.new(30))

# Enumerable: implementa each
class Lista
  include Enumerable
  def each(&bloco)
    # iterar elementos
  end
end
# Ganha: map, select, reduce, sort, etc.

Comparable + <=> dá todos os operadores de comparação. Enumerable + eachmap, select, reduce, sort, etc. Mixins da stdlib que adicionam comportamento rico com mínimo código.

Avançado


8 cards
Error handling avançado
# Hierarquia de exceções:
# Exception
#   ├── StandardError (rescue default)
#   │   ├── ArgumentError
#   │   ├── RuntimeError
#   │   ├── TypeError
#   │   ├── NoMethodError
#   │   └── IOError
#   └── NoMemoryError, SignalException...

# Exceção customizada:
class SaldoInsuficiente < StandardError
  attr_reader :saldo, :valor

  def initialize(saldo, valor)
    @saldo = saldo
    @valor = valor
    super("Saldo #{saldo} insuficiente para #{valor}")
  end
end

raise SaldoInsuficiente.new(100, 200)

# Resgatar específica:
rescue SaldoInsuficiente => e
  puts e.saldo

Resgatar só StandardError (não Exception). Criar exceções customizadas herdando de StandardError. super(message) define a mensagem. Atributos extras para contexto. Hierarquia permite rescue por categoria.

Regex
# Matching:
"olá mundo" =~ /mundo/    # 4 (índice)
"olá mundo".match?(/\d/)  # false

# Captura:
m = "2024-01-15".match(/(\d{4})-(\d{2})-(\d{2})/)
m[1]  # "2024"
m[2]  # "01"
m[3]  # "15"

# Named captures:
m = "Ana:25".match(/(?<nome>\w+):(?<idade>\d+)/)
m[:nome]   # "Ana"
m[:idade]  # "25"

# Substituição:
"abc123".gsub(/\d+/, "X")   # "abcX"
"a1b2c3".scan(/\d/)          # ["1", "2", "3"]

# Modificadores:
/regex/i   # case-insensitive
/regex/m   # multiline
/regex/x   # verbose (comentários)

=~ retorna índice do match. match? retorna boolean (mais rápido). Captura com () e named com (?<nome>). gsub substitui, scan extrai todos. %r{} como alternativa a //.

Threads e Mutex
# Threads:
threads = 5.times.map do |i|
  Thread.new do
    sleep(rand)
    puts "Thread #{i} terminou"
  end
end
threads.each(&:join)  # espera todas

# Mutex (thread-safety):
mutex = Mutex.new
contador = 0
10.times.map do
  Thread.new do
    1000.times { mutex.synchronize { contador += 1 } }
  end
end.each(&:join)
contador  # 10000 (sem race condition)

# Queue (thread-safe):
queue = Queue.new
Thread.new { queue.push("trabalho") }
item = queue.pop  # bloqueia até ter item

Ruby tem GIL — paralelismo limitado para CPU-bound. Mutex protege estado partilhado. Queue é thread-safe para comunicação. join espera thread terminar. Para paralelismo real, usar Ractor (Ruby 3.0+).

IO e Ficheiros
# Ler ficheiro inteiro:
conteudo = File.read("dados.txt")

# Ler linha a linha:
File.foreach("dados.txt") do |linha|
  puts linha.chomp
end

# Escrever:
File.write("saida.txt", "Olá!\n")

# Append:
File.open("log.txt", "a") do |f|
  f.puts "Nova entrada"
end

# Verificar:
File.exist?("dados.txt")   # true/false
File.size("dados.txt")     # bytes
File.extname("foto.png")   # ".png"

# Dir:
Dir.glob("*.rb")           # lista ficheiros
Dir.mkdir("nova_pasta")
Dir.pwd                    # diretório atual

# STDIN:
input = gets.chomp

File.read lê inteiro. File.foreach itera linhas (memory-friendly). File.write escreve. Bloco com File.open fecha automaticamente. Dir.glob para padrões. gets lê stdin. chomp remove newline.

Ractors (Ruby 3+)
# Ractor: paralelismo real (sem GIL)
r1 = Ractor.new do
  resultado = calculo_pesado
  resultado
end

r2 = Ractor.new do
  outro_calculo_pesado
end

# Resultados:
v1 = r1.take   # espera e obtém
v2 = r2.take

# Comunicação por mensagens:
r = Ractor.new do
  msg = Ractor.receive
  msg * 2
end
r.send(21)
r.take  # 42

# Restrições:
# - Sem estado partilhado mutável
# - Objetos são copiados ou frozen
# - Ideal para CPU-bound isolado

Ractors (Ruby 3.0+) permitem paralelismo real — memória isolada. Comunicação por mensagens (send/receive/take). Sem objetos mutáveis partilhados. Ideal para CPU-bound. Alternativa: Fibers para concorrência cooperativa.

JSON e HTTP
require "json"
require "net/http"
require "uri"

# JSON:
hash = { nome: "Ana", idade: 25 }
json_str = JSON.generate(hash)
# '{"nome":"Ana","idade":25}'

dados = JSON.parse('{"nome":"Ana"}')
dados["nome"]  # "Ana"

# Ficheiro JSON:
File.write("data.json", JSON.pretty_generate(hash))
dados = JSON.parse(File.read("data.json"))

# HTTP GET:
uri = URI("https://api.example.com/dados")
resposta = Net::HTTP.get_response(uri)
if resposta.is_a?(Net::HTTPSuccess)
  dados = JSON.parse(resposta.body)
end

# HTTP POST:
Net::HTTP.post(uri, json_str, "Content-Type" => "application/json")

JSON.generate serializa, JSON.parse desserializa. JSON.pretty_generate para output legível. Net::HTTP da stdlib para requests. Gems: httparty ou faraday para APIs mais complexas. URI faz parsing de URLs.

DSLs em Ruby
# DSL com instance_eval:
class HTMLBuilder
  def initialize
    @html = []
  end

  def method_missing(nome, *args, &bloco)
    @html << "<#{nome}>#{args.first}</#{nome}>"
  end

  def to_s
    @html.join("\n")
  end
end

def html(&bloco)
  b = HTMLBuilder.new
  b.instance_eval(&bloco)
  b.to_s
end

html do
  h1 "Título"
  p "Conteúdo"
end
# <h1>Título</h1>
# <p>Conteúdo</p>

DSLs são ubíquas em Ruby — Rails, RSpec, Rake, Gemfile. instance_eval + method_missing criam APIs declarativas. O bloco executa com self mudado. Sintaxe natural sem receptor. Padrão fundamental do ecossistema Ruby.

Testing (Minitest)
require "minitest/autorun"

class CalculadoraTest < Minitest::Test
  def setup
    @calc = Calculadora.new
  end

  def test_soma
    assert_equal 5, @calc.soma(2, 3)
  end

  def test_divisao_por_zero
    assert_raises(ZeroDivisionError) do
      @calc.dividir(1, 0)
    end
  end

  def test_negativo
    refute @calc.positivo?(-1)
  end
end

# Executar:
# ruby test/calculadora_test.rb

# Assertions comuns:
# assert_equal, assert_nil, assert_includes
# refute, refute_nil, assert_raises
# assert_match(/regex/, string)

Minitest vem na stdlib — zero dependências. assert_equal compara valores. assert_raises verifica exceções. refute nega. setup executa antes de cada teste. Rails usa Minitest por default. Alternativa: RSpec.

Instalação e Setup


8 cards
Instalar Ruby (rbenv)
# macOS (Homebrew):
brew install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0

# Linux (Ubuntu):
sudo apt install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0

# Configurar shell (.bashrc/.zshrc):
eval "$(rbenv init -)"

# Verificar:
ruby --version
# ruby 3.3.0 (2024-01-01) [x86_64-linux]

rbenv é o gestor de versões recomendado. Instala múltiplas versões lado a lado. rbenv global define a versão padrão. rbenv local por projeto (ficheiro .ruby-version). Preferir sobre rvm (mais leve e simples).

Gems e Bundler
# Instalar gem:
gem install httparty
gem install rails -v 7.1.0

# Listar gems:
gem list
gem list --local

# Bundler (gestão de dependências):
bundle init          # cria Gemfile

# Gemfile:
source "https://rubygems.org"
gem "rails", "~> 7.1"
gem "puma", ">= 5.0"

group :test do
  gem "rspec"
end

# Comandos:
bundle install       # instala do Gemfile
bundle update        # atualiza gems
bundle exec rspec    # executa com gems

gem install instala pacotes. Bundler gere dependências por projeto. Gemfile declara gems, Gemfile.lock fixa versões. ~> permite patches. bundle exec garante ambiente correto. Essencial para projetos reais.

Windows e Docker
# Windows:
# 1. Download em rubyinstaller.org
# 2. Instalar com MSYS2 (checkbox)
# 3. Verificar no terminal:
ruby --version
gem --version

# Docker:
docker run -it ruby:3.3 ruby -e "puts 'Olá'"

# Dockerfile:
# FROM ruby:3.3-slim
# WORKDIR /app
# COPY Gemfile* ./
# RUN bundle install
# COPY . .
# CMD ["ruby", "app.rb"]

# WSL2 (recomendado para Windows):
wsl --install
# Depois instalar rbenv dentro do WSL

Windows: RubyInstaller com MSYS2. Docker tem imagens oficiais (ruby:3.3-slim). WSL2 é recomendado para desenvolvimento sério em Windows. Ruby 3.x requer compilador C para gems nativas.

Estrutura de projeto
meu_projeto/
├── Gemfile           # dependências
├── Gemfile.lock      # versões exatas
├── Rakefile          # tarefas (rake)
├── .ruby-version     # versão do Ruby
├── lib/
│   ├── meu_projeto.rb
│   └── meu_projeto/
│       ├── version.rb
│       └── core.rb
├── spec/             # testes (RSpec)
│   └── core_spec.rb
├── bin/
│   └── console       # IRB com projeto
└── README.md

# Rakefile:
task :default => :spec
task :spec do
  sh "bundle exec rspec"
end

lib/ contém o código. spec/ ou test/ para testes. Gemfile + Gemfile.lock para dependências. .ruby-version fixa versão (rbenv). Rakefile define tarefas. bin/console abre IRB com o projeto carregado.

Hello World e scripts
# ola.rb
puts "Olá, Mundo!"

# Sem main() — executa de cima para baixo
# puts = print com newline
# print = sem newline

# Executar:
ruby ola.rb

# Script com argumentos:
# args.rb
puts "Args: #{ARGV.inspect}"
# ruby args.rb um dois três
# Args: ["um", "dois", "três"]

# Shebang (Linux/Mac):
#!/usr/bin/env ruby
puts "Script executável"
# chmod +x script.rb
# ./script.rb

puts imprime com newline. print sem newline. Scripts executam top-level sem main(). ARGV é o array de argumentos. Shebang (#!/usr/bin/env ruby) torna scripts executáveis. Tudo é objeto em Ruby.

Require e Load
# require: carrega uma vez (stdlib/gems)
require "json"
require "net/http"
require "date"

# require_relative: caminho relativo
require_relative "lib/minha_classe"
require_relative "../utils/helper"

# load: recarrega sempre (dev)
load "config.rb"

# autoload: lazy loading
autoload :Parser, "lib/parser"
# Parser só é carregado quando usado!

# No Gemfile (Bundler):
require "bundler/setup"
Bundler.require(:default)
# Carrega todas as gems do Gemfile

require carrega ficheiro/gem uma vez. require_relative usa caminho relativo ao ficheiro. load recarrega sempre (útil em dev). autoload faz lazy loading. Bundler.require carrega todas as gems do Gemfile automaticamente.

IRB (REPL interativo)
# Iniciar:
$ irb

irb> 2 + 3
=> 5

irb> "ruby".upcase
=> "RUBY"

irb> [1,2,3].map { |n| n * 2 }
=> [2, 4, 6]

irb> exit

# Pry (REPL melhorado):
gem install pry
$ pry

pry> ls String    # lista métodos
pry> show-source Array#map
pry> cd [1,2,3]   # navega para objeto

# Dicas:
# Tab = autocomplete
# _ = último resultado

IRB é o REPL nativo do Ruby. => mostra o resultado. Pry é alternativo com mais features (ls, show-source, cd). Ideal para experimentar e debug. Tab faz autocomplete de métodos.

Rake (tarefas)
# Rakefile
desc "Diz olá"
task :ola do
  puts "Olá do Rake!"
end

desc "Tarefa com argumentos"
task :saudar, [:nome] do |t, args|
  puts "Olá, #{args.nome}!"
end

# Dependências entre tarefas:
task :build => [:clean, :compile]

task :clean do
  puts "Limpar..."
end

task :compile do
  puts "Compilar..."
end

# Executar:
rake ola
rake "saudar[Ana]"
rake build       # executa clean + compile
rake -T          # lista tarefas

Rake é o make do Ruby. task :nome define tarefas. desc adiciona descrição. Dependências com =>. Argumentos com [:arg]. rake -T lista tarefas. Rails usa Rake extensivamente (rake db:migrate).

Ecossistema


8 cards
Rails (básico)
# Instalar:
gem install rails
rails new minha_app
cd minha_app
rails server    # http://localhost:3000

# Estrutura MVC:
# app/models/       → Model (ActiveRecord)
# app/controllers/  → Controller
# app/views/        → View (ERB)

# Gerar scaffold:
rails generate scaffold Post title:string body:text
rails db:migrate

# Model:
class Post < ApplicationRecord
  validates :title, presence: true
  has_many :comments
end

# Controller:
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Rails é o framework web mais popular de Ruby. Convenção sobre configuração. ActiveRecord é o ORM. scaffold gera CRUD completo. rails db:migrate gere schema. MVC: Models, Controllers, Views. Ecossistema enorme de gems.

Debugging
# binding.irb (breakpoint):
def metodo_bugado(x)
  resultado = x * 2
  binding.irb    # para aqui!
  resultado + 1
end

# binding.pry (melhor):
# gem "pry"
require "pry"
def metodo
  binding.pry
end

# Comandos no Pry:
# ls           → variáveis locais
# cd objeto    → navegar
# show-source  → ver código
# whereami     → localização
# step/next    → debug passo a passo

# debug gem (Ruby 3.1+):
# gem "debug"
require "debug"
binding.b    # breakpoint moderno

# Quick debug:
p variavel          # imprime com inspect
puts variavel.inspect

binding.irb / binding.pry criam breakpoints interativos. Pry tem mais features (ls, cd, show-source). Ruby 3.1+ tem gem debug nativa. p imprime com .inspect para debug rápido.

Sinatra (micro-framework)
# Gemfile:
# gem "sinatra"

require "sinatra"

get "/" do
  "Olá, Mundo!"
end

get "/ola/:nome" do
  "Olá, #{params[:nome]}!"
end

post "/dados" do
  body = request.body.read
  "Recebi: #{body}"
end

# Executar:
# ruby app.rb
# http://localhost:4567

# Com views (ERB):
get "/pagina" do
  @titulo = "Minha Página"
  erb :pagina   # views/pagina.erb
end

Sinatra é micro-framework — ideal para APIs pequenas e protótipos. Rotas com verbos HTTP (get, post). params acede parâmetros. erb renderiza templates. Sem ORM — adicionar ActiveRecord ou Sequel manualmente.

Performance
# Benchmark:
require "benchmark"

Benchmark.bm do |x|
  x.report("map:") { 1_000_000.times.map { |i| i * 2 } }
  x.report("each:") { arr = []; 1_000_000.times.each { |i| arr << i * 2 } }
end

# benchmark-ips (gem):
# gem "benchmark-ips"
Benchmark.ips do |x|
  x.report("gsub") { "hello".gsub("l", "r") }
  x.report("tr")   { "hello".tr("l", "r") }
  x.compare!
end

# Dicas de performance:
# - freeze strings: # frozen_string_literal: true
# - lazy para coleções grandes
# - Set#include? vs Array#include?
# - Evitar alocações em loops
# - ObjectSpace para memory profiling

Benchmark da stdlib para medições. benchmark-ips para comparações estatísticas. # frozen_string_literal: true evita alocação de strings. lazy para coleções grandes. Set para lookups O(1). Evitar alocações em hot paths.

RSpec
# Gemfile: gem "rspec"
# bundle exec rspec --init

# spec/calculadora_spec.rb
RSpec.describe Calculadora do
  describe "#soma" do
    it "soma dois números" do
      calc = Calculadora.new
      expect(calc.soma(2, 3)).to eq(5)
    end

    it "soma negativos" do
      expect(subject.soma(-1, -2)).to eq(-3)
    end
  end

  context "com divisão" do
    it "lança erro em zero" do
      expect { subject.dividir(1, 0) }
        .to raise_error(ZeroDivisionError)
    end
  end
end

# Matchers: eq, be, include, match
# be_truthy, be_nil, be > 5

RSpec é o framework de testes mais popular. Sintaxe descritiva (describe, context, it). expect().to para asserções. subject é o objeto em teste. Matchers ricos: eq, include, raise_error. TDD/BDD friendly.

Deploy e produção
# Dockerfile para app Ruby:
# FROM ruby:3.3-slim
# RUN apt-get update && apt-get install -y build-essential
# WORKDIR /app
# COPY Gemfile* ./
# RUN bundle install --without development test
# COPY . .
# CMD ["ruby", "app.rb"]

# Puma (web server):
# gem "puma"
# bundle exec puma -p 3000

# Environment variables:
# gem "dotenv"
require "dotenv/load"
DB_URL = ENV["DATABASE_URL"]

# Produção:
# - RACK_ENV=production
# - bundle install --without development test
# - Usar Puma/Unicorn como server
# - Logs: STDOUT (12-factor)
# - Health check endpoint

Docker com imagem ruby:3.3-slim. Puma é o web server padrão. dotenv para variáveis de ambiente. RACK_ENV=production ativa otimizações. Logs para STDOUT (12-factor). Platforms: Heroku, Fly.io, Render, Railway.

RuboCop (linting)
# Instalar:
gem install rubocop

# Executar:
rubocop              # verifica tudo
rubocop app/         # só pasta app/
rubocop -a           # auto-corrige

# .rubocop.yml (config):
AllCops:
  TargetRubyVersion: 3.3
  NewCops: enable

Style/StringLiterals:
  EnforcedStyle: double_quotes

Metrics/MethodLength:
  Max: 20

Layout/LineLength:
  Max: 120

# Categorias:
# Style/    → estilo de código
# Layout/   → formatação
# Lint/     → bugs potenciais
# Metrics/  → complexidade

RuboCop é o linter/formatter padrão de Ruby. Verifica estilo, layout, bugs e complexidade. -a auto-corrige. .rubocop.yml configura regras. Integrar no CI e editor. Categorias: Style, Layout, Lint, Metrics.

Ferramentas úteis
# Solargraph (LSP para editores):
gem install solargraph
# Configurar no VS Code / Neovim

# yard (documentação):
gem install yard
yard doc lib/
yard server   # http://localhost:8808

# gem "awesome_print":
require "ap"
ap({ nome: "Ana", itens: [1, 2, 3] })
# Output colorido e formatado

# gem "httparty" (HTTP simples):
require "httparty"
resposta = HTTParty.get("https://api.ex.com/dados")
resposta.parsed_response

# gem "sequel" (SQL builder):
require "sequel"
DB = Sequel.connect("sqlite://db.sqlite3")
DB[:users].where(age: 25).all

# gem "sidekiq" (background jobs):
# Processamento assíncrono com Redis

Solargraph dá autocomplete/LSP. YARD gera documentação. awesome_print para debug colorido. HTTParty simplifica HTTP. Sequel é SQL builder leve. Sidekiq para background jobs. Ecossistema rico de gems para tudo.