Uma carta de amor ao Ruby on Rails
Há algumas semanas, resolvi sair da minha zona de conforto e aprender Ruby on Rails. Vindo de um background em Java, a experiência foi… interessante, para dizer o mínimo. Ruby é uma linguagem completamente diferente. Não é só sintaxe diferente, é uma forma diferente de pensar sobre programação.
A ideia era criar um projeto que me forçasse a pensar diferente. Algo que não fosse só mais um CRUD básico, mas que tivesse regras de negócio complexas e interessantes. Foi aí que pensei em um sistema de apuração tributária - um sistema para calcular o DAS (Documento de Arrecadação do Simples Nacional) mensalmente.
O que me atraiu nesse projeto foi justamente o fato de lidar com regras de negócio bem diferentes do que estou acostumado. Em vez de sistemas de e-commerce ou CRUDs administrativos, aqui eu teria que pensar em imutabilidade fiscal, cálculos determinísticos, e garantir que uma vez que uma nota fiscal fosse emitida, ela não pudesse mais ser alterada. É um tipo de problema que força você a pensar na modelagem de dados de forma diferente.
O Projeto
O Ruby Fiscal Project é um MVP de um sistema de apuração tributária. A ideia é simples: você cadastra notas fiscais, cria apurações mensais, e o sistema calcula automaticamente o DAS baseado nas notas de saída do período.
Mas o que realmente me interessou foi a oportunidade de trabalhar com regras de negócio completamente diferentes do que estou acostumado. Sistemas fiscais têm características únicas: dados imutáveis, cálculos que precisam ser determinísticos, e uma série de validações que não existem em outros tipos de sistemas.
É o tipo de projeto que te força a pensar em modelagem de dados de forma diferente. Não é só criar tabelas e relacionamentos. É pensar em como garantir que uma nota fiscal, uma vez criada, não possa ser alterada. É pensar em como garantir que os cálculos sejam sempre os mesmos, independente de quando são executados.
Ruby: Uma Forma Diferente de Pensar
Vindo do Java, a primeira coisa que me chamou atenção no Ruby foi a sintaxe. Tudo parece mais limpo, mais direto. Em Java, você escreve:
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
Em Ruby, a mesma coisa fica assim:
class UserService
def initialize(user_repository)
@user_repository = user_repository
end
def find_by_id(id)
@user_repository.find_by_id(id) || raise UserNotFoundError, "User not found"
end
end
É menos código, mas também é uma forma diferente de pensar. Ruby te força a ser mais expressivo, a escrever código que se lê quase como inglês. E isso, no começo, foi difícil. Eu estava acostumado a ser explícito sobre tudo - tipos, modificadores de acesso, exceções. Ruby confia mais em você, e isso foi um ajuste que precisei fazer.
Mas depois que você se acostuma, você percebe que Ruby é uma linguagem incrivelmente poderosa. A flexibilidade que ela oferece permite escrever código muito mais expressivo e legível.
Rails: As Facilidades Que Fazem Diferença
O que mais me impressionou no Rails foram as facilidades que ele traz. Coisas que em Java/Spring você precisa configurar manualmente, no Rails já vêm prontas ou são extremamente simples de implementar.
Active Record: ORM Que Faz Sentido
O Active Record do Rails é muito bom de se usar. Em vez de escrever queries SQL ou usar uma API complexa, você escreve código Ruby que se lê quase como português:
# Buscar todas as notas fiscais de saída de janeiro de 2024
notas = NotaFiscal.where(tipo: :saida)
.where("data_emissao >= ? AND data_emissao <= ?",
Date.new(2024, 1, 1),
Date.new(2024, 1, 31))
# Calcular a receita bruta
receita_bruta = notas.sum(:valor_base_calculo)
É intuitivo, é legível, e funciona. Não precisa de configuração complexa, não precisa de múltiplos arquivos XML ou anotações espalhadas. É só código Ruby.
Migrations: Versionamento de Banco de Dados
As migrations do Rails são geniais. Em vez de escrever SQL manualmente e ter que gerenciar scripts de atualização, você escreve código Ruby que descreve as mudanças:
class CreateNotasFiscais < ActiveRecord::Migration[7.0]
def change
create_table :notas_fiscais do |t|
t.string :numero, null: false
t.string :serie, null: false
t.date :data_emissao, null: false
t.decimal :valor_total, precision: 10, scale: 2, null: false
t.integer :tipo, null: false
t.string :cnpj_emitente, null: false
t.string :cnpj_destinatario
t.timestamps
end
add_index :notas_fiscais, [:numero, :serie], unique: true
end
end
E o Rails gerencia tudo. Você pode fazer rollback, pode ver o histórico, pode aplicar em qualquer ambiente. É simples e funciona.
Concerns: Reutilização de Código
Uma das coisas que mais usei no projeto foram concerns. Em vez de herança múltipla ou composição complexa, você cria módulos que podem ser incluídos onde necessário:
module Imutavel
extend ActiveSupport::Concern
included do
before_update :prevenir_alteracao, if: :imutavel?
end
def imutavel?
# Lógica específica de cada modelo
end
private
def prevenir_alteracao
errors.add(:base, "Este registro não pode ser alterado")
throw(:abort)
end
end
E então você simplesmente inclui no modelo:
class NotaFiscal < ApplicationRecord
include Imutavel
def imutavel?
true # Notas fiscais são sempre imutáveis
end
end
É elegante, é simples, e funciona perfeitamente para o que eu precisava.
Service Objects: Organizando Lógica Complexa
Para organizar a lógica de negócio, usei service objects. Em Rails, isso é simples - você cria classes em app/services e pronto:
module ApuracaoService
class Processar
def self.call(apuracao)
new(apuracao).call
end
def initialize(apuracao)
@apuracao = apuracao
end
def call
notas = buscar_notas_do_periodo
receita_bruta = calcular_receita_bruta(notas)
valor_das = calcular_das(receita_bruta)
@apuracao.update!(
receita_bruta: receita_bruta,
valor_das: valor_das
)
associar_notas(notas)
end
private
def buscar_notas_do_periodo
NotaFiscal.where(tipo: :saida)
.where("data_emissao >= ? AND data_emissao <= ?",
@apuracao.periodo.inicio,
@apuracao.periodo.fim)
end
def calcular_receita_bruta(notas)
notas.sum(:valor_base_calculo)
end
def calcular_das(receita_bruta)
DasCalculator.calcular(receita_bruta: receita_bruta)[:valor_das]
end
def associar_notas(notas)
notas.each { |nota| @apuracao.notas_fiscais << nota }
end
end
end
E para usar, é só chamar:
ApuracaoService::Processar.call(apuracao)
É limpo, é testável, e não precisa de injeção de dependência complexa. Ruby permite isso de forma natural.
Value Objects: Conceitos do Domínio
Para encapsular conceitos fiscais, criei value objects. Em Ruby, isso é simples - você cria classes normais:
module Fiscal
class Aliquota
FAIXAS = [
{ limite: 180_000, aliquota: 0.06 },
{ limite: 360_000, aliquota: 0.075 },
{ limite: 720_000, aliquota: 0.095 },
{ limite: Float::INFINITY, aliquota: 0.11 }
].freeze
attr_reader :valor
def initialize(valor)
@valor = valor
end
def self.para_receita(receita_bruta)
faixa = FAIXAS.find { |f| receita_bruta <= f[:limite] }
new(faixa[:aliquota])
end
def percentual
(valor * 100).round(2)
end
end
end
E usa assim:
aliquota = Fiscal::Aliquota.para_receita(200_000)
puts aliquota.percentual # => 7.5
É simples, é direto, e encapsula a lógica de negócio de forma clara.
O Projeto: Regras de Negócio Diferentes
O que mais me interessou nesse projeto foi trabalhar com regras de negócio completamente diferentes do que estou acostumado. Em sistemas fiscais, você precisa pensar em:
Imutabilidade: Uma nota fiscal, uma vez criada, não pode ser alterada. Não é uma questão de “seria legal”, é uma questão de “alterar isso pode gerar problemas com a Receita Federal”. No projeto, implementei isso através de um concern que previne updates:
class NotaFiscal < ApplicationRecord
include Imutavel
def imutavel?
true
end
end
Cálculos Determinísticos: O cálculo do DAS precisa ser sempre o mesmo para os mesmos inputs. Não pode depender de estado externo, não pode ter efeitos colaterais. Implementei isso através de um service puro:
class DasCalculator
def self.calcular(receita_bruta:)
aliquota = Fiscal::Aliquota.para_receita(receita_bruta)
valor_das = receita_bruta * aliquota.valor
{
receita_bruta: receita_bruta,
aliquota_percentual: aliquota.percentual,
valor_das: valor_das
}
end
end
É uma função pura - sempre retorna o mesmo resultado para os mesmos inputs. Isso facilita testes e garante consistência.
Validações Complexas: Uma apuração não pode ser criada para um período que já tem uma apuração finalizada. Essas validações precisam estar no modelo, e Rails facilita isso:
class Apuracao < ApplicationRecord
validates :mes, :ano, presence: true
validate :periodo_unico_se_finalizada
def periodo_unico_se_finalizada
if finalizada? && existe_apuracao_para_periodo?
errors.add(:base, "Já existe uma apuração finalizada para este período")
end
end
end
É simples, é direto, e funciona perfeitamente.
O Que Aprendi Sobre Ruby
-
Ruby é expressivo: A sintaxe permite escrever código que se lê quase como português. Isso torna o código mais legível e mais fácil de entender.
-
Ruby confia em você: Em Java, você precisa ser explícito sobre tudo - tipos, modificadores de acesso, exceções. Ruby confia que você sabe o que está fazendo, e isso permite escrever código mais conciso.
-
Ruby é flexível: A flexibilidade da linguagem permite implementar padrões de forma mais natural. Service objects, value objects, concerns - tudo isso flui de forma mais natural em Ruby.
-
Ruby tem uma comunidade incrível: A quantidade de gems disponíveis, a qualidade da documentação, e a comunidade em geral são impressionantes.
O Que Aprendi Sobre Rails
-
Rails facilita muito o desenvolvimento: Coisas que em Java/Spring você precisa configurar manualmente, no Rails já vêm prontas ou são extremamente simples.
-
Convention over Configuration funciona: Seguir as convenções do Rails acelera muito o desenvolvimento. Você não precisa decidir onde colocar cada arquivo, como nomear cada classe - o Rails já tem uma resposta.
-
Active Record é poderoso: O ORM do Rails é intuitivo e poderoso. Você escreve código Ruby e ele gera SQL otimizado. É simples e funciona.
-
Rails tem ferramentas para tudo: Migrations, concerns, service objects, value objects - tudo pode ser implementado de forma elegante sem precisar de gems externas.
Os Desafios de Aprender Ruby Vindo do Java
-
Pensar diferente: Java te força a ser explícito sobre tudo. Ruby te permite ser mais flexível, e isso foi um ajuste mental grande.
-
Sintaxe diferente: A sintaxe do Ruby é completamente diferente do Java. Blocos, símbolos, métodos sem parênteses - tudo isso foi novo.
-
Falta de tipos: Em Java, você sempre sabe o tipo de uma variável. Em Ruby, você precisa confiar na documentação ou no código. Isso foi difícil no começo.
-
Debugging diferente: As ferramentas de debugging são diferentes, e a forma de pensar sobre erros também é diferente.
Mas no final, valeu a pena. Ruby é uma linguagem incrível, e Rails é um framework que realmente facilita o desenvolvimento.
O Fim da História
Aprender Ruby vindo do Java foi uma experiência interessante. No começo, foi difícil pensar diferente. Eu estava acostumado a ser explícito sobre tudo, a ter tipos em todo lugar, a ter uma estrutura rígida. Ruby me forçou a pensar de forma mais flexível, e isso foi um ajuste.
Mas depois que você se acostuma, você percebe que Ruby é uma linguagem incrivelmente poderosa. A sintaxe é limpa, o código é expressivo, e a flexibilidade permite implementar padrões de forma mais natural.
E Rails… Rails é impressionante. As facilidades que ele traz, a forma como ele acelera o desenvolvimento, a quantidade de coisas que já vêm prontas - tudo isso faz diferença. É um framework que realmente facilita o desenvolvimento, sem sacrificar flexibilidade.
O projeto em si também foi interessante. Trabalhar com regras de negócio fiscais me forçou a pensar em modelagem de dados de forma diferente. Imutabilidade, cálculos determinísticos, validações complexas - tudo isso são conceitos que você não encontra em CRUDs básicos normalmente.
E no final das contas, foi uma experiência de aprendizado valiosa. Não só aprendi Ruby e Rails, mas também aprendi a pensar sobre problemas de forma diferente. E isso, talvez, seja o mais importante.
O projeto está disponível no GitHub: ruby-fiscal-project.
Fora isso… Obrgiado pela atenção, um abraço e até mais!
j