Ruby do Zero: Módulos — Namespaces, Mixins e Métodos de Módulo

Capítulo 16

Tempo estimado de leitura: 8 minutos

+ Exercício

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.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

module AppConfig
  DEFAULT_TIMEZONE = "America/Sao_Paulo"
  MAX_RETRIES = 3
end

AppConfig::MAX_RETRIES

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

Passo 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

ObjetivoUseResultado
Compartilhar comportamento entre instânciasincludeMétodos viram métodos de instância
Compartilhar utilitários como “métodos de classe”extendMé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) # => 10

Observaçã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? e errors
  • 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? e errors sã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.5

Como manter responsabilidades claras

  • Não misture temas: texto em Utils::Text, números em Utils::Numbers.
  • Evite “módulo Deus”: se Utils começ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 Logger e 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::VERSION em vez de VERSION.
  • Seja explícito ao referenciar: use SeuModulo::SuaClasse para 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
end

Evite comentários que apenas repitam o nome do método. Prefira indicar intenção, restrições e expectativas.

Agora responda o exercício sobre o conteúdo:

Em Ruby, qual é a diferença prática entre usar include e extend ao aplicar um módulo a uma classe?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

include mistura o módulo na cadeia de instâncias da classe, tornando seus métodos disponíveis em objetos. extend adiciona os métodos diretamente ao receptor; em uma classe, isso cria métodos de classe.

Próximo capitúlo

Ruby do Zero: Classes e Objetos — Estado, Comportamento e Encapsulamento

Arrow Right Icon
Capa do Ebook gratuito Ruby do Zero: Fundamentos, Coleções, Blocos e Organização de Código
76%

Ruby do Zero: Fundamentos, Coleções, Blocos e Organização de Código

Novo curso

21 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.