self em Ruby: o que é e por que importa
Em Ruby, self é uma referência ao “objeto atual” no ponto exato em que o código está sendo executado. Isso muda conforme o contexto (top-level, dentro de uma instância, dentro da classe, dentro de um módulo). Entender self ajuda a diferenciar métodos de instância e métodos de classe, além de evitar confusões com variáveis e chamadas de método.
Como inspecionar self rapidamente
Uma forma prática de entender o contexto é imprimir self e sua classe:
puts self
puts self.classEm um arquivo Ruby executado diretamente, no topo do arquivo, self costuma ser o objeto especial main (uma instância de Object).
self em diferentes contextos
1) No topo do arquivo (top-level)
# arquivo: demo_self.rb
puts self # => main
puts self.class # => ObjectNesse contexto, quando você define um método “solto” (fora de classes), ele vira um método privado de Object, disponível em todo lugar (o que costuma ser indesejado em projetos). Prefira organizar em classes/módulos.
2) Dentro de um método de instância
Dentro de um método de instância, self é a instância que recebeu a chamada.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
class User
def initialize(name)
@name = name
end
def greet
puts self # => #<User:...>
puts self.class # => User
"Olá, #{@name}"
end
end
u = User.new("Ana")
puts u.greet3) Dentro do corpo da classe (fora de métodos)
No corpo da classe, self é o próprio objeto-classe. Isso é útil para definir métodos de classe e configurações.
class User
puts self # => User
puts self.class # => Class
end4) Dentro de um método de classe
Em um método de classe, self também é o objeto-classe (por exemplo, User).
class User
def self.role
puts self # => User
"member"
end
end
puts User.role5) Dentro de módulos e namespaces
Em módulos, self é o módulo (no corpo) e pode ser usado para definir métodos de módulo (métodos “estáticos” do módulo).
module Slug
def self.normalize(text)
text.downcase.strip.gsub(" ", "-")
end
end
puts Slug.normalize("Olá Mundo")Métodos de instância vs métodos de classe
Quando usar método de instância
Use método de instância quando o comportamento depende do estado de um objeto específico (variáveis de instância como @name, @items, etc.).
class Cart
def initialize
@items = []
end
def add(item)
@items << item
end
endQuando usar método de classe
Use método de classe quando o comportamento pertence ao “tipo” e não a uma instância específica: fábricas (factory methods), construtores alternativos, utilitários relacionados ao domínio, ou consultas que retornam instâncias.
class Money
def initialize(cents)
@cents = cents
end
def self.from_reais(value)
cents = (value * 100).round
new(cents)
end
end
m = Money.from_reais(10.50)Dois jeitos comuns de definir métodos de classe
Ambos são equivalentes; escolha um padrão e mantenha consistência.
class User
def self.build_guest
new("Guest")
end
endclass User
class << self
def build_guest
new("Guest")
end
end
endPor que às vezes você precisa escrever self. explicitamente
Ruby permite chamar métodos sem receptor explícito (parece uma “função”), e isso pode confundir atribuição com chamada de método setter. Para chamar um setter, você geralmente precisa de self..
Exemplo clássico: setter vs variável local
class Profile
attr_accessor :name
def initialize(name)
# Sem self, Ruby interpreta como variável local 'name = ...'
self.name = name
end
endRegra prática: dentro da instância, use self.algo = ... quando estiver chamando um setter.
Padrões comuns com métodos de classe
Factory method (método fábrica)
Uma fábrica encapsula regras para criar objetos. Ajuda a evitar initialize com muitos parâmetros e condicionais.
class User
def initialize(name, role)
@name = name
@role = role
end
def self.guest
new("Guest", :guest)
end
def self.admin(name)
new(name, :admin)
end
end
u1 = User.guest
u2 = User.admin("Carla")Construtores alternativos (parse, from_*, build_*)
Construtores alternativos criam instâncias a partir de formatos diferentes (string, hash, etc.).
class Point
def initialize(x, y)
@x = x
@y = y
end
def self.parse(text)
x_str, y_str = text.split(",")
new(Integer(x_str), Integer(y_str))
end
end
p1 = Point.parse("10,20")Validação e normalização na criação
Uma fábrica pode normalizar dados e validar antes de instanciar.
class Email
def initialize(value)
@value = value
end
def self.build(raw)
value = raw.to_s.strip.downcase
raise ArgumentError, "email inválido" unless value.include?("@")
new(value)
end
end
email = Email.build(" TESTE@EXEMPLO.COM ")Colaboração entre objetos: evitando classes inchadas
Um erro comum é colocar responsabilidades demais em uma única classe: validação, cálculo, formatação, persistência, envio de e-mail, etc. Em Ruby, é natural criar objetos pequenos que colaboram: um objeto usa outro para cumprir uma tarefa.
Exemplo de classe inchada (problema)
Imagine um Order que calcula total, aplica desconto, formata recibo e ainda envia e-mail:
class Order
def initialize(items, customer_email)
@items = items
@customer_email = customer_email
end
def total
@items.sum { |i| i[:price] * i[:qty] }
end
def total_with_discount(coupon)
t = total
if coupon == "OFF10"
t * 0.9
else
t
end
end
def receipt_text
lines = @items.map { |i| "#{i[:qty]}x #{i[:name]}" }
lines.join("\n") + "\nTotal: #{total}"
end
def email_receipt
# ... envio de email ...
end
endProblemas: difícil testar partes isoladas, difícil reaproveitar regras (desconto/recibo), e qualquer mudança aumenta o risco de quebrar algo.
Refatoração 1: separar cálculo de total (objeto colaborador)
Passo a passo:
- Extrair a regra de total para uma classe dedicada.
- Fazer
Orderdelegar o cálculo. - Testar manualmente rodando um script simples.
class OrderTotalCalculator
def initialize(items)
@items = items
end
def total
@items.sum { |i| i[:price] * i[:qty] }
end
end
class Order
def initialize(items, customer_email)
@items = items
@customer_email = customer_email
end
def total
OrderTotalCalculator.new(@items).total
end
endScript de teste manual
# arquivo: test_total.rb
require_relative "order"
items = [
{ name: "Teclado", price: 100.0, qty: 2 },
{ name: "Mouse", price: 50.0, qty: 1 }
]
order = Order.new(items, "a@b.com")
puts order.total # esperado: 250.0Ao rodar o script, você valida a regra sem depender de outras partes (recibo, desconto, e-mail).
Refatoração 2: desconto como estratégia (colaboração e extensibilidade)
Em vez de condicional por cupom dentro de Order, crie objetos de desconto. Isso reduz if/else e facilita adicionar novos cupons.
class NoDiscount
def apply(amount)
amount
end
end
class PercentageDiscount
def initialize(percent)
@percent = percent
end
def apply(amount)
amount * (1.0 - @percent)
end
end
class CouponFactory
def self.build(code)
case code
when "OFF10" then PercentageDiscount.new(0.10)
when nil, "" then NoDiscount.new
else
raise ArgumentError, "cupom desconhecido"
end
end
end
class Order
def initialize(items, customer_email)
@items = items
@customer_email = customer_email
end
def total
OrderTotalCalculator.new(@items).total
end
def total_with_coupon(code)
discount = CouponFactory.build(code)
discount.apply(total)
end
endTeste manual via script
# arquivo: test_discount.rb
require_relative "order"
items = [
{ name: "Teclado", price: 100.0, qty: 2 },
{ name: "Mouse", price: 50.0, qty: 1 }
]
order = Order.new(items, "a@b.com")
puts order.total_with_coupon("OFF10") # esperado: 225.0
puts order.total_with_coupon(nil) # esperado: 250.0Note como Order não precisa conhecer detalhes de cada cupom; ela apenas colabora com um objeto que responde a apply.
Refatoração 3: recibo como objeto de apresentação (separar formatação)
Formatação de texto é uma responsabilidade diferente de regras de negócio. Extraia para um gerador de recibo.
class ReceiptBuilder
def initialize(items, total)
@items = items
@total = total
end
def text
lines = @items.map { |i| "#{i[:qty]}x #{i[:name]} - #{format("%.2f", i[:price])}" }
(lines + ["Total: #{format("%.2f", @total)}"]).join("\n")
end
end
class Order
def initialize(items, customer_email)
@items = items
@customer_email = customer_email
end
def total
OrderTotalCalculator.new(@items).total
end
def receipt_text
ReceiptBuilder.new(@items, total).text
end
endTeste manual via script
# arquivo: test_receipt.rb
require_relative "order"
items = [
{ name: "Teclado", price: 100.0, qty: 2 },
{ name: "Mouse", price: 50.0, qty: 1 }
]
order = Order.new(items, "a@b.com")
puts order.receipt_textAgora você pode ajustar o layout do recibo sem tocar em cálculo de total ou descontos.
self e colaboração: onde entram métodos de classe
Métodos de classe são úteis para construir colaboradores e manter a criação centralizada. No exemplo, CouponFactory.build é um método de classe que retorna um objeto de desconto apropriado. Esse padrão é comum quando:
- Você quer esconder detalhes de criação (quais classes concretas existem).
- Você quer normalizar/validar entradas antes de criar.
- Você quer manter
initializesimples e previsível.
Comparando: criar direto vs usar fábrica
| Abordagem | Como fica | Impacto |
|---|---|---|
| Criar direto | PercentageDiscount.new(0.10) | Espalha regras de criação pelo código |
| Fábrica | CouponFactory.build(code) | Centraliza regras e facilita manutenção |
Organizando responsabilidades para melhorar testabilidade manual
Ao separar em objetos pequenos, você consegue testar manualmente cada peça com scripts curtos, sem precisar “montar o sistema inteiro”. Um checklist prático:
- Regra de cálculo: teste
OrderTotalCalculatorcom diferentes itens. - Desconto: teste cada classe de desconto isoladamente e a fábrica com códigos válidos/ inválidos.
- Apresentação: teste
ReceiptBuildercom itens e total conhecido. - Integração leve: teste
Orderchamandototal,total_with_couponereceipt_text.
Exemplo: script único de verificação rápida
# arquivo: smoke_test.rb
require_relative "order"
items = [
{ name: "Teclado", price: 100.0, qty: 2 },
{ name: "Mouse", price: 50.0, qty: 1 }
]
order = Order.new(items, "a@b.com")
puts "Total: #{order.total}"
puts "Total OFF10: #{order.total_with_coupon("OFF10")}"
puts "--- RECIBO ---"
puts order.receipt_textArmadilhas comuns e como evitar
Confundir método de classe com método de instância
Se você definir def self.build, você chama com Classe.build. Se definir def build, você chama em uma instância: obj.build. Se errar, verá erros como NoMethodError.
class A
def self.x; end
def y; end
end
A.x # ok
A.y # erro: y é de instância
A.new.y # okUsar self sem necessidade e reduzir legibilidade
Dentro de métodos de instância, não é necessário escrever self para chamar métodos comuns (sem setter). Use self quando:
- Você precisa chamar um setter (
self.name = ...). - Você quer deixar explícito o receptor por clareza em um ponto crítico.
Classes “inchadas” por falta de colaboradores
Se uma classe começa a ter muitos métodos com temas diferentes (cálculo, formatação, IO, validação, integração), extraia colaboradores. Uma dica prática: se você consegue nomear um grupo de métodos com um substantivo (ex.: “calculadora”, “formatador”, “validador”), provavelmente merece uma classe própria.