Como Criar uma Gem Ruby para Encapsular Chamadas à API de CPF

Aprenda a criar uma gem Ruby para encapsular chamadas à API de CPF, com configuração, tratamento de erros, testes e publicação no RubyGems.

Redação CPFHub.io
Redação CPFHub.io
··7 min de leitura
Como Criar uma Gem Ruby para Encapsular Chamadas à API de CPF

Criar uma gem Ruby para encapsular chamadas à API de CPF centraliza a lógica de integração em um único lugar, padroniza o tratamento de erros e permite que qualquer aplicação da organização consulte o endpoint https://api.cpfhub.io/cpf/{CPF} com uma única linha no Gemfile. O processo envolve configurar o cliente Faraday, definir a hierarquia de exceções e cobrir os cenários com RSpec e WebMock.

Introdução

Quando múltiplas aplicações Ruby da sua organização consomem a mesma API de CPF, encapsular as chamadas em uma gem centraliza a lógica de integração, padroniza o tratamento de erros e facilita a manutenção. Uma gem bem construída permite que qualquer desenvolvedor integre a API com uma única linha no Gemfile.


Estrutura da gem

Crie a estrutura básica da gem usando o Bundler.

# Estrutura de diretórios
# cpfhub/
# lib/
# cpfhub/
# client.rb
# configuration.rb
# errors.rb
# response.rb
# version.rb
# cpfhub.rb
# spec/
# cpfhub/
# client_spec.rb
# spec_helper.rb
# cpfhub.gemspec
# Gemfile
# README.md

# cpfhub.gemspec
Gem::Specification.new do |spec|
    spec.name = "cpfhub"
    spec.version = CpfHub::VERSION
    spec.authors = ["Seu Nome"]
    spec.email = ["seu@email.com"]
    spec.summary = "Cliente Ruby para a API do CPFHub"
    spec.description = "Gem para consultar e validar CPFs via API do CPFHub"
    spec.homepage = "https://github.com/seu-usuario/cpfhub-ruby"
    spec.license = "MIT"
    spec.required_ruby_version = ">= 3.0"

    spec.add_dependency "faraday", "~> 2.0"
    spec.add_dependency "faraday-retry", "~> 2.0"

    spec.add_development_dependency "rspec", "~> 3.12"
    spec.add_development_dependency "webmock", "~> 3.18"
end
ArquivoResponsabilidade
lib/cpfhub.rbPonto de entrada, configuração global
lib/cpfhub/client.rbCliente HTTP principal
lib/cpfhub/configuration.rbClasse de configuração
lib/cpfhub/errors.rbHierarquia de exceções
lib/cpfhub/response.rbWrapper para respostas da API
lib/cpfhub/version.rbVersão da gem

Módulo principal e configuração

O módulo principal expõe a configuração global e um método de conveniência para consultas.

# lib/cpfhub/version.rb
module CpfHub
    VERSION = "1.0.0"
end

# lib/cpfhub/configuration.rb
module CpfHub
    class Configuration
    attr_accessor :api_key, :base_url, :timeout, :open_timeout,
    :max_retries, :retry_delay, :logger

    def initialize
    @api_key = ENV["CPFHUB_API_KEY"]
    @base_url = "https://api.cpfhub.io"
    @timeout = 10
    @open_timeout = 5
    @max_retries = 3
    @retry_delay = 1
    @logger = Logger.new($stdout)
    end
    end
end

# lib/cpfhub.rb
require "cpfhub/version"
require "cpfhub/configuration"
require "cpfhub/errors"
require "cpfhub/response"
require "cpfhub/client"

module CpfHub
    class << self
    attr_accessor :configuration

    def configure
    self.configuration ||= Configuration.new
    yield(configuration)
    end

    def client
    @client ||= Client.new
    end

    def consultar(cpf)
    client.consultar(cpf)
    end

    def reset!
    @client = nil
    @configuration = nil
    end
    end
end

Cliente HTTP e tratamento de erros

O cliente encapsula toda a comunicação com a API.

# lib/cpfhub/errors.rb
module CpfHub
    class Error < StandardError; end
    class ConfigurationError < Error; end
    class InvalidCpfError < Error; end
    class AuthenticationError < Error; end
    class NotFoundError < Error; end
    class RateLimitError < Error; end
    class ServerError < Error; end
    class TimeoutError < Error; end
end

# lib/cpfhub/response.rb
module CpfHub
    class Response
    attr_reader :cpf, :name, :name_upper, :gender,
    :birth_date, :day, :month, :year, :raw

    def initialize(data)
    @raw = data
    @cpf = data["cpf"]
    @name = data["name"]
    @name_upper = data["nameUpper"]
    @gender = data["gender"]
    @birth_date = data["birthDate"]
    @day = data["day"]
    @month = data["month"]
    @year = data["year"]
    end

    def masculino?
    gender == "M"
    end

    def feminino?
    gender == "F"
    end

    def to_h
    raw
    end
    end
end

# lib/cpfhub/client.rb
require "faraday"
require "faraday-retry"
require "json"

module CpfHub
    class Client
    def initialize(config = nil)
    @config = config || CpfHub.configuration || Configuration.new
    validate_config!
    @connection = build_connection
    end

    def consultar(cpf)
    cpf_limpo = sanitizar_cpf(cpf)
    validar_cpf!(cpf_limpo)

    resposta = @connection.get("/cpf/#{cpf_limpo}")
    tratar_resposta(resposta)
    rescue Faraday::TimeoutError
    raise CpfHub::TimeoutError, "Timeout ao consultar CPF"
    rescue Faraday::ConnectionFailed => e
    raise CpfHub::Error, "Falha de conexao: #{e.message}"
    end

    private

    def validate_config!
    if @config.api_key.nil? || @config.api_key.empty?
    raise ConfigurationError,
    "API key nao configurada. Use CpfHub.configure"
    end
    end

    def build_connection
    Faraday.new(url: @config.base_url) do |conn|
    conn.request :retry, max: @config.max_retries,
    interval: @config.retry_delay,
    backoff_factor: 2,
    exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
    conn.headers["x-api-key"] = @config.api_key
    conn.headers["Content-Type"] = "application/json"
    conn.headers["User-Agent"] = "cpfhub-ruby/#{VERSION}"
    conn.options.timeout = @config.timeout
    conn.options.open_timeout = @config.open_timeout
    conn.adapter Faraday.default_adapter
    end
    end

    def sanitizar_cpf(cpf)
    cpf.to_s.gsub(/\D/, "")
    end

    def validar_cpf!(cpf)
    unless cpf.match?(/\A\d{11}\z/)
    raise InvalidCpfError, "CPF deve conter 11 digitos"
    end
    end

    def tratar_resposta(resposta)
    case resposta.status
    when 200
    body = JSON.parse(resposta.body)
    if body["success"]
    Response.new(body["data"])
    else
    raise NotFoundError, "CPF nao encontrado"
    end
    when 401
    raise AuthenticationError, "API key invalida"
    when 429
    raise RateLimitError, "Rate limit excedido"
    when 500..599
    raise ServerError, "Erro no servidor (#{resposta.status})"
    else
    raise Error, "Resposta inesperada (#{resposta.status})"
    end
    end
    end
end

Testes com RSpec e WebMock

Testes garantem que a gem funciona corretamente em todos os cenários.

# spec/spec_helper.rb
require "webmock/rspec"
require "cpfhub"

RSpec.configure do |config|
    config.before(:each) do
    CpfHub.configure do |c|
    c.api_key = "test-api-key"
    end
    end

    config.after(:each) do
    CpfHub.reset!
    end
end

# spec/cpfhub/client_spec.rb
RSpec.describe CpfHub::Client do
    let(:client) { described_class.new }
    let(:cpf) { "12345678901" }

    describe "#consultar" do
    context "quando o CPF e encontrado" do
    before do
    stub_request(:get, "https://api.cpfhub.io/cpf/#{cpf}")
    .with(headers: { "x-api-key" => "test-api-key" })
    .to_return(
    status: 200,
    body: {
    success: true,
    data: {
    cpf: cpf, name: "Joao da Silva",
    nameUpper: "JOAO DA SILVA", gender: "M",
    birthDate: "1990-01-15", day: 15, month: 1, year: 1990
    }
    }.to_json
    )
    end

    it "retorna um Response com os dados" do
    resultado = client.consultar(cpf)
    expect(resultado).to be_a(CpfHub::Response)
    expect(resultado.name).to eq("Joao da Silva")
    expect(resultado.gender).to eq("M")
    expect(resultado.masculino?).to be true
    end
    end

    context "quando o CPF e invalido" do
    it "levanta InvalidCpfError" do
    expect { client.consultar("123") }
    .to raise_error(CpfHub::InvalidCpfError)
    end
    end

    context "quando a API retorna 429" do
    before do
    stub_request(:get, "https://api.cpfhub.io/cpf/#{cpf}")
    .to_return(status: 429)
    end

    it "levanta RateLimitError" do
    expect { client.consultar(cpf) }
    .to raise_error(CpfHub::RateLimitError)
    end
    end
    end
end

Uso da gem

Com a gem publicada, a integração em qualquer aplicação Ruby é simples.

# Gemfile
gem "cpfhub"

# config/initializers/cpfhub.rb (Rails)
CpfHub.configure do |config|
    config.api_key = ENV["CPFHUB_API_KEY"]
    config.timeout = 15
    config.max_retries = 3
end

# Uso em qualquer parte da aplicação
resultado = CpfHub.consultar("123.456.789-01")
puts resultado.name # => "Joao da Silva"
puts resultado.gender # => "M"
puts resultado.birth_date # => "1990-01-15"
puts resultado.masculino? # => true

Perguntas frequentes

Como a gem lida com o excedente de consultas no plano gratuito da CPFHub.io?

A API CPFHub.io não bloqueia requisições ao atingir o limite de 50 consultas mensais do plano gratuito — ela cobra R$0,15 por consulta adicional. A gem não precisa de nenhuma lógica especial para isso, mas é boa prática monitorar o consumo pelo dashboard e implementar um contador local para evitar cobranças inesperadas.

É possível usar a gem em projetos Rails com múltiplos workers em paralelo?

Sim. A gem é thread-safe desde que cada instância de CpfHub::Client seja criada com sua própria configuração, ou que a configuração global seja definida em um initializer antes do fork dos workers. Use CpfHub.configure no config/initializers/cpfhub.rb para garantir que a api_key seja carregada antes do Puma ou Sidekiq iniciarem.

Como testar a gem sem consumir consultas reais da API?

Use WebMock para interceptar as requisições HTTP nos testes. O exemplo com stub_request mostrado neste artigo permite simular respostas de sucesso, CPF não encontrado, timeout e rate limit sem fazer nenhuma chamada real à API CPFHub.io. Para testes de integração pontuais, o OWASP Testing Guide recomenda ambientes de staging isolados.

Como publicar a gem no RubyGems após o desenvolvimento?

Execute gem build cpfhub.gemspec para gerar o arquivo .gem, depois gem push cpfhub-1.0.0.gem com sua conta autenticada no RubyGems. Lembre-se de incrementar a versão em lib/cpfhub/version.rb a cada release e manter um CHANGELOG documentando as mudanças.


Conclusão

Criar uma gem Ruby para encapsular chamadas à API de CPF centraliza a lógica de integração, padroniza o tratamento de erros e simplifica a manutenção. Com configuração flexível, tratamento robusto de exceções e testes abrangentes, a gem permite que qualquer aplicação Ruby da organização integre a API de CPF com uma única dependência.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a construir sua gem Ruby com acesso completo ao endpoint de consulta de CPF da CPFHub.io.

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp