Skip to content

Ruby Folha de referência

Linguagem dinâmica e elegante, otimizada para a felicidade do desenvolvedor.

01

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.

ruby
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)  # true

Sí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.

ruby
status = :active
puts status.class        # Symbol
puts status.to_s         # "active"
# Symbols are immutable, reusable strings
hash = { name: "Alice", status: :active }
puts hash[:status]       # active

Nil 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.

ruby
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" # falsy

Conversã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.

ruby
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.

ruby
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} here
02

Strings

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.

ruby
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, W0rld

Mutaçã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.

ruby
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.

ruby
text = <<~HEREDOC
  Hello,
  World!
  Indentation is stripped.
HEREDOC
puts text
# <<~ strips leading whitespace (Ruby 2.3+)
# <<HEREDOC preserves indentation

Formataçã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.

ruby
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 float

Concatenaçã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.

ruby
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, s4
03

Estruturas 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.

ruby
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) # true

Hashes

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.

ruby
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.

ruby
(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.

ruby
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.

ruby
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)
04

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.

ruby
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 >= 60

Case (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.

ruby
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"
end

While / 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.

ruby
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
end

Iteradores (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.

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 3

Break / 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.

ruby
[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  # 20
05

Mé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 =.

ruby
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)  # 10

Blocos 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.

ruby
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 }  # 10

Procs 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.

ruby
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 method

Objeto 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.

ruby
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.inspect

Valores 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.

ruby
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 = coords
06

Classes 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.

ruby
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.count

Heranç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.

ruby
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.

ruby
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 walking

Controle 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.

ruby
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 method

Self 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.

ruby
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  # 2
07

Tratamento 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).

ruby
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/failure

Raise 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.

ruby
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}"
end

Retry

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.

ruby
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 top

Modifier 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.

ruby
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.

ruby
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 loops
08

E/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.

ruby
# 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.

ruby
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}" }
end

Existê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.

ruby
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.

ruby
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 } # recursive

CSV 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.

ruby
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"]  # Alice
09

Data/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.

ruby
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          # tomorrow

Parsing 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.

ruby
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/.

ruby
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.

ruby
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/2024

Opçõ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).

ruby
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"
10

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.

ruby
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 methods

method_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.

ruby
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.

ruby
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.

ruby
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 preemptively

Send 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.

ruby
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!
11

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).

ruby
# 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
end

Procs 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.

ruby
# 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  # 2

Objetos 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.

ruby
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, 3

Closures 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.

ruby
# 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)  # 42

Iteradores 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.

ruby
# 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) }
12

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.

ruby
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_a

Lazy 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.

ruby
# 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.

ruby
# 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_a

Ordenaçã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.

ruby
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   # 1

Iteraçã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.

ruby
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"]}
13

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).

ruby
# 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'
end

Comandos 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.

ruby
# 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 clean

Gemspec (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.

ruby
# 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.gem

Tarefas 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].

ruby
# 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'
end

Rbenv 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.

ruby
# 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 -v
14

Noçõ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.

ruby
# 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
end

Models 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.

ruby
# 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.

ruby
# 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
end

Views 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.

ruby
<!-- 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.

ruby
# 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, :integer
15

Testes 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/.

ruby
# 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) }
end

Mocks 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).

ruby
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.

ruby
# 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).

ruby
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 evaluation

Testes 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.

ruby
# 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
end
16

Operaçõ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.

ruby
# 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.

ruby
# 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.close

Operaçõ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.

ruby
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 \).

ruby
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 after

CSV 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.

ruby
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"))
17

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
# 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 reason

Conversã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.

ruby
# 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_8

Mé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.

ruby
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.

ruby
# 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.string

Sí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.

ruby
# 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)
18

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.

ruby
# 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  # NoMethodError

Include 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.

ruby
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 save

Mixin 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.

ruby
# 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  # false

Mé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.

ruby
# 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)  # true

Refinements (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.

ruby
# 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 in
19

Blocos 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).

ruby
# 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.

ruby
# 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
end

Objetos 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.

ruby
# 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.

ruby
# 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.

ruby
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.
20

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.

ruby
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
end

Classes 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.

ruby
# 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.

ruby
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, prepended

eval 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.

ruby
# 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.

ruby
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)          # true
21

Testes (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.

ruby
# 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: rspec

Mocks 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).

ruby
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
end

Minitest

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).

ruby
# 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.rb

Dados 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.

ruby
# 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.

ruby
# 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
end
22

Threads 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.

ruby
# 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 thread

Sincronizaçã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.

ruby
# 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.new

Fibers

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).

ruby
# 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 34

Ruby 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.

ruby
# 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.shutdown

Ractors (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.

ruby
# 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 safely
23

Codificaçã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
# 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.

ruby
# 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.

ruby
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
end

E/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.

ruby
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.

ruby
# 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')
24

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.

ruby
# 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)
end

Vinculaçã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.

ruby
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}"
end

Padrõ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.

ruby
# 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"
end

Pattern 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.

ruby
# 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, 30

Casos 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.

ruby
# 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
end
25

Metaprogramaçã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.

ruby
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 visibility

method_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.

ruby
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
end

eval

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.

ruby
# 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.

ruby
class String
  def shout
    upcase + "!"
  end
end
"hello".shout  # "HELLO!"
# Monkey patching: modifying existing classes
# Use sparingly, can cause conflicts

Macros 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.

ruby
class ActiveRecord::Base
  def self.has_many(name)
    define_method(name) { [] }
  end
end
class Post < ActiveRecord::Base
  has_many :comments
end
Post.new.comments  # []

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.