O que são módulos em Ruby
Em Ruby, módulos (modules) são estruturas para agrupar código. Eles são usados principalmente de duas formas: namespaces (para organizar nomes e evitar colisões) e mixins (para reutilizar comportamento em classes sem herança).
Um módulo pode conter constantes, métodos e até outros módulos. Ele não é instanciado como uma classe, mas pode ser incluído/estendido por classes e objetos.
Módulos como namespaces (evitando colisões de nomes)
Quando projetos crescem, é comum surgirem nomes repetidos (por exemplo, duas classes chamadas Parser ou Validator). Namespaces resolvem isso agrupando nomes sob um “prefixo” (o módulo).
Exemplo: duas classes com o mesmo nome, em contextos diferentes
module Payments # namespace de pagamentos
class Parser
def parse(payload)
{ source: :payments, raw: payload }
end
end
end
module Reports # namespace de relatórios
class Parser
def parse(payload)
{ source: :reports, raw: payload }
end
end
end
payments_parser = Payments::Parser.new
reports_parser = Reports::Parser.new
payments_parser.parse("...")
reports_parser.parse("...")Repare no operador :: para acessar constantes dentro do namespace: Payments::Parser e Reports::Parser.
Organizando constantes dentro de um módulo
Constantes (como configurações e listas fixas) ficam mais seguras e fáceis de localizar quando agrupadas em um módulo.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
module AppConfig
DEFAULT_TIMEZONE = "America/Sao_Paulo"
MAX_RETRIES = 3
end
AppConfig::MAX_RETRIESBoa prática: prefira constantes imutáveis (ou tratadas como tal). Se for um array/hash, considere congelar:
module AppConfig
SUPPORTED_LOCALES = ["pt-BR", "en"].freeze
endPasso a passo: criando um namespace para um domínio
- Passo 1: escolha um nome de módulo que represente o domínio (ex.:
Billing,Auth,Catalog). - Passo 2: mova classes relacionadas para dentro do módulo.
- Passo 3: ajuste referências para usar
SeuModulo::SuaClasse. - Passo 4: mantenha o módulo “fino”: ele organiza; quem faz trabalho são classes/mixins internos.
Mixins: reutilizando comportamento com include e extend
Mixins permitem compartilhar métodos entre classes sem criar uma hierarquia de herança artificial. Em Ruby, isso é feito com include e extend.
include: métodos de instância
include adiciona os métodos do módulo como métodos de instância da classe.
module Taggable
def tags
@tags ||= []
end
def add_tag(tag)
tags << tag
end
end
class Post
include Taggable
end
post = Post.new
post.add_tag("ruby")
post.tags # => ["ruby"]extend: métodos de classe (ou do objeto)
extend adiciona os métodos do módulo como métodos do receptor. Quando usado em uma classe, vira “método de classe”.
module SlugGenerator
def slugify(text)
text.downcase.strip.gsub(/\s+/, "-")
end
end
class Category
extend SlugGenerator
end
Category.slugify("Ruby Básico") # => "ruby-básico"Também é possível estender um objeto específico:
user = Object.new
user.extend(SlugGenerator)
user.slugify("Nome do Usuário")Quando usar include vs extend
| Objetivo | Use | Resultado |
|---|---|---|
| Compartilhar comportamento entre instâncias | include | Métodos viram métodos de instância |
| Compartilhar utilitários como “métodos de classe” | extend | Métodos viram métodos da classe (singleton) |
Métodos de módulo (module_function) e responsabilidades
Às vezes você quer um módulo que funcione como “biblioteca” de funções, sem precisar de classe. Para isso, você pode definir métodos e expô-los como métodos do módulo.
Exemplo com module_function
module MathUtils
module_function
def clamp(value, min, max)
return min if value < min
return max if value > max
value
end
end
MathUtils.clamp(15, 0, 10) # => 10Observação prática: com module_function, o método também fica disponível como método privado quando o módulo é incluído. Isso pode ser útil para evitar “poluir” a API pública de instâncias.
Exemplo prático 1: módulo de validação (mixin com include)
Um módulo de validação costuma ser um bom candidato a mixin: várias classes precisam validar dados, mas cada uma tem suas regras. A ideia é fornecer um “esqueleto” reutilizável e deixar a classe definir as regras específicas.
Objetivo
- Fornecer uma API mínima:
valid?eerrors - Manter responsabilidade clara: o módulo gerencia erros; a classe define validações
Passo a passo
- Passo 1: crie o módulo com armazenamento de erros e método
valid?. - Passo 2: defina um “gancho” (método esperado) para a classe implementar, por exemplo
validate. - Passo 3: inclua o módulo nas classes que precisam validar.
module Validation
# Responsabilidade: armazenar erros e orquestrar a validação.
def errors
@errors ||= []
end
def valid?
errors.clear
validate
errors.empty?
end
private
# A classe que inclui deve implementar este método.
def validate
raise NotImplementedError, "Implemente #validate na classe"
end
def add_error(message)
errors << message
end
end
class User
include Validation
attr_reader :email, :age
def initialize(email:, age:)
@email = email
@age = age
end
private
def validate
add_error("email é obrigatório") if email.nil? || email.strip.empty?
add_error("email inválido") unless email.to_s.include?("@")
add_error("idade deve ser >= 18") if age.to_i < 18
end
end
user = User.new(email: "", age: 16)
user.valid? # => false
user.errors # => ["email é obrigatório", "email inválido", "idade deve ser >= 18"]Boas práticas aplicadas
- API pequena: apenas
valid?eerrorssão públicos. - Responsabilidade clara: o módulo não conhece regras de negócio; ele só executa e coleta erros.
- Comentário objetivo: uma linha explicando o papel do módulo e o contrato esperado (
#validate).
Exemplo prático 2: módulo utilitário (namespace + métodos de módulo)
Módulos utilitários são úteis para funções puras (sem estado), como normalização de texto, formatação e pequenas conversões. Para evitar colisões e manter organização, coloque-os em um namespace.
Passo a passo
- Passo 1: crie um namespace (ex.:
Utils). - Passo 2: crie módulos internos por tema (ex.:
Utils::Text,Utils::Numbers). - Passo 3: exponha métodos como métodos do módulo (ex.:
module_function).
module Utils
module Text
module_function
# Remove espaços extras e normaliza quebras de espaço.
def squish(text)
text.to_s.strip.gsub(/\s+/, " ")
end
# Gera um identificador simples para URLs.
def slug(text)
squish(text).downcase.gsub(" ", "-")
end
end
module Numbers
module_function
# Converte string com vírgula para float (ex.: "10,50" => 10.5).
def comma_to_float(text)
text.to_s.strip.gsub(",", ".").to_f
end
end
end
Utils::Text.squish(" Ruby do Zero ") # => "Ruby do Zero"
Utils::Text.slug("Ruby do Zero") # => "ruby-do-zero"
Utils::Numbers.comma_to_float("10,50") # => 10.5Como manter responsabilidades claras
- Não misture temas: texto em
Utils::Text, números emUtils::Numbers. - Evite “módulo Deus”: se
Utilscomeçar a crescer demais, quebre em submódulos. - Funções previsíveis: utilitários devem ter entradas/saídas claras e pouco efeito colateral.
Colisões de nomes: sinais e estratégias
Sinais comuns
- Você cria uma classe
Loggere descobre que já existe outra com o mesmo nome no projeto. - Dois arquivos definem a mesma constante e o Ruby emite warning (dependendo de configuração).
- Fica difícil saber “de onde vem” uma classe ao ler o código.
Estratégias práticas
- Use namespaces por domínio:
Auth::Token,Payments::Invoice,Reports::Parser. - Evite constantes genéricas no topo: prefira
MyApp::VERSIONem vez deVERSION. - Seja explícito ao referenciar: use
SeuModulo::SuaClassepara deixar claro o contexto.
Padrão comum: include + extend no mesmo módulo
Às vezes você quer oferecer métodos de instância e também métodos de classe relacionados. Um padrão simples é separar em submódulos InstanceMethods e ClassMethods e usar um gancho included.
module Identifiable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def prefix
"ID"
end
end
def build_id(number)
"#{self.class.prefix}-#{number}"
end
end
class Order
include Identifiable
end
Order.prefix # => "ID"
Order.new.build_id(10) # => "ID-10"Esse padrão ajuda a manter a API organizada: o que é de classe fica em ClassMethods, o que é de instância fica no corpo principal do módulo.
Documentação mínima com comentários objetivos
Em módulos, comentários curtos e diretos costumam ser suficientes quando eles explicam responsabilidade e contrato (o que a classe precisa implementar, ou o que o método garante).
module CacheKey
# Responsabilidade: gerar uma chave estável para cache.
# Contrato: a classe deve expor #id.
def cache_key
"#{self.class.name}:#{id}"
end
endEvite comentários que apenas repitam o nome do método. Prefira indicar intenção, restrições e expectativas.