Noções Básicas
Variáveis e Tipos
Ruby é tipado dinamicamente—variáveis não precisam de declarações de tipo. Use .class para inspecionar o tipo e is_a? para verificações de tipo. Tudo é um objeto, incluindo números e booleanos.
name = "Alice" # string
age = 30 # integer
pi = 3.14 # float
is_dev = true # boolean
nums = [1, 2, 3] # array
puts name.class # String
puts age.is_a?(Integer) # trueSímbolos
Símbolos (:name) são strings imutáveis e internadas—apenas uma cópia existe na memória. Use-os para chaves de hash, nomes de métodos e identificadores onde a identidade importa mais que o conteúdo. Mais eficientes em memória que strings para uso repetido.
status = :active
puts status.class # Symbol
puts status.to_s # "active"
# Symbols are immutable, reusable strings
hash = { name: "Alice", status: :active }
puts hash[:status] # activeNil e Truthiness
Em Ruby, apenas nil e false são falsy—todo o resto (incluindo 0, '' e []) é truthy. Isso difere de muitas linguagens onde 0 é falsy. Use || para padrões e nil? para verificar nil especificamente.
x = nil
puts x.nil? # true
puts x || "default" # default
# Only nil and false are falsy; 0 and "" are truthy!
puts 0 ? "truthy" : "falsy" # truthy
puts "" ? "truthy" : "falsy" # truthy
puts nil ? "truthy" : "falsy" # falsyConversão de Tipos
to_i/to_s/to_f são conversões lenient (retornam 0 em falha). Integer()/Float() são strict (lançam ArgumentError). Use conversão strict quando precisar de validação, lenient quando quiser degradação graciosa.
puts "42".to_i # 42
puts 42.to_s # "42"
puts "3.14".to_f # 3.14
puts 3.14.to_i # 3
puts "abc".to_i # 0 (no error)
puts Integer("42") # 42 (raises if invalid)Interpolação de Strings
Strings com aspas duplas suportam interpolação #{expr}—qualquer expressão Ruby dentro. Strings com aspas simples são literais (sem interpolação ou escapes, exceto \\ e \'). Use aspas duplas quando precisar de interpolação ou sequências de escape.
name = "Alice"
age = 30
puts "Name: #{name}, Age: #{age}"
puts "5 + 3 = #{5 + 3}"
puts "Upper: #{name.upcase}"
# Single quotes don't interpolate
puts 'No #{name} here' # No #{name} hereStrings
Métodos Comuns de String
Strings em Ruby têm métodos ricos. A maioria retorna novas strings (strings são mutáveis em Ruby). Use variantes com ! (upcase!, gsub!) para modificação in-place—elas modificam o receptor e retornam nil se não houver mudança.
s = "Hello, World"
puts s.length # 12
puts s.upcase # HELLO, WORLD
puts s.downcase # hello, world
puts s.reverse # dlroW ,olleH
puts s.split(", ") # ["Hello", "World"]
puts s.gsub("o", "0") # Hell0, W0rldMutação de Strings (Métodos Bang)
Métodos terminados com ! modificam o objeto in-place e são frequentemente mais eficientes. Eles podem retornar nil se nenhuma mudança foi feita. Use quando quiser evitar criar cópias, mas esteja ciente dos efeitos colaterais.
s = "hello"
s.upcase! # s is now "HELLO"
s.gsub!(/L/, "1") # s is now "HE11O"
puts s # HE11O
# Bang methods modify in-place; use with caution
arr = [3, 1, 2]
arr.sort! # arr is now [1, 2, 3]Heredoc e Multilinha
Heredocs (<<TEXT ... TEXT) criam strings multilinha. <<~ (squiggly heredoc) remove o whitespace comum à esquerda para código limpo. Útil para consultas SQL, templates HTML ou mensagens longas.
text = <<~HEREDOC
Hello,
World!
Indentation is stripped.
HEREDOC
puts text
# <<~ strips leading whitespace (Ruby 2.3+)
# <<HEREDOC preserves indentationFormatação (sprintf)
Use o operador % ou format/sprintf para formatação de strings estilo C. %-10s alinha à esquerda em 10 caracteres, %08.2f preenche com zeros até 8 caracteres com 2 decimais. Útil para saída tabular e formatação de largura fixa.
puts sprintf("%s is %d", "Alice", 30)
puts "%-10s|%5d" % ["Name", 42]
puts format("%.2f", 3.14159) # 3.14
puts "%08.2f" % 3.14 # 00003.14
# %s string, %d integer, %f floatConcatenação de Strings
+ cria uma nova string, << anexa in-place (mais eficiente para construir strings). * repete uma string. join combina elementos de array com um separador. Prefira << ou join em vez de + repetido para desempenho.
s1 = "Hello" + ", " + "World"
s2 = ["a", "b", "c"].join("-") # a-b-c
s3 = "Hello"
s3 << " " << "World" # in-place append
s4 = "x" * 3 # xxx (repetition)
puts s1, s2, s3, s4Estruturas de Dados
Arrays
Arrays são ordenados, indexados a partir de zero e podem conter tipos mistos. << e push adicionam ao final. Use include? para pertinência, sum para totais. Arrays são mutáveis—use freeze para torná-los imutáveis.
nums = [1, 2, 3, 4, 5]
nums.push(6) # [1,2,3,4,5,6]
nums << 7 # same as push
nums[0] = 0
puts nums.first # 0
puts nums.length # 7
puts nums.sum # 28
puts nums.include?(3) # trueHashes
Hashes são dicionários chave-valor. Chaves de símbolo (name:) são idiomáticas e eficientes. Use key? para existência, fetch para acesso seguro (lança em caso de ausência), transform_values para atualizações em massa.
user = { name: "Alice", age: 30 }
user[:email] = "[email protected]"
puts user[:name] # Alice
puts user.key?(:name) # true
user.each { |k, v| puts "#{k}: #{v}" }
puts user.values # ["Alice", 30, "[email protected]"]
puts user.transform_values(&:to_s)Ranges
'..' inclui o final, '...' exclui o final. Ranges são lazy e eficientes em memória para sequências grandes. Úteis para iteração, slicing e geração de sequências. Podem ser usados com qualquer tipo Comparable.
(1..5).each { |n| puts n } # 1 2 3 4 5
puts (1...5).to_a # [1, 2, 3, 4]
puts (1..10).select(&:even?) # [2, 4, 6, 8, 10]
puts ('a'..'e').include?('c') # true
puts (1..5).map { |n| n ** 2 } # [1, 4, 9, 16, 25]Métodos Enumerable
Enumerable é o mixin mais poderoso do Ruby—map, select, reject, reduce, find, group_by, sort_by, etc. Use &:method como abreviação para { |x| x.method }. Eles permitem pipelines expressivos de transformação de dados.
nums = [1, 2, 3, 4, 5]
puts nums.map { |n| n * 2 }.inspect # [2,4,6,8,10]
puts nums.select(&:even?).inspect # [2, 4]
puts nums.reject(&:odd?).inspect # [2, 4]
puts nums.reduce(0) { |sum, n| sum + n } # 15
puts nums.find { |n| n > 3 } # 4
puts nums.group_by(&:even?).inspect # {false=>[1,3,5], true=>[2,4]}Sets
Set (da biblioteca 'set') armazena elementos únicos com lookup O(1). Use para desduplicação e operações de conjunto (união, interseção, diferença). Converta com to_a quando precisar de um array. Requer 'set' require.
require 'set'
a = Set.new([1, 2, 3])
b = Set.new([3, 4, 5])
puts a.union(b).to_a.inspect # [1,2,3,4,5]
puts a.intersection(b).to_a.inspect # [3]
puts a.subtract([1]).to_a.inspect # [2, 3]
puts a.subset?(Set.new([1,2,3,4])) # true
a.add(6)Fluxo de Controle
If / Elsif / Unless
if/elsif/else é o branching padrão. unless é o oposto de if (executa quando a condição é falsa). A forma modifier (statement if condition) é idiomática para guardas de uma linha—melhora a legibilidade para casos simples.
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "C"
end
# Modifier form
puts "Pass" if score >= 60
puts "Fail" unless score >= 60Case (When)
case/when usa === para correspondência, permitindo ranges, classes e regex. Múltiplos valores separados por vírgulas. then permite corpos de uma linha. Sem um alvo, case age como uma cadeia if/elsif mais limpa.
grade = "B"
case grade
when "A" then puts "Excellent"
when "B", "C" then puts "Good"
when "D".."F" then puts "Poor"
else puts "Unknown"
end
# Case without value = multi-condition if
case
when score > 90 then puts "Top"
when score > 60 then puts "Pass"
endWhile / Until / Loop
while executa enquanto verdadeiro, until executa até ser verdadeiro (enquanto falso). loop é infinito—use break para sair. Prefira iteradores (each, map) em vez de while para coleções—são mais idiomáticos e menos propensos a erros.
count = 0
while count < 3
puts count
count += 1
end
# until = opposite of while
n = 3
until n == 0
puts n
n -= 1
end
# Infinite loop with break
loop do
puts "forever"
break if rand > 0.8
endIteradores (Each/Times/Upto)
Os iteradores do Ruby são mais idiomáticos que loops for. times para contagem, upto/downto para ranges, each_with_index para índice+valor. step controla o incremento. Eles são a espinha dorsal da iteração em Ruby.
3.times { |i| puts i } # 0 1 2
1.upto(3) { |n| puts n } # 1 2 3
3.downto(1) { |n| puts n } # 3 2 1
[1,2,3].each_with_index do |n, i|
puts "#{i}: #{n}"
end
(1..3).step(1) { |n| puts n } # 1 2 3Break / Next / Redo
next pula para a próxima iteração (como continue), break sai do loop cedo. break pode retornar um valor de um bloco. redo reinicia a iteração atual sem re-verificar a condição—raramente usado.
[1, 2, 3, 4, 5].each do |n|
next if n.even? # skip even
break if n > 4 # stop at 5
puts n # prints 1, 3
end
# break value returns from the block
result = [1,2,3].each { |n| break n * 10 if n == 2 }
puts result # 20Métodos e Blocos
Definição de Métodos
Métodos usam def/end. Argumentos nomeados (key:) melhoram a legibilidade para muitos parâmetros. *args coleta argumentos posicionais extras em um array, **kwargs coleta argumentos nomeados em um hash. Valores padrão usam =.
def greet(name, greeting: "Hello")
"#{greeting}, #{name}!"
end
puts greet("Alice") # Hello, Alice!
puts greet("Bob", greeting: "Hi") # Hi, Bob!
def add(*nums) # splat (variadic)
nums.sum
end
puts add(1, 2, 3, 4) # 10Blocos e Yield
Blocos são trechos anônimos de código passados para métodos. yield invoca o bloco. Blocos estão por toda parte em Ruby (each, map, etc.). Use yield para tornar seus métodos flexíveis—os chamadores fornecem o comportamento.
def repeat(n)
n.times { yield }
end
repeat(3) { puts "hi" } # prints hi 3 times
def with_result
yield(5)
end
puts with_result { |x| x * 2 } # 10Procs e Lambdas
Procs e lambdas são blocos reutilizáveis armazenados em variáveis. Diferença principal: lambdas verificam a contagem de argumentos (lançam em incompatibilidade) e retornam apenas de si mesmos; procs são lenient e retornam do método que os envolve. Prefira lambda para comportamento estrito.
square = proc { |x| x * x }
puts square.call(5) # 25
puts square.(5) # 25 (shortcut)
double = lambda { |x| x * 2 }
puts double.call(5) # 10
# Lambda checks arity; Proc doesn't
# Lambda returns from itself; Proc returns from enclosing methodObjeto Method (&)
&:method converte um nome de método em um Proc. É a abreviação idiomática para blocos simples de um método: map(&:to_i) em vez de map { |s| s.to_i }. Mais limpo e legível para transformações simples.
nums = ["1", "2", "3"]
ints = nums.map(&:to_i) # [1, 2, 3]
puts ints.inspect
# &:to_i is shorthand for { |s| s.to_i }
names = ["alice", "bob"]
up = names.map(&:upcase) # ["ALICE", "BOB"]
puts up.inspectValores de Retorno
Métodos Ruby retornam implicitamente a última expressão avaliada—sem necessidade de return explícito. Use return para saídas antecipadas. Múltiplos valores são retornados como um array e podem ser desestruturados. Isso torna o código conciso.
def status(ok)
return "error" unless ok
"ok" # implicit return of last expression
end
puts status(true) # ok
puts status(false) # error
# Multiple assignment
def coords
return 1, 2 # returns [1, 2]
end
x, y = coordsClasses e POO
Variáveis de Classe e Instância
@var = variável de instância (por objeto), @@var = variável de classe (compartilhada). attr_accessor gera getter+setter, attr_reader apenas getter, attr_writer apenas setter. self.method define métodos de classe.
class Person
attr_accessor :name, :age
attr_reader :id
@@count = 0 # class variable
def initialize(name, age)
@name = name # instance variable
@age = age
@@count += 1
end
def self.count; @@count; end
end
p = Person.new("Alice", 30)
puts p.name, Person.countHerança e Super
< denota herança (apenas herança única). super chama a versão do pai do método atual. Use super (com parênteses para passar argumentos, sem para passar os mesmos argumentos) para estender o comportamento do pai. Ruby usa herança única + mixins.
class Animal
def initialize(name); @name = name; end
def speak; "..."; end
end
class Dog < Animal
def speak; "#{@name}: Woof!"; end
end
class Puppy < Dog
def speak; "#{super} (small)"; end
end
puts Puppy.new("Rex").speak # Rex: Woof! (small)Módulos (Mixins)
Módulos agrupam métodos reutilizáveis. include adiciona métodos de instância, extend adiciona métodos de classe. Esta é a solução do Ruby para herança múltipla. Enumerable é um mixin famoso—inclua-o e defina each para obter map, select, etc.
module Walkable
def walk; "#{@name} is walking"; end
end
module Swimmable
def swim; "#{@name} is swimming"; end
end
class Duck
include Walkable # instance methods
extend Swimmable # class methods
def initialize(n); @name = n; end
end
puts Duck.new("Donald").walk # Donald is walkingControle de Acesso
public (padrão), private (apenas chamável sem receptor explícito), protected (chamável dentro da hierarquia de classes). Use private para helpers internos, protected para métodos compartilhados entre instâncias da mesma classe.
class BankAccount
def initialize(bal); @balance = bal; end
def deposit(amt); @balance += amt; end
def balance; @balance; end
private
def audit; "auditing..."; end
protected
def compare(other); @balance > other.balance; end
end
a = BankAccount.new(100)
puts a.balance # 100
# a.audit # Error: private methodSelf e Métodos de Classe
self refere-se ao objeto atual. Dentro do corpo da classe, self é a classe—def self.method define métodos de classe. Métodos de classe são chamados na classe (Counter.total), métodos de instância em instâncias. alias_method cria um alias de método.
class Counter
@@total = 0
def initialize; @@total += 1; end
def self.total; @@total; end
def self.reset!; @@total = 0; end
def instance_method; "I'm an instance"; end
alias_method :count, :instance_method
end
Counter.new; Counter.new
puts Counter.total # 2Tratamento de Erros
Begin / Rescue / Ensure
begin/rescue é o try/catch do Ruby. Resgate classes de exceção específicas para tratamento direcionado. => e captura o objeto da exceção. ensure é sempre executado—use para limpeza (fechar arquivos, liberar locks).
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "Caught: #{e.message}"
rescue => e
puts "Other error: #{e.class}"
ensure
puts "Always runs (cleanup)"
end
# ensure runs regardless of success/failureRaise e Exceções Customizadas
raise lança uma exceção (raise sem argumentos relança). Exceções customizadas herdam de StandardError (ou uma classe mais específica). Nomeie-as com o sufixo Error. Resgate por classe para tratar modos específicos de falha.
class InvalidAgeError < StandardError; end
def set_age(age)
raise InvalidAgeError, "Age cannot be negative" if age < 0
raise ArgumentError, "Must be Integer" unless age.is_a?(Integer)
@age = age
end
begin
set_age(-5)
rescue InvalidAgeError => e
puts "Custom: #{e.message}"
endRetry
retry reinicia o bloco begin do início. Use para falhas transitórias (rede, limites de taxa) com um contador para evitar loops infinitos. Sem um limite, retry pode travar seu programa—sempre o proteja.
attempts = 0
begin
attempts += 1
fetch_data # might fail
rescue NetworkError
retry if attempts < 3
puts "Failed after 3 attempts"
end
# retry re-runs the begin block from the topModifier Rescue
rescue como modifier é uma forma concisa de fornecer um valor de fallback. Ele captura StandardError e retorna o lado direito. Use para casos simples—evite para lógica complexa, pois oculta erros. Ótimo para parsing ou operações opcionais.
result = risky_operation rescue "default"
puts result
# Same as:
# result = begin; risky_operation; rescue; "default"; end
# Catches StandardError only
json = JSON.parse(str) rescue nil
puts "Invalid JSON" if json.nil?Throw / Catch (Fluxo de Controle)
throw/catch NÃO é tratamento de exceção—é um mecanismo de fluxo de controle para saída antecipada de aninhamento profundo (diferente de outras linguagens). throw :symbol salta para o catch correspondente. Use para sair de loops aninhados; use begin/rescue para erros reais.
catch(:done) do
[1, 2, 3, 4, 5].each do |n|
throw :done, n if n > 3
puts n
end
end
# Prints 1, 2, 3 and returns 4
# Not for errors—use for early exit from nested loopsE/S de Arquivos
Ler e Escrever Arquivos
File.write/File.read são métodos simples de uma só vez. File.open com um bloco fecha o arquivo automaticamente. File.foreach lê linha por linha sem carregar o arquivo inteiro—eficiente em memória para arquivos grandes. chomp remove a nova linha final.
# Write
File.write("test.txt", "Hello, File!")
# Read
content = File.read("test.txt")
puts content # Hello, File!
# Append
File.open("log.txt", "a") { |f| f.puts "new line" }
# Read line by line
File.foreach("test.txt") { |line| puts line.chomp }Bloco de Arquivo (Auto-Close)
Sempre use File.open com um bloco—garante que o arquivo seja fechado mesmo se ocorrer um erro. A forma de bloco é a maneira idiomática e segura de lidar com arquivos. Sem um bloco, você deve chamar close manualmente.
File.open("data.txt", "w") do |f|
f.puts "Line 1"
f.puts "Line 2"
f.write("No newline")
end # file auto-closed here
File.open("data.txt", "r") do |f|
f.each_line.with_index { |line, i| puts "#{i}: #{line}" }
endExistência e Informações de Arquivo
A classe File fornece consultas ao sistema de arquivos. file?/directory? distinguem tipos. mtime/ctime/atime fornecem timestamps. rename/delete modificam o sistema de arquivos. Sempre verifique a existência antes de operações para evitar erros.
puts File.exist?("test.txt") # true
puts File.file?("test.txt") # true (regular file)
puts File.directory?(".") # true
puts File.size("test.txt") # bytes
puts File.mtime("test.txt") # modification time
File.rename("old.txt", "new.txt")
File.delete("new.txt") if File.exist?("new.txt")Operações de Diretório
Dir gerencia diretórios. mkdir cria, chdir muda (a forma de bloco restaura depois). glob corresponde padrões de arquivo—* corresponde a qualquer, ** corresponde recursivamente. Útil para descoberta de arquivos e processamento em lote.
Dir.mkdir("test_dir") unless Dir.exist?("test_dir")
Dir.chdir("test_dir") do
File.write("a.txt", "a")
puts Dir.pwd # current path
end
Dir.glob("*.txt") { |f| puts f } # list .txt files
Dir.glob("**/*.rb") { |f| puts f } # recursiveCSV e JSON
CSV e JSON estão na biblioteca padrão. CSV.foreach faz streaming de linhas (eficiente em memória). JSON.parse retorna hashes/arrays com chaves de string. to_json serializa qualquer objeto. Eles são essenciais para intercâmbio de dados.
require 'csv'
require 'json'
CSV.write("data.csv", [["a", 1], ["b", 2]])
CSV.foreach("data.csv") { |row| puts row.inspect }
data = { name: "Alice", age: 30 }
File.write("data.json", data.to_json)
parsed = JSON.parse(File.read("data.json"))
puts parsed["name"] # AliceData/Hora e Regex
Time e Date
Time representa um momento (com fuso horário). Date representa uma data de calendário (sem hora). Aritmética em Time usa segundos. strftime formata com %Y (ano), %m (mês), %d (dia), %H:%M (hora). Requer 'time' para parsing ISO8601.
require 'time'
now = Time.now
puts now # 2024-01-15 14:30:00 +0800
puts now.strftime("%Y-%m-%d %H:%M") # 2024-01-15 14:30
tomorrow = now + 86400 # +1 day (seconds)
puts tomorrow.strftime("%A") # weekday name
date = Date.today
puts date.next_day # tomorrowParsing e Aritmética de Datas
Aritmética de datas funciona naturalmente—adicionar inteiros adiciona dias. next_month/prev_month lidam com limites de mês. Date - Date retorna um Rational (dias). upto/downto iteram sobre ranges de datas. Use Date para lógica de calendário, Time para timestamps.
require 'date'
d = Date.parse("2024-01-15")
puts d.year # 2024
puts d + 7 # 2024-01-22 (+7 days)
puts d.next_month # 2024-02-15
diff = (Date.today - d).to_i
puts "#{diff} days since"
puts Date.today.upto(Date.today + 6).map(&:wday)Correspondência de Regex
=~ retorna a posição da correspondência ou nil. $1, $2 contêm grupos capturados após uma correspondência. .match retorna um objeto MatchData para mais detalhes. Use =~ para verificações simples, .match para extrair capturas. Literais de regex usam /pattern/.
s = "Phone: 123-4567"
if s =~ /(\d+)-(\d+)/
puts "Area: #{$1}, Number: #{$2}"
end
m = /\w+@(\w+)/.match("[email protected]")
puts m[1] # example
puts "abc123" =~ /\d/ ? "has digit" : "no digit"Substituição de Regex
gsub substitui todas as correspondências (sub substitui a primeira). Passe um bloco para substituição dinâmica. scan extrai todas as correspondências para um array. Backreferences (\1, \2) em strings de substituição referem-se a grupos capturados. Poderoso para processamento de texto.
s = "Hello, World"
puts s.gsub(/o/, "0") # Hell0, W0rld
puts s.gsub(/\w+/) { |w| w.capitalize }
puts s.scan(/\w+/).inspect # ["Hello", "World"]
puts "2024-01-15".gsub(/(\d+)-(\d+)-(\d+)/, '\3/\2/\1')
# => 15/01/2024Opções de Regex
Opções de regex: i (case-insensitive), m (multiline—ponto corresponde a nova linha), x (extended—permite whitespace/comentários no padrão). %r{} é um delimitador alternativo útil quando o padrão contém barras (como URLs).
puts /hello/i =~ "HELLO" # 0 (case-insensitive)
puts /line/m =~ "a\nb" # 0 (multiline: . matches \n)
puts /x.y/x =~ "x y" # 0 (extended: ignore whitespace)
# Common patterns
email = /[\w.]+@[\w]+\.[a-z]+/
url = %r{https?://[\w./]+}
puts email.match("[email protected]") ? "valid" : "invalid"Metaprogramação e Concorrência
Definição Dinâmica de Métodos
define_method cria métodos em tempo de execução. Use para gerar múltiplos métodos similares ou construir DSLs. Isso é metaprogramação—código que escreve código. Poderoso, mas use com critério; pode tornar o código mais difícil de entender.
class Dynamic
[:foo, :bar, :baz].each do |name|
define_method(name) { puts "Called #{name}" }
end
end
d = Dynamic.new
d.foo # Called foo
d.bar # Called bar
# Useful for generating similar methodsmethod_missing
method_missing intercepta chamadas a métodos indefinidos. Use para construir APIs flexíveis ou proxies. Sempre sobrescreva respond_to_missing? também, para que a reflexão funcione. Use com moderação—pode ocultar bugs e confundir análise estática.
class Proxy
def method_missing(name, *args)
puts "Called: #{name} with #{args.inspect}"
end
def respond_to_missing?(name, include_private = false)
true
end
end
p = Proxy.new
p.anything(1, 2) # Called: anything with [1, 2]Threads
Threads executam código concorrentemente. join espera pela conclusão. MRI (Ruby padrão) tem um GIL, então threads CPU-bound não executam em paralelo real—threads I/O-bound sim. Para paralelismo real, use Ractor (Ruby 3.0+) ou múltiplos processos.
threads = [1, 2, 3].map do |n|
Thread.new { puts "Thread #{n}: #{n * n}" }
end
threads.each(&:join) # wait for all
# MRI has GIL—threads don't run truly parallel for CPU work
# Use for I/O concurrency (network, file)Fibers (Cooperativo)
Fibers são concorrência leve e cooperativa—elas pausam e retomam manualmente via yield/resume. Diferente de threads, não executam em paralelo. Use para generators, lazy evaluation ou computações pausáveis. Overhead menor que threads.
fiber = Fiber.new do
Fiber.yield 1
Fiber.yield 2
3
end
puts fiber.resume # 1
puts fiber.resume # 2
puts fiber.resume # 3
# Fibers yield control cooperatively, not preemptivelySend e Eval
send chama qualquer método pelo nome (incluindo privados). public_send respeita a visibilidade. eval executa uma string como código Ruby—extremamente poderoso, mas perigoso com entrada não confiável (injeção de código). Use send para dispatch dinâmico, evite eval em produção.
class Obj
def secret; "hidden"; end
end
o = Obj.new
puts o.send(:secret) # hidden (calls private too)
puts o.public_send(:secret) # hidden (respects visibility)
x = 5
result = eval("x * 2")
puts result # 10
# eval runs a string as code—avoid with untrusted input!Blocos, Procs e Lambdas
Noções Básicas de Blocos
Blocos são a closure mais comum do Ruby—código anônimo passado para métodos via { } ou do...end. yield invoca o bloco. |n| declara parâmetros do bloco. block_given? verifica se um bloco foi passado. Blocos não são objetos (não podem ser armazenados em variáveis)—use Proc/Lambda para isso. Todo método pode aceitar um bloco implícito, tornando as DSLs naturais (Rails usa isso intensamente).
# Block: anonymous chunk of code passed to a method
[1, 2, 3].each { |n| puts n } # do-end for multi-line
[1, 2, 3].each do |n|
puts n * 2
end
# yield: call the block from a method
def greet
puts "before"
yield # invokes the block
puts "after"
end
greet { puts "in block" }
# before / in block / after
# yield with arguments
def compute
result = yield(10, 20)
puts "got: #{result}"
end
compute { |a, b| a + b } # got: 30
# block_given? checks if a block was passed
def maybe_yield
return "no block" unless block_given?
yield
endProcs vs Lambdas
Procs e Lambdas são ambos objetos callable (blocos transformados em objetos). Procs são lenient: argumentos extras tornam-se nil, argumentos ausentes são nil, e 'return' sai do método que envolve. Lambdas são strict: verificam a contagem de argumentos e 'return' sai apenas da lambda. Use lambdas quando quiser comportamento semelhante a método; Procs quando quiser comportamento semelhante a bloco. -> (stabby lambda) é a sintaxe concisa moderna.
# Proc: a block stored as an object
p = Proc.new { |x| puts x * 2 }
p.call(5) # 10
p.(5) # 10 (shorthand)
p[5] # 10 (another shorthand)
# Lambda: stricter Proc
l = lambda { |x| puts x * 2 }
l = ->(x) { puts x * 2 } # stabby lambda syntax
l.call(5)
# Key differences:
# 1. Argument checking
p = Proc.new { |a, b| puts a }
p.call(1) # 1 (b is nil, no error)
l = ->(a, b) { puts a }
l.call(1) # ArgumentError (wrong number of args)
# 2. return behavior
def proc_test
p = Proc.new { return 1 }
p.call
return 2 # never reached—Proc return exits method
end
proc_test # 1
def lambda_test
l = lambda { return 1 }
l.call # returns from lambda, not method
return 2
end
lambda_test # 2Objetos Method
method(:name) recupera um método como um objeto Method (callable como um Proc). &:symbol converte um símbolo em um proc que envia esse método—idioma extremamente comum: array.map(&:to_s). O prefixo & converte um Proc em um bloco (ou vice-versa). Isso permite passar métodos como argumentos elegantemente. Objetos Method retêm seu receptor, então são bound ao objeto.
class Calculator
def add(a, b) a + b end
end
calc = Calculator.new
# method(:name) gets a Method object
m = calc.method(:add)
m.call(2, 3) # 5
m.(2, 3) # 5
m[2, 3] # 5
# Convert method to Proc
p = m.to_proc
p.call(4, 5) # 9
# &:method shorthand (common in map, each)
["a", "b", "c"].map(&:upcase) # ["A", "B", "C"]
# Equivalent to: .map { |s| s.upcase }
# Symbol#to_proc
:upcase.to_proc.call("hello") # "HELLOW"
# Useful for passing methods as blocks
[1, 2, 3].each(&method(:puts)) # prints 1, 2, 3Closures e Binding
Closures (blocos, procs, lambdas) capturam variáveis por referência—elas veem atualizações das variáveis capturadas. Isso permite closures com estado (contadores, acumuladores). Proc#binding dá acesso ao ambiente da closure (para metaprogramação avançada). O método Kernel#binding captura o contexto de execução atual para eval. Closures são o motivo pelo qual os blocos Ruby são tão poderosos para callbacks e iteradores.
# Closures capture surrounding variables
counter = 0
increment = lambda { counter += 1 }
increment.call # 1
increment.call # 2
puts counter # 2 (closure modified it)
# Multiple closures share the same variables
x = 10
add = lambda { |n| x += n }
get = lambda { x }
add.call(5)
get.call # 15
# Proc#binding: access the closure's environment
def make_counter
count = 0
lambda { count += 1 }
end
c = make_counter
c.call # 1
# c.binding.eval("count") # 1 (peek at captured var)
# Binding for eval in a specific context
b = binding
x = 42
eval("x", b) # 42Iteradores Customizados com Blocos
Incluir Enumerable e definir #each dá a você map, select, reduce e mais de 50 métodos de graça—este é o protocolo de iterador do Ruby. each_with_object é mais limpo que inject para construir acumuladores. tap insere efeitos colaterais em cadeias de métodos (ótimo para depuração). Métodos customizados com yield permitem criar suas próprias DSLs (with_timing, with_database, etc.). Blocos tornam os padrões de iteração e callback do Ruby elegantes.
# Define your own iterator using yield
class LinkedList
include Enumerable # gets map, select, etc. for free
def each
node = @head
while node
yield node.value
node = node.next
end
end
end
# each_with_object (memo pattern)
result = [1, 2, 3].each_with_object({}) do |n, hash|
hash[n] = n * n
end
# {1=>1, 2=>4, 3=>9}
# tap (for debugging chains)
[1, 2, 3].map { |n| n * 2 }
.tap { |arr| puts "after map: #{arr}" }
.select { |n| n > 2 }
# Custom method with block
def with_timing
start = Time.now
yield
puts "took #{Time.now - start}s"
end
with_timing { sleep(1) }Enumerable e Iteradores
Métodos Centrais do Enumerable
Enumerable é o mixin mais poderoso do Ruby—inclua-o e defina #each para obter mais de 50 métodos. map transforma, select/reject filtram, reduce/inject agregam, find retorna a primeira correspondência, group_by/partition agrupam. Atalhos de símbolo (reduce(:+)) são idiomáticos. Esses métodos funcionam em Arrays, Hashes, Ranges, Files—qualquer coisa com #each. Dominar Enumerable é a chave para Ruby idiomático.
nums = [1, 2, 3, 4, 5, 6]
# map/collect: transform
nums.map { |n| n * 2 } # [2, 4, 6, 8, 10, 12]
# select/filter: keep matching
nums.select { |n| n.even? } # [2, 4, 6]
nums.reject { |n| n.even? } # [1, 3, 5]
# reduce/inject: accumulate
nums.reduce(0) { |sum, n| sum + n } # 21
nums.reduce(:+) # 21 (symbol shorthand)
nums.reduce(:*) # 720
# find/detect: first match
nums.find { |n| n > 3 } # 4
# group_by: cluster
nums.group_by { |n| n.even? } # {false=>[1,3,5], true=>[2,4,6]}
# partition: split into two
nums.partition { |n| n.even? } # [[2,4,6], [1,3,5]]
# chunk: consecutive grouping
[1,1,2,2,3].chunk { |n| n }.to_aLazy Evaluation
lazy converte um enumerable em um lazy—valores são computados apenas quando necessário. Isso permite sequências infinitas e evita computar toda a coleção para cadeias que precisam apenas de alguns resultados. Sem lazy, map/select criam arrays intermediários. Com lazy, o pipeline puxa valores um de cada vez. Use lazy para datasets grandes/infinitos ou transformações caras. A desvantagem: lazy tem overhead por elemento, então é mais lento para coleções pequenas.
# Lazy: evaluate on demand (infinite sequences possible)
require 'prime'
# Without lazy: infinite loop!
# primes = (1..Float::INFINITY).select(&:prime?).first(5)
# With lazy: works!
primes = (1..Float::INFINITY).lazy.select(&:prime?).first(5)
# [2, 3, 5, 7, 11]
# Chain without intermediate arrays
(1..1_000_000).lazy
.map { |n| n * 2 }
.select { |n| n > 1_000_000 }
.first(10) # only computes what's needed
# Build a lazy enumerator
enum = Enumerator::Lazy.new(1..Float::INFINITY) do |yielder, n|
yielder << n if n.prime?
end
enum.first(5) # [2, 3, 5, 7, 11]Enumerator e Iteração Externa
Enumerator envolve uma iteração como um objeto—você pode chamar .next manualmente (iteração externa) em vez de usar um bloco (iteração interna). Isso permite pausar/retomar iteração, espiar e criar sequências infinitas. Enumerator.new com um bloco permite construir iteradores customizados (generators). A maioria dos métodos Enumerable retorna Enumerators quando chamados sem um bloco: [1,2,3].map retorna um Enumerator que você pode encadear.
# Enumerator: an iterator object you can control
enum = [10, 20, 30].each
enum.next # 10
enum.next # 20
enum.next # 30
enum.next # StopIteration
# External iteration with loop (handles StopIteration)
enum = [1, 2, 3].each
loop do
puts enum.next
end
# Create custom Enumerator
fib = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a
a, b = b, a + b
end
end
fib.first(10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# Convert to array
fib.take(10).to_aOrdenação e Comparação
sort recebe um bloco comparando dois elementos (retorna -1, 0 ou 1). sort_by é mais eficiente—computa a chave de ordenação uma vez por elemento (transformada Schwartzian) em vez de a cada comparação. Use sort_by para chaves complexas. O operador <=> (spaceship) retorna -1/0/1 e é a base da ordenação do Ruby. min/max/min_by/max_by encontram extremos. Inclua Comparable e defina <=> para ordenação natural dos seus objetos.
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# sort: returns new array
arr.sort # [1, 1, 2, 3, 4, 5, 6, 9]
arr.sort { |a, b| b <=> a } # descending (9, 6, 5, ...)
# sort_by: more efficient (Schwartzian transform)
arr.sort_by { |n| -n } # descending
words = ["banana", "apple", "cherry"]
words.sort_by { |w| w.length } # ["apple", "banana", "cherry"]
# min/max
arr.min # 1
arr.max # 9
arr.minmax # [1, 9]
arr.min_by { |n| n.abs } # 1
# min/max with block
people.max_by { |p| p[:age] }
# <=> (spaceship): -1, 0, 1
1 <=> 2 # -1
2 <=> 2 # 0
3 <=> 2 # 1Iteração e Transformação de Hash
Hashes também são Enumerable. each/each_pair iteram pares chave-valor. transform_keys/transform_values (Ruby 2.5+) criam novos hashes com chaves/valores modificados. select/reject filtram em um novo hash. merges combina hashes (o bloco resolve conflitos). group_by constrói um hash a partir de um array. A iteração de Hash é a fundação do processamento de dados em Ruby—domine-a para manipulação de dados limpa e expressiva.
hash = { a: 1, b: 2, c: 3 }
# Iterate
hash.each { |k, v| puts "#{k}=#{v}" }
hash.each_key { |k| puts k }
hash.each_value { |v| puts v }
hash.each_pair { |k, v| puts "#{k}: #{v}" }
# Transform keys/values
hash.transform_keys { |k| k.to_s } # {"a"=>1, "b"=>2, "c"=>3}
hash.transform_values { |v| v * 10 } # {a:10, b:20, c:30}
# Filter
hash.select { |k, v| v > 1 } # {b:2, c:3}
hash.reject { |k, v| v > 1 } # {a:1}
# Merge
{ a: 1 }.merge({ b: 2 }) # {a:1, b:2}
{ a: 1 }.merge({ a: 2 }) { |k, old, new| old + new } # {a:3}
# Invert (values become keys)
{ a: 1, b: 2 }.invert # {1=>:a, 2=>:b}
# Group arrays into hash
%w[apple apricot banana].group_by { |w| w[0] }
# {"a"=>["apple", "apricot"], "b"=>["banana"]}Gems e Bundler
Noções Básicas de Gem
Gems são pacotes Ruby (bibliotecas). gem install gerencia gems do sistema. Para projetos, use Bundler com um Gemfile para fixar versões e gerenciar dependências. Especificadores de versão: '~> 1.4' (pessimistic, permite patches), '>= 5.0' (optimistic). Grupos (:development, :test, :production) permitem carregar apenas as gems necessárias por ambiente. require: false significa que o Bundler não fará auto-require (você require manualmente quando necessário).
# Install a gem
$ gem install rails
$ gem install rails -v 7.0.0
$ gem install rails --pre # pre-release
# List installed gems
$ gem list
$ gem list rails
# Uninstall
$ gem uninstall rails
# Gemfile: project dependencies
# Gemfile
source 'https://rubygems.org'
gem 'rails', '7.0.0'
gem 'pg', '~> 1.4' # pessimistic: >= 1.4, < 2.0
gem 'puma', '>= 5.0' # optimistic: >= 5.0
gem 'rspec', group: :test # group-specific
gem 'pry', require: false # don't auto-require
# Groups
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
end
group :production do
gem 'newrelic_rpm'
endComandos do Bundler
Bundler garante que seu projeto use as versões exatas de gem especificadas. bundle install lê o Gemfile e escreve Gemfile.lock (versões exatas para reprodutibilidade—faça commit deste!). bundle exec executa comandos com as versões corretas de gem (evita conflitos). bundle update muda versões (cuidado—pode quebrar coisas). Sempre use bundle exec para rake/rspec/rails para garantir que as gems certas sejam carregadas. Gemfile.lock torna as implantações reprodutíveis.
# Install all gems from Gemfile
$ bundle install
$ bundle install --without production # skip group
# Update gems
$ bundle update # update all
$ bundle update rails # update specific gem
$ bundle outdated # show outdated gems
# Run commands in bundle context
$ bundle exec rails server
$ bundle exec rspec
$ bundle exec rake db:migrate
# Check for dependency issues
$ bundle check
$ bundle doctor
# Lock file: Gemfile.lock
# Records exact versions installed (for reproducibility)
# Commit Gemfile.lock to version control!
# Add/remove gems
$ bundle add rspec
$ bundle remove rspec
# Clean old gems
$ bundle cleanGemspec (Criando uma Gem)
O arquivo .gemspec define os metadados e dependências de uma gem. spec.files lista os arquivos incluídos; require_paths diz ao Ruby onde encontrá-los. add_dependency para dependências de runtime, add_development_dependency para dependências de teste/build. required_ruby_version impõe a versão do Ruby. gem build cria o pacote .gem; gem install instala-o localmente. Publique em rubygems.org com gem push. Estrutura: lib/ para código, spec/ para testes.
# my_gem.gemspec
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = "0.1.0"
spec.summary = "A useful Ruby gem"
spec.description = "Longer description..."
spec.authors = ["Alice"]
spec.email = ["[email protected]"]
spec.homepage = "https://github.com/alice/my_gem"
spec.license = "MIT"
spec.files = Dir["lib/**/*.rb"] + ["README.md"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 3.0"
spec.add_dependency "httparty", "~> 0.21"
spec.add_development_dependency "rspec", "~> 3.12"
end
# Directory structure:
# my_gem/
# lib/my_gem.rb (main file)
# lib/my_gem/version.rb
# my_gem.gemspec
# Gemfile
# spec/ (tests)
# Build and install
$ gem build my_gem.gemspec # creates my_gem-0.1.0.gem
$ gem install ./my_gem-0.1.0.gemTarefas Rake
Rake é o make do Ruby—um task runner. Defina tarefas com task :name do ... end. Namespaces agrupam tarefas relacionadas. Tarefas de arquivo têm dependências (rebuild se a fonte mudar). sh executa comandos shell. Rake é usado para testes, builds, implantações e tarefas de banco de dados (Rails o usa intensamente). Execute com rake task_name. A tarefa default é executada quando você digita apenas rake. Passe argumentos com rake greet[Alice].
# Rakefile
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
# Custom task
task :greet, [:name] do |t, args|
puts "Hello, #{args.name}!"
end
# $ rake greet[Alice]
# Namespace
namespace :db do
task :migrate do
puts "migrating..."
end
task :seed do
puts "seeding..."
end
end
# $ rake db:migrate
# File tasks (build dependencies)
file 'output.txt' => 'input.txt' do |t|
sh "cp #{t.source} #{t.name}"
end
# Multi-line task
task :deploy do
sh 'git push heroku main'
sh 'heroku run rails db:migrate'
endRbenv e RVM (Versões do Ruby)
rbenv e RVM gerenciam múltiplas versões do Ruby em uma máquina. rbenv é leve (shims); RVM é mais pesado (sobrescreve comandos shell). O arquivo .ruby-version (commitado) garante que todos usem a mesma versão do Ruby. Gemsets (RVM) ou bundle config path isolam gems do projeto. Para produção, use bundle config set path para instalar gems localmente no projeto, evitando poluição de gems do sistema. Sempre fixe a versão do Ruby em .ruby-version.
# rbenv: lightweight Ruby version manager
$ rbenv install 3.2.0 # install a version
$ rbenv global 3.2.0 # set global version
$ rbenv local 3.1.0 # set per-project (.ruby-version)
$ rbenv versions # list installed
# .ruby-version file (committed to project)
# 3.2.0
# RVM: alternative version manager
$ rvm install 3.2.0
$ rvm use 3.2.0
$ rvm gemset create myapp # isolated gem sets
$ rvm use 3.2.0@myapp
# Bundler config
$ bundle config set path 'vendor/bundle' # install locally
$ bundle config set without 'development test' # for production
# Check versions
$ ruby -v
$ gem -v
$ bundle -vNoções Básicas de Rails
Estrutura MVC
Rails é uma estrutura MVC: Models (ActiveRecord) lidam com dados, Controllers (ActionController) lidam com requisições HTTP, Views (ActionView) renderizam respostas. resources gera 7 rotas RESTful automaticamente. Rotas mapeiam URLs para ações do controller. Convenção sobre configuração: nomeie seu model User, controller UsersController, e Rails conecta tudo. Essa estrutura é a espinha dorsal de todo app Rails.
# Rails follows Model-View-Controller
# app/
# models/ (ActiveRecord: data + business logic)
# controllers/ (ActionController: handle requests)
# views/ (ActionView: render responses)
# helpers/ (view helpers)
# config/routes.rb (URL routing)
# Route → Controller → Model → View
# GET /users → UsersController#index → User.all → index.html.erb
# config/routes.rb
Rails.application.routes.draw do
resources :users # generates 7 RESTful routes
# GET /users → index
# GET /users/new → new
# POST /users → create
# GET /users/:id → show
# GET /users/:id/edit → edit
# PATCH /users/:id → update
# DELETE /users/:id → destroy
root 'pages#home' # root path
get 'about', to: 'pages#about' # custom route
endModels do ActiveRecord
ActiveRecord é o ORM do Rails—models mapeiam para tabelas do banco de dados. validates impõe integridade de dados. Associações (has_many, belongs_to, has_one) definem relacionamentos. Callbacks (before_save, after_create) se conectam ao lifecycle. Scopes são fragmentos de consulta reutilizáveis. ActiveRecord usa convenção: model User → tabela users, colunas created_at/updated_at. É o coração do Rails—domine-o para desenvolvimento Rails eficaz.
# app/models/user.rb
class User < ApplicationRecord
# Validations
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, uniqueness: true,
format: { with: URI::MailTo::EMAIL_REGEXP }
# Associations
has_many :posts, dependent: :destroy
has_one :profile
belongs_to :company
has_and_belongs_to_many :tags
# Callbacks
before_save :normalize_email
after_create :send_welcome_email
# Scopes (reusable queries)
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
private
def normalize_email
self.email = email.downcase
end
end
# Usage
User.create(name: "Alice", email: "[email protected]")
User.active.recent
User.find(1)
User.where("age > ?", 18)Controllers e Strong Params
Controllers lidam com requisições HTTP e coordenam models/views. before_action executa filtros (autenticação, carregamento de recursos). Strong parameters (permit) previnem vulnerabilidades de mass-assignment—apenas campos whitelist podem ser definidos. redirect_to envia o usuário para outro lugar; render mostra uma view. As variáveis @instance estão disponíveis nas views. Ações RESTful (index, show, new, create, edit, update, destroy) são convencionais.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: %i[show edit update destroy]
def index
@users = User.all
end
def show; end # @user set by before_action
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'User created'
else
render :new, status: :unprocessable_entity
end
end
private
def set_user
@user = User.find(params[:id])
end
# Strong parameters: whitelist allowed fields
def user_params
params.require(:user).permit(:name, :email, :age)
end
endViews e Helpers
ERB (Embedded Ruby) é o templating padrão do Rails: <%= %> produz saída, <% %> executa. link_to/button_to geram links/forms HTML. form_with constrói forms vinculados a models. Partials (_form.html.erb) são fragmentos de view reutilizáveis renderizados com render. Path helpers (new_user_path, user_path(user)) geram URLs a partir de rotas. Helpers mantêm views limpas. Para lógica complexa, use view helpers (app/helpers/) ou decorators.
<!-- app/views/users/index.html.erb -->
<h1>Users</h1>
<%= link_to "New User", new_user_path %>
<ul>
<% @users.each do |user| %>
<li>
<%= link_to user.name, user_path(user) %>
(<%= user.email %>)
<%= link_to "Edit", edit_user_path(user) %>
<%= button_to "Delete", user, method: :delete %>
</li>
<% end %>
</ul>
<!-- Partials: reusable view fragments -->
<!-- app/views/users/_form.html.erb -->
<%= form_with model: user do |form| %>
<% if user.errors.any? %>
<div class="errors">
<% user.errors.full_messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
</div>
<% end %>
<%= form.text_field :name %>
<%= form.email_field :email %>
<%= form.submit %>
<% end %>
<!-- Render partial -->
<%= render "form", user: @user %>Migrations e Banco de Dados
Migrations evoluem o schema do banco de dados ao longo do tempo, com controle de versão. create_table define tabelas; add_column/remove_column as modificam. t.references cria chaves estrangeiras. t.timestamps adiciona created_at/updated_at. db:migrate aplica migrations pendentes; db:rollback desfaz a última. O arquivo schema.rb é a fonte autoritativa do schema atual (auto-gerado). Nunca edite schema.rb diretamente—use migrations. Isso torna as mudanças de banco reprodutíveis entre ambientes.
# Generate a migration
$ rails generate migration CreateUsers name:string email:string
# db/migrate/20240101_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: { unique: true }
t.integer :age, default: 0
t.text :bio
t.references :company, foreign_key: true
t.timestamps # created_at, updated_at
end
end
end
# Run migrations
$ rails db:migrate
$ rails db:rollback # undo last migration
$ rails db:seed # load seed data
$ rails db:reset # drop + create + migrate + seed
# Schema file (don't edit manually)
# db/schema.rb reflects current database structure
# Add a column later
$ rails generate migration AddAgeToUsers age:integer
# creates: add_column :users, :age, :integerTestes com RSpec
Sintaxe Básica (describe, it, expect)
RSpec é a estrutura de testes dominante do Ruby. describe agrupa testes relacionados; it define um único teste. expect(...).to / not_to fazem asserções. let define variáveis lazy memoized (computadas uma vez quando acessadas pela primeira vez). context é um alias para describe, usado para ramificações (when...). A gem shoulda-matchers fornece sintaxe de uma linha para validações/associações comuns do Rails. Testes vão em spec/ espelhando a estrutura de app/.
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
# Setup with let (lazy, memoized)
let(:user) { User.new(name: "Alice", email: "[email protected]") }
describe '#name' do
it 'returns the name' do
expect(user.name).to eq("Alice")
end
# Multiple expectations with context
context 'when name is blank' do
let(:user) { User.new(name: "") }
it 'is invalid' do
expect(user).not_to be_valid
end
end
end
# One-liner syntax
it { should validate_presence_of(:email) }
it { should have_many(:posts) }
endMocks e Stubs
Stubs (allow) substituem valores de retorno de métodos; mocks (expect) verificam que um método foi chamado. Doubles são objetos falsos para testes (mais rápidos que objetos reais). Use stubs para isolar o código em teste de dependências externas (APIs, bancos de dados). Use mocks para verificar interações. Excesso de mocking torna os testes frágeis—prefira objetos reais quando rápido o suficiente. FactoryBot cria dados de teste; use create (salva no DB) ou build (apenas em memória).
RSpec.describe PaymentService do
let(:user) { create(:user) }
it 'charges the card' do
# Stub: replace a method's return value
allow(user).to receive(:premium?).and_return(true)
# Mock: expect a method to be called
expect(Stripe::Charge).to receive(:create).with(
amount: 1000, currency: 'usd'
).and_return(double(id: 'ch_123'))
service = PaymentService.new(user)
result = service.charge(10)
expect(result).to eq('ch_123')
end
it 'handles failure' do
# Stub to raise an error
allow(Stripe::Charge).to receive(:create)
.and_raise(Stripe::StripeError.new("card declined"))
service = PaymentService.new(user)
expect { service.charge(10) }.to raise_error(PaymentError)
end
end
# Double: test stand-in for an object
fake_card = double('Card', last4: '4242', brand: 'Visa')FactoryBot e Fixtures
FactoryBot cria objetos de teste com padrões sensatos. Traits criam variações (:admin, :inactive). Sequences geram valores únicos (emails). create persiste no banco de dados; build não. create_list cria múltiplos. Sobrescreva qualquer atributo passando-o. Factories são mais flexíveis que fixtures (YAML), mas mais lentas (escritas no DB). Use build_stubbed para testes rápidos que não acessam o DB. Mantenha factories simples—factories complexas indicam models complexos.
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "Alice" }
email { "[email protected]" }
age { 30 }
active { true }
# Traits: variations
trait :admin do
role { "admin" }
end
trait :inactive do
active { false }
end
# Associations
company { association(:company) }
# Sequences for unique values
sequence(:email) { |n| "user#{n}@example.com" }
end
end
# Usage in specs
let(:user) { create(:user) } # saved to DB
let(:admin) { create(:user, :admin) } # with trait
let(:user) { build(:user) } # not saved
let(:users) { create_list(:user, 5) } # 5 users
# Override attributes
let(:alice) { create(:user, name: "Alice") }Hooks Before/After
Hooks before executam código de setup: before(:each) (mais comum) antes de cada teste, before(:all) uma vez por grupo. Hooks after fazem limpeza. DatabaseCleaner gerencia o estado do banco de testes (transaction para velocidade, truncation para minúcia). Prefira let em vez de before(:each)—let é lazy (apenas computa quando usado) e memoized, enquanto before é executado mesmo se o teste não precisar. Use before para efeitos colaterais que devem acontecer (logging, congelamento de tempo).
RSpec.describe User do
before(:all) do
# Runs once before all tests in this group
@shared_data = load_expensive_data
end
before(:each) do # or just 'before'
# Runs before each test
@user = User.create(name: "test")
end
after(:each) do
# Runs after each test (cleanup)
User.delete_all
end
after(:all) do
# Runs once after all tests
@shared_data = nil
end
# Database cleaner for transactional tests
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
end
# Use before for setup, after for cleanup
# Prefer let over before(:each) for lazy evaluationTestes de Integração e Sistema
Request specs testam a pilha completa (routing → controller → model → view) via HTTP. System specs (Capybara) dirigem um navegador real—preenchendo forms, clicando, verificando conteúdo da página. Testes de unidade (model specs) são rápidos e isolados; testes de integração/sistema são mais lentos, mas capturam bugs de wiring. Use a pirâmide de testes: muitos testes de unidade rápidos, menos testes de integração, testes de sistema mínimos. have_http_status verifica códigos de resposta; visit/fill_in/click_button dirigem o navegador.
# spec/requests/users_spec.rb (integration)
require 'rails_helper'
RSpec.describe 'Users API', type: :request do
describe 'GET /users' do
before { create_list(:user, 3) }
it 'returns all users' do
get '/users'
expect(response).to have_http_status(200)
expect(JSON.parse(response.body).size).to eq(3)
end
end
describe 'POST /users' do
it 'creates a user' do
post '/users', params: { user: { name: 'Bob' } }
expect(response).to have_http_status(:created)
end
end
end
# spec/system/login_spec.rb (browser tests)
require 'rails_helper'
RSpec.describe 'Login', type: :system do
it 'logs in a user' do
user = create(:user, password: 'secret')
visit login_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'secret'
click_button 'Log in'
expect(page).to have_content('Welcome')
end
endOperações de Arquivo e Diretório
Lendo Arquivos
File.read carrega o arquivo inteiro na memória—bom para arquivos pequenos. File.foreach lê linha por linha—essencial para arquivos grandes (não estoura a memória). File.open com um bloco fecha o arquivo automaticamente (RAII). readlines retorna um array de linhas (com novas linhas—use chomp). binread para arquivos binários. Sempre verifique File.exist? antes de ler se o arquivo pode não existir, ou resgate Errno::ENOENT.
# Read entire file
content = File.read("data.txt")
# Read line by line (memory efficient)
File.foreach("large.txt") do |line|
puts line.chomp # chomp removes trailing newline
end
# Read all lines into array
lines = File.readlines("data.txt")
# With a block (auto-closes)
File.open("data.txt") do |f|
f.each_line { |line| puts line }
end
# Read with options
File.read("data.txt", encoding: "utf-8")
File.binread("image.png") # binary mode
# Check existence
File.exist?("data.txt")
File.file?("data.txt") # is it a regular file?
File.directory?("path") # is it a directory?Escrevendo Arquivos
File.write é a forma mais simples de escrever um arquivo (sobrescreve por padrão). Use mode: 'a' para anexar. File.open com um bloco garante que o arquivo seja fechado mesmo se ocorrer uma exceção. puts adiciona uma nova linha; write não. << é um alias para write (comum em Ruby). Para logs, abra uma vez e escreva múltiplas vezes (com buffer para desempenho). Sempre feche arquivos ou use a forma de bloco para evitar vazamentos de recursos.
# Write (overwrites)
File.write("output.txt", "Hello, World!")
# Append
File.write("output.txt", "More text\n", mode: "a")
# With a block (buffered, auto-closes)
File.open("log.txt", "w") do |f|
f.puts "Line 1"
f.puts "Line 2"
f.write("No newline")
f << "appended" # << is an alias for write
end
# Binary write
File.binwrite("data.bin", binary_data)
# Modes: r (read), w (write/truncate), a (append),
# r+ (read/write), w+ (read/write/truncate),
# b (binary, Windows)
# Flush buffer
f = File.open("log.txt", "w")
f.write("data")
f.flush # write without closing
f.closeOperações de Diretório
Dir.glob com padrões encontra arquivos (** para recursivo). FileUtils fornece operações robustas de arquivo: mkdir_p cria diretórios aninhados, cp_r copia recursivamente, rm_rf remove fortemente (cuidado!). Dir.chdir muda o diretório de trabalho (use a forma de bloco para mudar temporariamente). Dir.entries inclui . e ..; glob não. Prefira FileUtils em vez de operações manuais de File para segurança multiplataforma.
require 'fileutils'
# List directory contents
Dir.entries(".") # [".", "..", "file.txt", ...]
Dir.glob("*.rb") # ["script.rb", "test.rb"]
Dir.glob("**/*.rb") # recursive
Dir.glob("src/**/*.{rb,erb}") # multiple extensions
# Create directories
Dir.mkdir("new_dir")
Dir.mkdir("nested/path") rescue Errno::ENOENT # fails if parent missing
FileUtils.mkdir_p("nested/path/deep") # creates all parents
# Remove
Dir.rmdir("empty_dir") # only works if empty
FileUtils.rm_rf("dir") # recursive force (dangerous!)
# Copy/Move
FileUtils.cp("a.txt", "b.txt")
FileUtils.mv("old.txt", "new.txt")
FileUtils.cp_r("src_dir", "dest_dir") # recursive
# Change directory
Dir.chdir("/tmp") { puts Dir.pwd } # temporarily
Dir.chdir("/tmp") # permanently for process
# Check if directory
File.directory?("path")
File.exist?("path")Pathname e Tempfile
Pathname é um wrapper orientado a objetos para caminhos de arquivo—mais limpo que manipulação de strings. Fornece dirname, basename, extname, join e verificações de arquivo como métodos. Tempfile cria arquivos temporários que são excluídos automaticamente (use a forma de bloco). Dir.mktmpdir cria diretórios temporários. Use-os para manipulação limpa e segura de caminhos e gerenciamento de arquivos temporários. Pathname compõe caminhos com segurança entre plataformas (lida com / vs \).
require 'pathname'
require 'tempfile'
# Pathname: object-oriented path manipulation
path = Pathname.new("/home/user/docs/file.txt")
path.dirname # #<Pathname:/home/user/docs>
path.basename # #<Pathname:file.txt>
path.extname # ".txt"
path.parent # #<Pathname:/home/user/docs>
path.join("sub", "file.rb") # /home/user/docs/sub/file.rb
path.exist?
path.directory?
path.file?
path.readable?
# Tempfile: auto-deleted file
Tempfile.create("prefix") do |f|
f.write("temporary data")
f.rewind
puts f.read
end # file deleted after block
# Tempfile without block (must close/unlink manually)
tf = Tempfile.new("prefix")
tf.write("data")
tf.close
tf.unlink # delete
# Dir.mktmpdir for temp directories
Dir.mktmpdir do |dir|
# work in dir
end # dir deleted afterCSV e JSON
CSV e JSON estão embutidos na biblioteca padrão do Ruby. CSV.foreach lê linha por linha (eficiente em memória); CSV.read carrega tudo. headers: true trata a primeira linha como nomes de colunas. JSON.parse converte JSON para hashes/arrays Ruby; to_json serializa objetos Ruby. symbolize_names dá chaves de símbolo (mais limpo). Para YAML, use require 'yaml' e YAML.load_file. Eles são essenciais para intercâmbio de dados em scripts Ruby e apps web.
require 'csv'
require 'json'
# CSV reading
CSV.foreach("data.csv", headers: true) do |row|
puts row['name'] # access by header
puts row[0] # access by index
end
# CSV reading all at once
rows = CSV.read("data.csv", headers: true)
rows.first['name'] # first row's name
# CSV writing
CSV.open("output.csv", "w") do |csv|
csv << ["name", "age"] # header
csv << ["Alice", 30]
csv << ["Bob", 25]
end
# JSON
data = { name: "Alice", age: 30 }
json = data.to_json # '{"name":"Alice","age":30}'
parsed = JSON.parse(json) # {"name"=>"Alice", "age"=>30}
# JSON with symbols
JSON.parse(json, symbolize_names: true) # {name: "Alice", age: 30}
# Read/write JSON files
File.write("data.json", data.to_json)
loaded = JSON.parse(File.read("data.json"))Codificação e Internos de String
Codificações de String
Strings Ruby carregam sua codificação (geralmente UTF-8). force_encoding reinterpreta bytes como uma codificação diferente (sem conversão—use quando você sabe que os bytes já estão nessa codificação). encode realmente converte entre codificações. valid_encoding? verifica se os bytes são válidos para a codificação da string. Problemas de codificação causam o temido Encoding::CompatibilityError. Sempre saiba a codificação dos seus dados; padrão UTF-8.
# Ruby strings have an encoding
"hello".encoding # #<Encoding:UTF-8>
"café".encoding # #<Encoding:UTF-8>
# Default external encoding (for file I/O)
Encoding.default_external # #<Encoding:UTF-8>
# Force an encoding (reinterprets bytes, doesn't convert)
bytes = "café".bytes # [99, 97, 102, 195, 169]
latin1 = bytes.pack("C*").force_encoding("ISO-8859-1")
# "caf" + é (as single byte)
# Convert encoding (transcodes)
utf8 = "café"
latin1 = utf8.encode("ISO-8859-1") # converts
back = latin1.encode("UTF-8") # converts back
# Check if valid
"abc".valid_encoding? # true
"\xff".valid_encoding? # false (invalid byte)
# Common encodings: UTF-8, ASCII, ISO-8859-1, Windows-1252
# Always use UTF-8 unless you have a specific reasonConversão de Codificação e E/S
E/S de arquivo usa Encoding.default_external para leitura. Especifique a codificação por arquivo com a opção encoding:. A sintaxe 'source:target' (ISO-8859-1:UTF-8) lê na codificação de origem e converte para o alvo. Definir default_internal faz o Ruby auto-converter todas as strings lidas para essa codificação. Para apps web, tudo deve ser UTF-8. Ao processar dados legados, especifique codificações explicitamente para evitar corrupção.
# Read a file with specific encoding
content = File.read("data.txt", encoding: "ISO-8859-1")
# content.encoding is ISO-8859-1
# Read and convert to UTF-8
content = File.read("data.txt", encoding: "ISO-8859-1:UTF-8")
# The "source:target" syntax converts while reading
# Write with specific encoding
File.write("output.txt", "café", encoding: "UTF-8")
# Open with encoding
File.open("data.txt", "r:ISO-8859-1") do |f|
f.read # ISO-8859-1 encoded string
end
# Convert while opening
File.open("data.txt", "r:ISO-8859-1:UTF-8") do |f|
f.read # UTF-8 encoded string
end
# Set default encodings
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8Métodos de String Profundo
Strings Ruby têm métodos ricos. Inspeção (length, include?, start_with?) verifica propriedades. Métodos de transformação retornam novas strings (strings são mutáveis em Ruby, mas estes não mutam). Acesso a substring usa [start, length] ou ranges. sub substitui a primeira correspondência; gsub substitui todas (suporta regex e blocos). split/join convertem entre strings e arrays. Nota: O pragma frozen_string_literal do Ruby 3.0+ torna strings imutáveis para desempenho—use << ou + para construir.
s = "Hello, World"
# Inspection
s.length # 12
s.empty? # false
s.include?("World") # true
s.start_with?("Hello") # true
s.end_with?("World") # true
# Transformation (return new string)
s.upcase # "HELLO, WORLD"
s.downcase # "hello, world"
s.capitalize # "Hello, world"
s.swapcase # "hELLO, wORLD"
s.reverse # "dlroW ,olleH"
s.strip # remove leading/trailing whitespace
s.chomp(",") # remove trailing substring
s.chop # remove last char
# Substrings
s[0, 5] # "Hello" (start, length)
s[7..11] # "World" (range)
s[-5..] # "World" (negative index)
# Replace
s.sub("World", "Ruby") # first match
s.gsub("o", "0") # all matches
s.gsub(/\w+/) { |w| w.upcase } # with block
# Split/Join
"a,b,c".split(",") # ["a", "b", "c"]
["a", "b"].join("-") # "a-b"Strings Congeladas e Desempenho
frozen_string_literal: true (comentário mágico no topo do arquivo) torna todos os literais de string imutáveis—esta é uma otimização de desempenho (strings congeladas podem compartilhar memória) e previne bugs de mutação acidental. Para construir strings, use << (anexação in-place, O(n)) não += (cria nova string a cada vez, O(n²)). join é o mais limpo para arrays. StringIO age como um arquivo, mas escreve em uma string—útil para construir saída complexa. Ruby 3.x encoraja strings congeladas por padrão.
# frozen_string_literal: true (at top of file)
# Makes all string literals in the file frozen (immutable)
# Mutable vs frozen
s = "hello"
s << " world" # "hello world" (mutates)
f = "hello".freeze
# f << " world" # Error: can't modify frozen String
# Why freeze? Performance: frozen strings share memory
# "abc".freeze is the same object everywhere
"a".freeze.equal?("a".freeze) # true (same object)
# Building strings efficiently
# BAD: creates many intermediate strings
result = ""
items.each { |i| result += i.to_s } # O(n²)
# GOOD: use << or join
result = ""
items.each { |i| result << i.to_s } # O(n)
result = items.map(&:to_s).join # cleanest
# StringIO for buffered building
require 'stringio'
io = StringIO.new
io << "line 1\n"
io << "line 2\n"
result = io.stringSímbolos vs Strings
Símbolos (:name) são identificadores imutáveis e singleton—apenas um :foo existe na memória, sempre. Strings são dados de texto mutáveis com múltiplas instâncias. Use símbolos para chaves de hash (verificações de igualdade mais rápidas), nomes de métodos e valores semelhantes a enum. Use strings para texto real. Símbolos são ligeiramente mais rápidos para chaves de hash e comparação. No Ruby moderno (2.2+), símbolos podem ser coletados como lixo, então a antiga preocupação com 'vazamento de memória de símbolos' desapareceu. Rails usa símbolos extensivamente para chaves e valores de status.
# Symbol: immutable, reusable identifier
:hello
:world
status = :active
# String: mutable, can have many instances
"hello"
"world"
status = "active"
# Key difference: identity
"hello".equal?("hello") # false (different objects)
:hello.equal?(:hello) # true (same object, singleton)
# Memory: symbols are singletons
1000.times { :foo } # one :foo object
1000.times { "foo" } # 1000 "foo" objects (unless frozen)
# Use symbols for:
# - Hash keys (faster comparison)
# - Method names (send(:method_name))
# - Enum-like values (:active, :pending, :closed)
# - Identifiers (not text data)
# Use strings for:
# - Text data (names, content)
# - Things that change
# Conversion
:hello.to_s # "hello"
"hello".to_sym # :hello
"hello world".to_sym # :"hello world" (valid but ugly)Módulos e Mixins
Noções Básicas de Módulo
Módulos servem a dois propósitos: namespacing (agrupar código relacionado, prevenindo colisões de nome) e mixins (compartilhar comportamento sem herança). Métodos de módulo (def self.method) são chamados no módulo. Métodos de instância (def method) são para misturar em classes. Módulos não podem ser instanciados. Use módulos para namespacing de classes (MyApp::User) e para organizar constantes e funções utilitárias. Esta é a alternativa do Ruby à herança múltipla.
# Module: a namespace + collection of methods
module MathUtils
PI = 3.14159
def self.circle_area(radius) # module method
PI * radius ** 2
end
def square(x) # instance method (for mixins)
x * x
end
end
# Access constants and module methods
MathUtils::PI # 3.14159
MathUtils.circle_area(5) # 78.54
# Namespacing classes
module MyApp
class User
# MyApp::User
end
end
# Prevent instantiation (modules can't be instantiated)
# MathUtils.new # NoMethodErrorInclude vs Extend vs Prepend
Três maneiras de misturar módulos: include (adiciona métodos de instância, vai abaixo da classe na lookup), extend (adiciona métodos de classe), prepend (adiciona métodos de instância, vai acima da classe—pode envolver/sobrescrever). prepend é poderoso para hooks before/after (chame super para invocar o original). Lookup de método: prepend → classe → include → superclass. Use include para mixins normais, prepend quando precisar envolver métodos existentes, extend para funcionalidade em nível de classe.
module Greetable
def greet
"Hello from #{self.class}"
end
end
class User
include Greetable # adds as instance methods
end
User.new.greet # "Hello from User"
class Service
extend Greetable # adds as class methods
end
Service.greet # "Hello from Service"
# Method lookup order:
# prepend → class → include → super
module Logging
def save
puts "before save"
super # calls the original save
puts "after save"
end
end
class Record
prepend Logging # Logging#save runs first
def save; puts "saving"; end
end
Record.new.save
# before save / saving / after saveMixin Enumerable
Incluir Enumerable e definir #each dá a você map, select, reduce, sort, min, max e mais de 40 métodos—este é o protocolo de iterador do Ruby. Incluir Comparable e definir <=> dá a você <, >, ==, between?, clamp e suporte a sort. Esses mixins são o motivo pelo qual as coleções do Ruby são tão poderosas. Qualquer classe que represente uma coleção ou tenha uma ordenação natural deve incluí-los. É composição sobre herança.
# Include Enumerable + define #each = 50+ methods free
class Playlist
include Enumerable
def initialize(songs)
@songs = songs
end
def each
@songs.each { |song| yield song }
end
end
playlist = Playlist.new(["Song A", "Song B", "Song C"])
playlist.map { |s| s.upcase } # ["SONG A", "SONG B", "SONG C"]
playlist.select { |s| s.include?("A") } # ["Song A"]
playlist.reduce(:+) # "Song ASong BSong C"
playlist.sort # ["Song A", "Song B", "Song C"]
playlist.first(2) # ["Song A", "Song B"]
playlist.include?("Song B") # true
playlist.count # 3
# Comparable: define <=> for natural ordering
class Temperature
include Comparable
attr_reader :celsius
def initialize(c) @celsius = c end
def <=>(other) celsius <=> other.celsius end
end
t1 = Temperature.new(20)
t2 = Temperature.new(30)
t1 < t2 # true (uses <=>)
t1 == t2 # falseMétodos Singleton e Métodos de Classe
Métodos singleton pertencem a um objeto específico. Métodos de classe são apenas métodos singleton no objeto classe. 'class << self' abre a classe singleton (eigenclass) para definir múltiplos métodos de classe de forma limpa. O padrão singleton usa uma variável de classe para manter uma instância. Entender classes singleton é chave para o modelo de objetos do Ruby—todo objeto tem uma classe singleton que contém seus métodos únicos. Isso permite customização por objeto e metaprogramação.
# Singleton method: defined on one object only
str = "hello"
def str.shout
upcase + "!"
end
str.shout # "HELLO!"
# "world".shout # NoMethodError (only str has it)
# Class methods are singleton methods on the class
class Calculator
def self.add(a, b) # singleton method on Calculator
a + b
end
end
Calculator.add(1, 2) # 3
# Singleton class (eigenclass): where singleton methods live
class Calculator
class << self # opens the singleton class
def multiply(a, b) a * b end
def divide(a, b) a / b end
end
end
Calculator.multiply(2, 3) # 6
# Singleton pattern
class Logger
@instance = Logger.new
class << self
attr_reader :instance
end
end
Logger.instance.equal?(Logger.instance) # trueRefinements (Monkey Patching Escopado)
Refinements (Ruby 2.1+) permitem monkey patching escopado—adicione métodos a classes existentes, mas apenas onde você explicitamente 'using' o refinement. Isso é mais seguro que monkey patching global (que pode quebrar outro código). Refinements são ativados por escopo (arquivo, classe, método). Eles são úteis para adicionar métodos de conveniência sem poluir o namespace global. Menos comuns do que deveriam ser devido ao desempenho e algumas peculiaridades de escopo, mas são a maneira 'certa' de estender classes centrais.
# Refinement: limited monkey patching
module StringRefinements
refine String do
def shout
upcase + "!!!"
end
def palindrome?
downcase == downcase.reverse
end
end
end
# Without using: shout is undefined
# "hello".shout # NoMethodError
# With using: refinement is active in this scope
using StringRefinements
"hello".shout # "HELLO!!!"
"racecar".palindrome? # true
# Scoped to file or module
class App
using StringRefinements
def greet
"hi".shout # works here
end
end
# "hi".shout # NoMethodError (outside App)
# Safer than global monkey patching
# Refinements only affect code that opts inBlocos Procs Lambdas Profundo
Noções Básicas de Blocos
Blocos são closures anônimas passadas para métodos usando { } ou do...end. yield invoca o bloco de dentro do método. Blocos podem receber parâmetros via |var|. Um método pode verificar se um bloco foi passado com block_given?. Blocos são a fundação do padrão de iterador do Ruby e das DSLs. Eles capturam variáveis do escopo envolvente (closures).
# Block: anonymous chunk of code passed to a method
[1, 2, 3].each { |n| puts n } # Single-line
[1, 2, 3].each do |n| # Multi-line
puts n
end
# yield invokes the block
def greet
print "Hello, "
yield # Calls the block
puts "!"
end
greet { print "World" } # Hello, World!
# yield with arguments
def calculate(a, b)
yield(a + b)
yield(a * b)
end
calculate(3, 4) { |result| puts "Result: #{result}" }Procs e Lambdas
Procs e lambdas são objetos que envolvem blocos, permitindo armazenamento em variáveis e passagem. Procs têm verificação lenient de argumentos (argumentos extras ignorados) e retornam do método envolvente. Lambdas têm verificação strict de argumentos e retornam apenas de si mesmos. Use Procs para flexibilidade (como métodos que aceitam blocos), lambdas para funções anônimas com comportamento previsível. A sintaxe ->() {} (stabby lambda) é concisa para one-liners.
# Proc: object wrapping a block
my_proc = Proc.new { |x| puts x * 2 }
my_proc.call(5) # 10
my_proc.(5) # 10 (shorthand)
my_proc[5] # 10 (another shorthand)
# Lambda: stricter Proc
my_lambda = lambda { |x| puts x * 2 }
my_lambda = ->(x) { puts x * 2 } # Stabby lambda
# Difference 1: argument checking
my_proc.call(1, 2, 3) # OK (ignores extra args)
my_lambda.call(1, 2, 3) # ArgumentError (wrong number of args)
# Difference 2: return behavior
def proc_test
p = Proc.new { return 1 }
p.call
return 2 # Never reached
end
def lambda_test
l = lambda { return 1 }
l.call # Returns 1 to the lambda, not the method
return 2 # Reached
endObjetos Method
O método method() retorna um objeto Method que envolve um método existente. O operador & converte um Method ou Proc em um bloco (e vice-versa). Symbol#to_proc converte :upcase em { |x| x.upcase }, habilitando a sintaxe concisa &:symbol. Isso é Ruby idiomático para operações curtas de bloco. Objetos Method são bound ao seu receptor, então lembram self quando passados.
# Convert method to Proc with method()
class Calculator
def add(a, b) a + b end
def multiply(a, b) a * b end
end
calc = Calculator.new
add_proc = calc.method(:add)
puts add_proc.call(2, 3) # 5
# Pass method as block with & operator
def apply_op(a, b, op)
op.call(a, b)
end
puts apply_op(2, 3, calc.method(:add)) # 5
puts apply_op(2, 3, calc.method(:multiply)) # 6
# Symbol#to_proc
words = ["hello", "world"]
upcased = words.map(&:upcase) # ["HELLO", "WORLD"]
# Equivalent to: words.map { |w| w.upcase }
# Common with &:map(&:to_i), select(&:positive?), sort_by(&:length)Closures e Variáveis
Closures capturam variáveis por referência, não por valor. Múltiplas closures podem compartilhar estado (o exemplo do contador). Variáveis block-local (declaradas após ; nos parâmetros) shadow variáveis externas sem modificá-las. Isso permite padrões funcionais como acumuladores, generators e memoization. Cuidado: closures que retêm referências podem causar vazamentos de memória se não liberadas. Use este padrão para encapsulamento de estado privado.
# Blocks/Procs/Lambdas capture variables (closures)
counter = 0
increment = lambda { counter += 1 }
increment.call
increment.call
puts counter # 2
# Multiple closures sharing state
def make_counters
count = 0
[
lambda { count += 1 },
lambda { count },
lambda { count = 0 }
]
end
inc, get, reset = make_counters
inc.call; inc.call
puts get.call # 2
reset.call
puts get.call # 0
# Block-local variables (shadow outer)
x = 10
[1, 2, 3].each do |y; x| # x is block-local
x = y * 2
end
puts x # 10 (unchanged)Iteradores Customizados
Incluir Enumerable e implementar each dá à sua classe todos os métodos de iterador (map, select, reduce, sort, etc.) de graça. Passe o bloco com &block e chame-o, ou use yield. O operador & converte um bloco em um Proc e vice-versa. Esta é a maneira idiomática de tornar coleções customizadas iteráveis. Implemente each para iteração direta; adicione reverse_each para bidirecional.
class Tree
include Enumerable
def initialize(value, children = [])
@value = value
@children = children
end
def each(&block)
block.call(@value)
@children.each { |child| child.each(&block) }
end
end
tree = Tree.new(1, [
Tree.new(2, [Tree.new(4), Tree.new(5)]),
Tree.new(3)
])
tree.each { |v| puts v } # 1 2 4 5 3
puts tree.map { |v| v * 2 }.inspect # [2, 4, 8, 10, 6]
puts tree.select(&:even?).inspect # [2, 4]
puts tree.reduce(:+) # 15
# Including Enumerable gives you map, select, reduce, etc.Metaprogramação
Métodos Dinâmicos
method_missing intercepta chamadas a métodos indefinidos, habilitando dispatch dinâmico. Sempre sobrescreva respond_to_missing? para corresponder. define_method cria métodos em tempo de execução, útil para gerar métodos similares (como find_by_* do ActiveRecord). instance_variable_get/set acessam variáveis de instância pelo nome. Use metaprogramação com moderação—torna o código mais difícil de entender e depurar. Prefira definições explícitas quando possível.
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
# Method missing: catch undefined method calls
def method_missing(name, *args, &block)
if name.to_s =~ /^(.*)_with_prefix$/
attr_name = $1
"PREFIX_#{send(attr_name)}"
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s =~ /^(.*)_with_prefix$/ || super
end
end
p = Person.new("Alice", 30)
puts p.name_with_prefix # PREFIX_Alice
# Define methods dynamically
class Person
[:home, :work, :mobile].each do |type|
define_method("#{type}_phone") do
instance_variable_get("@#{type}_phone")
end
define_method("#{type}_phone=") do |value|
instance_variable_set("@#{type}_phone", value)
end
end
endClasses Abertas e Monkey Patching
Classes Ruby são abertas: você pode adicionar métodos a qualquer classe, incluindo built-ins como String. Isso é poderoso, mas perigoso (monkey patching pode quebrar outro código). Refinements (Ruby 2.1+) fornecem monkey patches escopados: aplicam-se apenas dentro de arquivos/classes que usam o módulo. Prefira refinements em vez de monkey patches globais para metaprogramação mais segura. Documente patches claramente e evite mudar comportamento central.
# Reopen existing classes (monkey patching)
class String
def shout
upcase + "!"
end
def word_count
split.size
end
end
puts "hello world".shout # HELLO WORLD!
puts "one two three".word_count # 3
# Refinements: scoped monkey patches
module ShoutRefinement
refine String do
def shout
upcase + "!"
end
end
end
class MyClass
using ShoutRefinement
def greet(name)
"hello #{name}".shout # Works here
end
end
# "test".shout # NoMethodError (refinement not active)Hooks e Callbacks
Ruby fornece hooks de lifecycle: inherited (subclasse criada), included (módulo incluído), prepended (módulo prependido), method_added (método definido), method_removed, method_undefined. Esses permitem que frameworks reajam a mudanças de classe automaticamente. ActiveRecord usa-os para rastrear atributos, Rails usa-os para routing. Sobrescreva hooks como métodos de classe (self.inherited) para eventos em nível de classe, métodos de instância para eventos de método.
class Observable
# Hook: called when class is subclassed
def self.inherited(subclass)
puts "#{subclass} inherits from #{self}"
end
# Hook: called when a module is included
def self.included(base)
puts "#{self} included in #{base}"
end
# Hook: called when a method is added
def method_added(name)
puts "Added method: #{name}"
end
# Hook: called when method is undefined
def method_removed(name)
puts "Removed method: #{name}"
end
end
class Child < Observable
def my_method; end
end
# Output:
# Child inherits from Observable
# Added method: my_method
# Other hooks: method_undefined, extended, prependedeval e Binding
eval executa uma string como código Ruby, perigoso se a entrada não for confiável (injeção de código). binding captura o contexto de execução atual (variáveis, self) para eval posterior. class_eval executa código em um contexto de classe (define métodos). instance_eval muda self para o receptor. Use-os para DSLs e geração de código, mas evite eval em entrada de usuário. Prefira blocos e define_method para código dinâmico. Sempre sanitize a entrada se eval for inevitável.
# eval: execute a string as Ruby code
result = eval("1 + 2 * 3")
puts result # 7
# eval with binding (context)
def eval_in_context(code)
x = 10
binding # Returns a Binding object capturing local variables
end
b = eval_in_context("binding")
puts eval("x * 2", b) # 20
# class_eval: execute code in class context
String.class_eval do
def palindrome?
self == reverse
end
end
puts "racecar".palindrome? # true
# instance_eval: execute in instance context
"hello".instance_eval do
puts length # 5 (self is the string)
end
# NEVER eval user input (security risk)Reflexão e Introspecção
Ruby fornece reflexão rica: methods lista todos os métodos, instance_variables lista variáveis de instância, ancestors mostra a cadeia de herança. parameters revela os nomes e tipos dos parâmetros de um método. respond_to? verifica se um objeto responde a um método. Use reflexão para depuração, serialização (inspecionar atributos) e construção de ferramentas genéricas (como ORMs). Evite excesso de reflexão—ela contorna a segurança de tipos e torna o código mais difícil de seguir.
class User
attr_accessor :name, :email
def initialize(name, email)
@name = name
@email = email
end
def save; end
def valid?; true; end
end
u = User.new("Alice", "[email protected]")
# Inspect methods
puts u.methods - Object.methods # [:name, :email, :save, :valid?, ...]
puts u.public_methods(false) # Only this class's methods
puts User.instance_methods(false) # [:save, :valid?, ...]
# Inspect variables
puts u.instance_variables.inspect # [:@name, :@email]
puts u.instance_variable_get(:@name) # Alice
# Inspect class
puts User.ancestors.inspect
puts User.instance_method(:save).parameters
# Check if responds to method
puts u.respond_to?(:save) # true
puts u.is_a?(User) # trueTestes (RSpec/Minitest)
Noções Básicas de RSpec
RSpec é uma estrutura de testes estilo BDD. describe agrupa testes relacionados, context agrupa por condição, it define um único teste. let cria variáveis lazy memoized. expect(...).to matcher é a sintaxe de asserção. Matchers comuns: eq, be_valid, include, raise_error. subject + is_expected reduz boilerplate. Execute com rspec (todos) ou rspec path/to/spec (específico). Use factories (FactoryBot) em vez de fixtures para dados de teste.
# spec/spec_helper.rb
require 'rspec'
require_relative '../lib/my_app'
# spec/models/user_spec.rb
require 'spec_helper'
RSpec.describe User do
# Setup with let (lazy, memoized)
let(:user) { User.new(name: 'Alice', email: '[email protected]') }
describe '#name' do
it 'returns the name' do
expect(user.name).to eq('Alice')
end
end
describe '#valid?' do
context 'with valid attributes' do
it 'is valid' do
expect(user).to be_valid
end
end
context 'without name' do
let(:user) { User.new(email: '[email protected]') }
it 'is invalid' do
expect(user).not_to be_valid
expect(user.errors[:name]).to include("can't be blank")
end
end
end
# Multiple examples
describe '#age' do
subject { user.age }
it { is_expected.to be >= 0 }
end
end
# Run: rspec spec/models/user_spec.rb
# Run all: rspecMocks e Stubs do RSpec
Stubs (allow) substituem valores de retorno de métodos; mocks (expect) verificam que o método foi chamado. double cria um test double (objeto falso). and_return define valores de retorno, and_raise simula erros, with define argumentos esperados. Use mocks com moderação—excesso de mocking torna testes frágeis. Teste comportamento, não implementação. Prefira objetos reais quando viável; use mocks para serviços externos (APIs, email, gateways de pagamento).
RSpec.describe PaymentProcessor do
let(:gateway) { double('PaymentGateway') }
let(:processor) { PaymentProcessor.new(gateway) }
describe '#charge' do
it 'charges the gateway' do
# Stub: replace method return value
allow(gateway).to receive(:charge).and_return(success: true)
result = processor.charge(100)
expect(result[:success]).to be true
end
it 'raises on gateway error' do
allow(gateway).to receive(:charge)
.and_raise(GatewayError, 'Network failure')
expect { processor.charge(100) }
.to raise_error(GatewayError, /Network/)
end
it 'verifies the gateway was called' do
# Mock: expect method to be called
expect(gateway).to receive(:charge).with(100)
processor.charge(100)
end
it 'receives multiple calls' do
allow(gateway).to receive(:charge).and_return(true, false, true)
expect(gateway.charge(1)).to be true
expect(gateway.charge(2)).to be false
end
end
endMinitest
Minitest é a biblioteca de testes padrão do Ruby (vem com Ruby). Suporta tanto sintaxe estilo Unit (assert/refute) quanto Spec (must_equal). setup é executado antes de cada teste. Asserções: assert_equal, assert_nil, assert_raises, assert_includes. Minitest é mais rápido que RSpec e tem menos dependências. Use Minitest::Mock para mocking, ou a gem Mocha para mais recursos. Rails usa Minitest por padrão (ActiveSupport::TestCase).
# test/test_helper.rb
require 'minitest/autorun'
require_relative '../lib/my_app'
# test/models/user_test.rb
require 'test_helper'
class UserTest < Minitest::Test
def setup
@user = User.new(name: 'Alice', email: '[email protected]')
end
def test_name
assert_equal 'Alice', @user.name
end
def test_valid_with_attributes
assert @user.valid?
end
def test_invalid_without_name
@user.name = nil
refute @user.valid?
assert_includes @user.errors[:name], "can't be blank"
end
def test_raises_on_error
assert_raises(ArgumentError) { User.new! }
end
end
# Spec-style Minitest
class UserSpec < Minitest::Spec
let(:user) { User.new(name: 'Alice') }
it 'has a name' do
_(user.name).must_equal 'Alice'
end
end
# Run: rake test
# Run specific: ruby -Itest test/models/user_test.rbDados de Teste e Factories
FactoryBot gera objetos de teste com valores padrão, traits para variações e associações. create persiste no DB; build não. Traits compõem (factory :admin_with_posts, traits: [:admin, :with_posts]). Factories são mais flexíveis que fixtures, mas mais lentas. Fixtures (YAML) são mais rápidas, mas menos flexíveis. Escolha com base nas necessidades: fixtures para dados simples, factories para relacionamentos complexos. Evite factories com muitos traits—mantenha-as focadas.
# FactoryBot (gem 'factory_bot')
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { 'Alice' }
email { '[email protected]' }
age { 30 }
trait :admin do
role { 'admin' }
end
trait :with_posts do
after(:create) do |user|
create_list(:post, 3, user: user)
end
end
factory :admin_user, traits: [:admin]
factory :user_with_posts, traits: [:with_posts]
end
end
# Usage in specs
let(:user) { create(:user) } # Saved to DB
let(:admin) { create(:admin_user) }
let(:user_with_posts) { create(:user_with_posts) }
let(:unsaved_user) { build(:user) } # Not saved
# Fixtures (alternative)
# test/fixtures/users.yml
# alice:
# name: Alice
# email: [email protected]
# In tests: users(:alice)Testes de Integração e Sistema
Request specs testam a pilha completa (routing, controllers, models) via HTTP. System specs (Capybara) dirigem um navegador real, testando interações JavaScript. Use request specs para endpoints de API, system specs para fluxos de usuário. have_http_status verifica códigos de resposta. visit/fill_in/click_button simulam ações do usuário. Testes de sistema são mais lentos, mas capturam bugs de integração. Execute testes de sistema com js: true para páginas dirigidas por JavaScript.
# spec/requests/users_spec.rb (integration)
require 'spec_helper'
RSpec.describe 'Users API', type: :request do
describe 'GET /users' do
before { create_list(:user, 3) }
it 'returns all users' do
get '/users', as: :json
expect(response).to have_http_status(200)
expect(JSON.parse(response.body).size).to eq(3)
end
end
describe 'POST /users' do
it 'creates a user' do
post '/users', params: { user: { name: 'Bob', email: '[email protected]' } }
expect(response).to have_http_status(201)
expect(User.last.name).to eq('Bob')
end
end
end
# System tests (browser automation)
# spec/system/login_spec.rb
require 'spec_helper'
RSpec.describe 'Login', type: :system do
it 'logs in a user' do
user = create(:user, password: 'password123')
visit login_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'password123'
click_button 'Log In'
expect(page).to have_content('Welcome')
end
endThreads e Fibers
Noções Básicas de Threads
Threads em Ruby (MRI) são green threads agendadas pela VM—não executam verdadeiramente em paralelo devido ao GIL (Global Interpreter Lock). No entanto, threads são úteis para concorrência de E/S (rede, operações de arquivo). join espera uma thread concluir. value recupera o valor de retorno. Para paralelismo CPU-bound, use múltiplos processos (fork, Sidekiq) ou JRuby (sem GIL). Sempre faça join de threads para evitar que sejam mortas quando a thread principal sai.
# Create and run threads
threads = (1..3).map do |i|
Thread.new(i) do |n|
puts "Thread #{n} started"
sleep(1)
puts "Thread #{n} finished"
end
end
# Wait for all threads
threads.each(&:join)
puts "All done"
# Thread with return value
thread = Thread.new { 1 + 2 }
result = thread.value # 3 (waits for completion)
# Thread status
thread = Thread.new { sleep(1) }
puts thread.status # 'run', 'sleep', false (finished), nil (error)
thread.join
# Current thread
Thread.current
Thread.list # All threads
Thread.main # Main threadSincronização de Threads
Mutex.synchronize garante que apenas uma thread execute um bloco por vez, prevenindo race conditions. Queue é uma FIFO thread-safe—produtores push, consumidores pop (bloqueia se vazio). ConditionVariable coordena threads: wait libera o mutex e dorme, signal/broadcast acorda threads em espera. Sempre use sincronização para estado mutável compartilhado. Deadlocks ocorrem quando threads esperam umas pelas outras—adquira locks em uma ordem consistente.
# Mutex: mutual exclusion
counter = 0
mutex = Mutex.new
threads = 10.times.map do
Thread.new do
1000.times do
mutex.synchronize { counter += 1 }
end
end
end
threads.each(&:join)
puts counter # 10000 (without mutex, would be less)
# Queue: thread-safe FIFO
require 'thread'
queue = Queue.new
producer = Thread.new do
5.times { |i| queue << "item #{i}" }
queue << :done
end
consumer = Thread.new do
loop do
item = queue.pop
break if item == :done
puts "Processed: #{item}"
end
end
[producer, consumer].each(&:join)
# ConditionVariable: signal between threads
mutex = Mutex.new
cv = ConditionVariable.newFibers
Fibers são threads leves cooperativas: elas yield controle manualmente em vez de serem preemptadas. resume inicia/retoma uma fiber; Fiber.yield a pausa e retorna um valor. Fibers são úteis para generators, lazy evaluation e parsing de state machines. Diferente de threads, apenas uma fiber é executada por vez, então nenhuma sincronização é necessária. Fibers são mais baratas que threads, mas não podem usar múltiplos núcleos. Use-as para multiplexação de E/S (EventMachine, Async).
# Fiber: cooperative concurrency (manual scheduling)
fiber = Fiber.new do
puts "Fiber started"
Fiber.yield "first yield"
puts "Fiber resumed"
Fiber.yield "second yield"
puts "Fiber ending"
"fiber done"
end
puts fiber.resume # "Fiber started", returns "first yield"
puts fiber.resume # "Fiber resumed", returns "second yield"
puts fiber.resume # "Fiber ending", returns "fiber done"
# Generator pattern with Fiber
def fibonacci
Fiber.new do
a, b = 0, 1
loop do
Fiber.yield a
a, b = b, a + b
end
end
end
fib = fibonacci
10.times { print fib.resume, " " }
# 0 1 1 2 3 5 8 13 21 34Ruby Async e Concorrente
A gem async fornece E/S assíncrona moderna usando Fibers por baixo dos panos, habilitando código de rede de alta concorrência. concurrent-ruby fornece abstrações thread-safe: Future (resultado async), Promise (encadeável), thread pools e variáveis atômicas. Use async para trabalho I/O-bound (HTTP, bancos de dados), thread pools para trabalho CPU-bound. Sempre desligue pools para evitar travamentos. Essas gems contornam o GIL do MRI para concorrência prática.
# Async gem for modern async I/O
# gem install async
require 'async'
require 'async/http/internet'
Async do
internet = Async::HTTP::Internet.new
# Run requests concurrently
tasks = 3.times.map do |i|
Async do
response = internet.get("https://httpbin.org/delay/#{i}")
puts "Request #{i}: #{response.status}"
end
end
tasks.each(&:wait)
end
# Concurrent Ruby (gem 'concurrent-ruby')
require 'concurrent'
# Future: async computation
future = Concurrent::Future.execute { sleep(1); 42 }
puts future.value # 42 (blocks until ready)
# Promise chain
Concurrent::Promise.execute { 1 }
.then { |v| v + 1 }
.then { |v| v * 2 }
.then { |v| puts v } # 4
# Thread pool
pool = Concurrent::FixedThreadPool.new(4)
10.times do |i|
pool.post { puts "Task #{i} on #{Thread.current.object_id}" }
end
pool.shutdownRactors (Ruby 3.0+)
Ractors (Ruby 3.0+) fornecem paralelismo real ao evitar o GIL. Cada Ractor tem seu próprio heap, então não há data races. A comunicação é via mensagens (send/receive, take). Objetos enviados são copiados (ou movidos com Ractor.move) para manter isolamento. Objetos congelados podem ser compartilhados. Ractors são o futuro do Ruby paralelo, mas têm restrições: a maioria das gems ainda não é Ractor-safe. Use-os para computação paralela CPU-bound onde o isolamento é aceitável.
# Ractor: true parallelism (no GIL)
# Ruby 3.0+ feature for thread-safe parallel execution
ractor = Ractor.new do
value = receive # Receive from main
value * 2
end
ractor.send(21)
puts ractor.take # 42
# Parallel computation
ractors = (1..4).map do |i|
Ractor.new(i) do |n|
sleep(1)
n * n
end
end
results = Ractor.select(*ractors)
# Or: ractors.map(&:take) # [1, 4, 9, 16]
# Ractor restrictions:
# - Cannot share mutable objects
# - Communication only via messages
# - Copy or move semantics for sending
# Shared frozen objects are OK
SHARED = Ractor.new { [1, 2, 3].freeze }
# Multiple ractors can read SHARED safelyCodificação e E/S
Codificação de String
Toda string Ruby tem uma codificação (padrão UTF-8). bytesize é a contagem de bytes; length é a contagem de caracteres (diferente para codificações multibyte). force_encoding muda a tag de codificação sem converter bytes (use quando você sabe a codificação real). encode converte entre codificações. Sempre verifique valid_encoding? antes de processar dados externos. Defina a codificação do arquivo fonte com o comentário mágico # encoding: utf-8 (embora UTF-8 seja padrão no Ruby 2.0+).
# Ruby strings have encodings
s = "hello"
puts s.encoding # <Encoding:UTF-8>
puts s.bytesize # 5
puts s.length # 5
# Multibyte characters
japanese = "こんにちは"
puts japanese.encoding # UTF-8
puts japanese.bytesize # 15 (3 bytes per char)
puts japanese.length # 5 (5 characters)
# Force encoding (interpret bytes differently)
bytes = "caf\u00e9".force_encoding('ASCII-8BIT')
puts bytes.encoding # ASCII-8BIT
# Encode (convert to different encoding)
utf8 = "café"
latin1 = utf8.encode('ISO-8859-1')
puts latin1.encoding # ISO-8859-1
# Invalid bytes
bad = "\xFF\xFE".force_encoding('UTF-8')
puts bad.valid_encoding? # false
fixed = bad.encode('UTF-8', invalid: :replace, replace: '?')E/S de Arquivo
File.read carrega o arquivo inteiro na memória; File.foreach lê linha por linha (eficiente em memória para arquivos grandes). File.write sobrescreve; modo 'a' anexa; 'r+' lê e escreve. Modo binário ('rb', 'wb') previne conversão de codificação. Sempre use a forma de bloco (File.open) para garantir que o arquivo seja fechado. FileUtils fornece operações de nível superior (cp, mv, mkdir_p). Verifique a existência com File.exist? antes de ler.
# Read entire file
content = File.read('data.txt')
# Read line by line (memory efficient)
File.foreach('large.log') do |line|
puts line.chomp
end
# Write to file
File.write('output.txt', 'Hello, World!')
# Append
File.open('log.txt', 'a') do |f|
f.puts "New log entry"
end
# Read/Write modes
File.open('file.txt', 'r+') do |f|
content = f.read
f.rewind
f.write("Updated: " + content)
end
# Binary mode
File.open('image.png', 'rb') do |f|
bytes = f.read
puts bytes.bytesize
end
# File operations
File.exist?('file.txt')
File.size('file.txt')
File.mtime('file.txt')
File.delete('file.txt')
File.rename('old.txt', 'new.txt')
FileUtils.cp('a.txt', 'b.txt') # require 'fileutils'StringIO e Tempfile
StringIO envolve uma string em uma interface semelhante a IO, útil para testar operações de arquivo sem tocar no disco. Tempfile cria arquivos temporários que são automaticamente excluídos quando o bloco sai (Tempfile.create) ou quando unlinked (Tempfile.new). Use Tempfile para dados grandes que não cabem na memória ou para passar a programas externos via path. Sempre garanta a limpeza com blocos ensure. StringIO é ótimo para testes de unidade de código de IO.
require 'stringio'
require 'tempfile'
# StringIO: in-memory file-like object
io = StringIO.new
io.puts "Hello"
io.puts "World"
io.rewind
puts io.read # "Hello\nWorld\n"
# Use like a file
io = StringIO.new("line1\nline2\nline3")
io.each_line { |line| puts line.chomp }
# Tempfile: auto-deleted temporary file
Tempfile.create('myapp') do |f|
f.write('temporary data')
f.rewind
puts f.read
end # File automatically deleted
# Tempfile with explicit cleanup
temp = Tempfile.new('myapp')
begin
temp.write('data')
temp.rewind
# Use temp.path to pass to external programs
ensure
temp.close
temp.unlink
endE/S de Rede
TCPSocket/TCPServer fornecem acesso TCP de baixo nível. Net::HTTP é o cliente HTTP padrão (built-in). Para necessidades HTTP complexas (sessões, cookies, retries), use as gems httparty ou faraday. Sempre defina use_ssl = true para HTTPS. Para HTTP de alto desempenho, considere async-http ou typhoeus. URI faz parse de URLs com segurança. Trate Net::ReadTimeout e Errno::ECONNREFUSED para código de rede robusto.
require 'socket'
require 'net/http'
require 'uri'
# TCP Client
TCPSocket.open('example.com', 80) do |socket|
socket.write("GET / HTTP/1.0\r\n\r\n")
puts socket.read
end
# TCP Server
server = TCPServer.new(2000)
loop do
client = server.accept
client.puts "Hello from server"
client.close
end
# HTTP Client
uri = URI('https://api.example.com/data')
response = Net::HTTP.get_response(uri)
puts response.code # 200
puts response.body
# HTTP POST
response = Net::HTTP.post_form(uri, key: 'value')
# HTTP with custom headers
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer token'
response = http.request(request)STDIO e Pipelines
$stdout, $stderr, $stdin são objetos IO globais (use as constantes STDOUT/STDERR/STDIN para os originais). Redirecione reatribuindo globais. StringIO captura saída para testes. Open3.popen3 dá controle total sobre stdin/stdout/stderr de um subprocesso. Open3.pipeline encadeia comandos como um pipe shell. Capture3 retorna stdout, stderr e status. Sempre feche stdin para sinalizar EOF ao subprocesso. Use wait.value para obter o status de saída.
# Standard IO
$stdout.puts "To stdout"
$stderr.puts "To stderr"
input = $stdin.gets # Read from stdin
# Redirect IO
$stdout = File.open('log.txt', 'w')
puts "This goes to file"
$stdout = $stdout # Restore (or use STDOUT constant)
# Capture output
captured = StringIO.new
original = $stdout
$stdout = captured
puts "Captured"
$stdout = original
puts captured.string # "Captured\n"
# Open3: run commands with full IO control
require 'open3'
Open3.popen3('grep hello') do |stdin, stdout, stderr, wait|
stdin.puts "hello world"
stdin.puts "goodbye"
stdin.close
puts stdout.read # "hello world"
puts wait.value.exitstatus
end
# Pipeline
output = Open3.pipeline('ls', 'grep .rb', 'wc -l')Pattern Matching (3.0+)
Pattern Matching Básico
Pattern matching (palavra-chave in) destrutura arrays e hashes, vinculando variáveis. => vincula valores correspondentes a variáveis. *rest captura elementos restantes do array. Hash matching é parcial: chaves extras são ignoradas. | corresponde a múltiplos padrões. case/in é a forma primária; a forma de uma linha é expression in pattern. Pattern matching é especialmente poderoso para parsing de dados estruturados (JSON, ASTs) e substituir cadeias if-else complexas.
# in: pattern matching (Ruby 2.7+, stable in 3.0)
case [1, 2, 3]
in [1, *rest]
puts "Starts with 1, rest: #{rest}"
end
case {name: 'Alice', age: 30}
in {name: String => name, age: Integer => age}
puts "#{name} is #{age}"
end
# Multiple patterns with |
case status
in :success | :ok
puts "Good"
else
puts "Other"
end
# Array patterns
case [1, 2, 3, 4]
in [_, _, *rest]
puts "Rest: #{rest}" # [3, 4]
end
# Hash patterns (partial match by default)
case {a: 1, b: 2, c: 3}
in {a: Integer}
puts "Has a" # Matches (ignores b, c)
endVinculação de Variáveis e Guards
=> vincula valores correspondentes a variáveis. O operador pin (^var) corresponde ao valor atual da variável (não vinculando). Guards (if/unless) adicionam condições. Padrões de array suportam * para splats em qualquer posição. Find pattern [*, target, *] procura um elemento dentro de um array. Pattern matching é declarativo e conciso para extração de dados complexos. Variáveis vinculadas em padrões estão disponíveis após a declaração case.
user = {name: 'Alice', age: 30, role: :admin}
case user
in {name: String => name, age: Integer => age, role: :admin} if age > 18
puts "#{name} is an adult admin"
end
# Pin operator (^): match against variable value
expected = 'Alice'
case user
in {name: ^expected}
puts "Matched expected name"
end
# Array destructuring with binding
case [1, 2, 3]
in [first, *middle, last]
puts "First: #{first}, Middle: #{middle}, Last: #{last}"
end
# Find pattern (search within array)
case [1, 2, 3, 4, 5]
in [*, 3 => three, *]
puts "Found 3: #{three}"
endPadrões de Classe e Tipo
Padrões de classe correspondem por tipo. Classes customizadas suportam destructuring implementando deconstruct (padrão array) e deconstruct_keys (padrão hash). O padrão Class[args] usa deconstruct; Class(key:) usa deconstruct_keys. Muitas classes built-in (Time, Date, MatchData) suportam destructuring. Isso permite matching expressivo em objetos de domínio. Implemente esses métodos para tornar suas classes pattern-matchable.
# Match by class
case value
in Integer
puts "Integer"
in String
puts "String"
in Array
puts "Array"
end
# Class with destructuring
class Point
attr_reader :x, :y
def initialize(x, y) @x, @y = x, y end
def deconstruct = [@x, @y] # Array pattern
def deconstruct_keys(keys) = {x: @x, y: @y} # Hash pattern
end
p = Point.new(1, 2)
case p
in Point[x, y]
puts "Point at (#{x}, #{y})"
end
case p
in Point(x:, y:)
puts "X: #{x}, Y: #{y}"
end
# Built-in classes support destructuring
case Time.now
in Time(hour: h) if h < 12
puts "Morning"
endPattern Matching de Uma Linha
A forma de uma linha (expr in pattern) retorna true/false em vez de lançar NoMatchingPatternError. Vincula variáveis em correspondência bem-sucedida. Útil para cláusulas guard e extração condicional. A forma rightward (pattern => var) vincula toda a correspondência. Matching de uma linha é conciso para casos simples; use case/in para lógica complexa de múltiplas ramificações. Variáveis vinculadas em correspondências falhas permanecem nil.
# One-line form (Ruby 3.0+)
{status: :ok, data: 42} in {status: :ok, data: Integer => data}
puts data # 42
# Returns true/false (does not raise)
[1, 2, 3] in [Integer, Integer, Integer] # true
[1, 2, 3] in [String, *, *] # false
# Useful for conditionals
if user in {role: :admin}
puts "Admin access"
end
# Guard clauses
def process(data)
return unless data in {type: String, value: Integer}
# ...
end
# Extracting from JSON
json = JSON.parse('{"user":{"name":"Alice","age":30}}')
json in {"user" => {"name" => String => name, "age" => Integer => age}}
puts "#{name}, #{age}" # Alice, 30Casos de Uso Práticos
Pattern matching se destaca em: parsing de dados estruturados (JSON, XML), state machines, traversal de AST e dispatch baseado na forma dos dados. Substitui cadeias if-else verbosas por padrões declarativos. A cláusula else lida com formatos inesperados. Combine com guards para condições complexas. Pattern matching torna o código mais legível e sustentável para manipulação complexa de dados. É um dos recursos mais poderosos do Ruby 3 para código limpo e expressivo.
# 1. Parse JSON API responses
def handle_response(response)
case JSON.parse(response)
in {"status" => "success", "data" => Array => items}
items.each { |item| process(item) }
in {"status" => "error", "message" => String => msg}
raise "API error: #{msg}"
in {"status" => "error", "code" => Integer => code} if code >= 500
retry_request
else
raise "Unknown response format"
end
end
# 2. State machines
case state
in :idle, event: :start
transition_to :running
in :running, event: :pause
transition_to :paused
in :paused, event: :resume
transition_to :running
in :running | :paused, event: :stop
transition_to :idle
end
# 3. AST traversal
def evaluate(node)
case node
in {type: 'number', value: n}
n
in {type: 'add', left:, right:}
evaluate(left) + evaluate(right)
in {type: 'mul', left:, right:}
evaluate(left) * evaluate(right)
end
endMetaprogramação
send e define_method
define_method cria métodos dinamicamente. send chama métodos pelo nome (mesmo privados). public_send respeita a visibilidade. Útil para DSLs e redução de boilerplate. Tenha cuidado com entrada de usuário para evitar problemas de segurança. Metaprogramação é poderosa, mas pode tornar o código mais difícil de entender.
class Foo
define_method(:greet) do |name|
"Hello, #{name}"
end
end
Foo.new.greet("Alice") # "Hello, Alice"
# Dynamic method call
Foo.new.send(:greet, "Bob") # "Hello, Bob"
Foo.new.public_send(:greet, "Bob") # Respects visibilitymethod_missing
method_missing intercepta chamadas a métodos indefinidos. Útil para dispatch dinâmico e DSLs. Sempre sobrescreva respond_to_missing? para corresponder. Pode ser lento e ocultar bugs. Prefira define_method quando o conjunto de métodos é conhecido. ActiveRecord o usa para accessors de atributo.
class DynamicHash
def method_missing(name, *args)
if name.to_s.end_with?('=')
self[name.to_s.chomp('=')] = args.first
else
self[name.to_s]
end
end
def respond_to_missing?(name, include_private = false)
true
end
endeval
eval executa uma string como código Ruby. Extremamente poderoso, mas perigoso. Nunca faça eval de entrada não confiável (injeção de código). Use binding.eval para um contexto específico. Para avaliação segura, use um parser como Ripper ou uma sandbox. A maioria dos casos de uso tem alternativas mais seguras.
# Execute a string as Ruby code
result = eval("1 + 2 * 3") # 7
# Dynamic method definition
eval("def dynamic_method; 42; end")
dynamic_method # 42
# WARNING: never eval untrusted input!Classes Abertas
Classes Ruby são abertas: você pode adicionar métodos a qualquer classe, incluindo built-ins. Isso é chamado monkey patching. Poderoso para correções rápidas, mas pode causar conflitos e confusão. Use refinements para modificações escopadas. Documente mudanças claramente. Prefira composição em vez de monkey patching.
class String
def shout
upcase + "!"
end
end
"hello".shout # "HELLO!"
# Monkey patching: modifying existing classes
# Use sparingly, can cause conflictsMacros de Classe
Macros de classe são métodos de classe que definem outros métodos. has_many, belongs_to, attr_accessor são exemplos. Eles usam define_method internamente. É assim que Rails cria métodos dinâmicos. DSLs como RSpec e Sinatra usam esse padrão. Torna o código declarativo e legível.
class ActiveRecord::Base
def self.has_many(name)
define_method(name) { [] }
end
end
class Post < ActiveRecord::Base
has_many :comments
end
Post.new.comments # []Snippets de Ruby relacionados
Copy-paste ready code for common tasks.
Blocks, Procs, Lambdas
Use blocks with yield, Procs, lambdas, and the & operator.
Classes and Modules
Define classes with inheritance, mix in modules, and add class methods.
Iterators
Use each, map, select, reduce, group_by, and lazy enumerators.
Strings
Interpolate, trim, split, replace, and pattern-match strings.
Hashes
Build, default, transform, merge, and group with Hash.
Metaprogramming
Define methods dynamically, intercept with method_missing, and build DSLs.
Error Handling
Raise and rescue typed exceptions with ensure and retry.
File I/O
Read, write, append, traverse directories, and process CSV files.
Was this helpful?