Cheatsheet Ruby
Referência completa da linguagem Ruby — elegante, expressiva e orientada a objetos
Ruby
Básico
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") # trueInterpolaçã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/arraysSymbols 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
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
endIteradores 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 0begin/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
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 } # 5sort 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
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 * 3Retorno 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
endBlocos 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 mesmoMé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)
endProc 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, DSLsmethod_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
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_hStruct 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
endRuby 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
enddefine_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.newModules 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
endRefinements 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 classepublic (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 + each dá map, select, reduce, sort, etc. Mixins da stdlib que adicionam comportamento rico com mínimo código.
Avançado
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.saldoResgatar 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 itemRuby 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.chompFile.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
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.rbputs 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 resultadoIRB é 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 tarefasRake é 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
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
endRails é 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
endSinatra é 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 profilingBenchmark 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 > 5RSpec é 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 RedisSolargraph 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.