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
| Arquivo | Responsabilidade |
|---|---|
lib/cpfhub.rb | Ponto de entrada, configuração global |
lib/cpfhub/client.rb | Cliente HTTP principal |
lib/cpfhub/configuration.rb | Classe de configuração |
lib/cpfhub/errors.rb | Hierarquia de exceções |
lib/cpfhub/response.rb | Wrapper para respostas da API |
lib/cpfhub/version.rb | Versã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.
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.



