Na construção de aplicações web com Ruby on Rails, lidar com a complexidade dos formulários e dos dados recebidos pode rapidamente se tornar um desafio. Frequentemente, a tentação é incluir lógica de negócios diretamente nos modelos, o que pode levar a um código denso e de difícil manutenção. A solução? Form Objects.
Essas estruturas simples, mas poderosas, oferecem uma maneira elegante de manipular e validar formulários e parâmetros de acordo com as regras de negócios, mantendo essa complexidade afastada dos modelos. Neste post, vamos mergulhar na utilização de Form Objects para criar uma camada clara e eficiente de manipulação de dados, garantindo que apenas os dados corretamente validados e formatados cheguem aos nossos modelos.
Para isso vamos criar um projeto de reserva de eventos.
Contexto
Nosso sistema de reserva de eventos precisa lidar com dois tipos de eventos: aniversários (birthday
) e eventos corporativos (business
). Cada tipo de evento tem suas próprias regras de validação que são um pouco diferentes:
-
Eventos de Aniversário (
birthday
): O número de pessoas deve ser maior que 10. -
Eventos Corporativos (
business
): O número de pessoas deve ser maior que 5.
Essas regras são simples, mas ilustram bem como as necessidades de validação podem variar significativamente com base no contexto do evento.
Criando o projeto
Passo 1: Criar um Novo Projeto Rails
# ruby 3.2.2
# rails 7
rails new event_reservation_system --api
Passo 2: Navegar para o Diretório do Projeto
cd event_reservation_system
rails db:create
Passo 3: Gerar o Modelo Event
rails generate model Event title:string description:text event_type:string number_of_people:integer special_requests:text
Passo 4: Executar as Migrações
rails db:migrate
Passo 5: Gerar o Controller de Events
Agora, crie um controller para os eventos. Este controller será usado para lidar com as requisições HTTP para criar, ler, atualizar e deletar eventos:
rails generate controller api/v1/events
Passo 6: Definir Rotas
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :events
end
end
end
No coração do nosso projeto event_reservation_system
, estamos explorando uma abordagem elegante e eficiente para lidar com validações condicionais em Ruby on Rails usando Form Objects. Especificamente, vamos demonstrar como diferentes tipos de eventos podem ter diferentes regras de validação, e como os Form Objects tornam esse processo mais gerenciável e limpo.
Uso de Form Objects
Poderíamos implementar essas validações diretamente nos modelos, mas isso rapidamente tornaria os modelos complexos e difíceis de manter, especialmente à medida que as regras de negócios se tornam mais complexas. Em vez disso, vamos usar Form Objects.
Com os Form Objects, podemos encapsular a lógica de validação específica de cada tipo de evento em um objeto separado. Isso não apenas mantém nosso modelo Event
limpo e focado, mas também nos permite gerenciar a complexidade de maneira mais eficiente.
Demonstração no Código
# frozen_string_literal: true
module Events
class EventFormBase
include ActiveModel::Model
attr_accessor :title, :description, :event_type, :number_of_people, :special_requests
validates :title, :event_type, :number_of_people, presence: true
def attributes
{
title:,
description:,
event_type:,
number_of_people:,
special_requests:
}
end
end
end
# frozen_string_literal: true
module Events
class EventBusinessForm < Events::EventFormBase
validates :number_of_people, numericality: { greater_than: 5 }
end
end
# frozen_string_literal: true
module Events
class EventBirthdayForm < Events::EventFormBase
validates :number_of_people, numericality: { greater_than: 10 }
end
end
Neste exemplo, desenvolvemos um Form Object base e, a partir dele, criamos dois Form Objects especializados: um para eventos do tipo 'birthday' (aniversário) e outro para 'business' (negócios).
O nosso modelo é configurado como exemplo abaixo, e é importante lembrar que ainda é possível incluir validações no próprio modelo. No entanto, estas tendem a se concentrar mais em aspectos relacionados à integridade dos dados no banco de dados, em vez de regras de negócios específicas. (Neste exemplo nāo adicionei nenhuma validação)
class Event < ApplicationRecord
enum event_type: { business: 'business', birthday: 'birthday' }
end
E o controller:
Lembre-se que este controller deveria ser mais skinny
, o foco aqui é falar do form object
# frozen_string_literal: true
module Api
module V1
class EventsController < ApplicationController
class InvalidEventTypeError < StandardError; end
INVALID_EVENT_TYPE_MSG = 'Invalid event type'
def create
form = build_event_form(event_params)
return render json: form.errors, status: :unprocessable_entity unless form.valid?
create_event(form.attributes)
rescue InvalidEventTypeError => e
render json: { error: e.message }, status: :bad_request
end
private
def create_event(attributes)
event = Event.new(attributes)
if event.save
render json: event, status: :created
else
render json: event.errors, status: :unprocessable_entity
end
end
def build_event_form(params)
case params[:event_type]
when Event.event_types[:birthday]
Events::EventBirthdayForm.new(event_params)
when Event.event_types[:business]
Events::EventBusinessForm.new(event_params)
else
raise InvalidEventTypeError, INVALID_EVENT_TYPE_MSG
end
end
def event_params
params.require(:event).permit(:title, :description, :event_type, :number_of_people, :special_requests)
end
end
end
end
Neste exemplo, no nosso controller, o método build_event_form
desempenha um papel crucial. Você notará que, com base no valor de params[:event_type]
, ele direciona para um Form Object diferente. Essa abordagem facilita a escalabilidade do nosso sistema para acomodar diferentes tipos de eventos e novas regras de validação com grande facilidade.
Uma observação importante: a estrutura atual deste método, utilizando um case
, é bastante direta. No entanto, em um cenário mais complexo ou para uma maior escalabilidade, poderíamos considerar implementar um padrão de design como o Factory. Isso será explorado em detalhes em um futuro post.
No método create do nosso controller, temos uma lógica que primeiro constrói o Form Object usando build_event_form e verifica sua validade. Se o formulário for válido, prosseguimos com a criação do evento. É interessante notar que algumas pessoas preferem encapsular a criação do evento diretamente dentro do próprio Form Object. Essa é uma abordagem viável e pode oferecer uma maior encapsulação da lógica de negócios. Abaixo, você encontra um exemplo de como isso pode ser feito.
Exemplo no Controller
def create
form = build_event_form(event_params)
return render json: form.errors, status: :unprocessable_entity unless form.valid?
event = form.create
render json: event, status: :created
rescue InvalidEventTypeError => e
render json: { error: e.message }, status: :bad_request
end
**Implementação no EventFormBase
# frozen_string_literal: true
module Events
class EventFormBase
include ActiveModel::Model
attr_accessor :title, :description, :event_type, :number_of_people, :special_requests
validates :title, :event_type, :number_of_people, presence: true
def attributes
{
title:,
description:,
event_type:,
number_of_people:,
special_requests:
}
end
def create
Event.create!(attributes)
end
end
end
Nesta abordagem, o método create no EventFormBase é responsável por criar o evento, tornando o controller mais enxuto e delegando a responsabilidade de criação do evento para o Form Object.
Para facilitar o entendimento, incluí alguns exemplos do Postman/Insomnia. Nestes exemplos, você encontrará demonstrações das mensagens de validação e também dos casos de sucesso para cada tipo de solicitação. Isso deve ajudar a visualizar melhor como cada cenário é tratado e as respostas correspondentes.
Birthday
Url: http://localhost:3000/api/v1/events/
Caso com validation
Espero que vocês tenham achado este mergulho nos Form Objects no Ruby on Rails útil. Se quiserem ver mais detalhes sobre o projeto event_reservation_system, incluindo a implementação completa e outros recursos interessantes, confiram o repositório no meu GitHub: https://github.com/rodrigonbarreto/event_reservation_system
Ainda temos muito a explorar neste tópico, então fiquem atentos para mais posts sobre Rails e outras técnicas e padrões de desenvolvimento. Até a próxima!
Top comments (5)
Eu achei este artigo extremamente informativo e útil. Ele explica o conceito de forma clara e também mostra como aplicá-lo na prática, o que é incrivelmente valioso. A mistura de teoria com exemplos práticos realment ajuda a entender como podemos aplicar o conceito. Otimo trabalho @rodrigonbarreto_86, aguardo os proximos posts.
Muito interessante a abordagem, Rodrigo! Assim conseguimos deixar nossos models menos poluídos.
Qual é a tua opinião sobre passar a lógica de selecionar o tipo de EventForm para o EventFormBase ao invés de ter isso no controller?
Ansiosa pelos próximos posts!
Ola Lorenna!
A ideia é que tenha validaçǎo sim nos modelos, porem a validaçǎo do modelo nāo é para regra de negocio mas sim paras as regras do banco de dados.
no caso para lógica de selecionar o tipo de EventForm, da uma olhadinha no meu outro post.
dev.to/rodrigonbarreto_86/evoluind...
Muito bom conteúdo, no aguardo dos próximos posts.
Excelente!