Indo além do ActiveModel

Explorando caminhos para extrair regras de negócio em POROs

Ruby on Rails é um framework web poderoso e flexível e na maioria de projetos, é possível construir aplicações robustas apenas com o que o framework oferece. No entanto, há momentos em que precisamos ir além do que o Rails oferece por padrão, e pensar fora da caixa.

Uma maneira simples e elegante de fazer isso é utilizando POROs (Plain Old Ruby Objects) para encapsular regras de negócio, validações e outras lógicas que não se encaixam perfeitamente em modelos do ActiveRecord ou controladores.

class PersonForm
  attr_accessor :age, :name, :birthday, :favorit_color

  def initialize(params = {})
    @age = params[:age]
    @name = params[:name]
    @birthday = params[:birthday]
    @favorit_color = params[:favorit_color]
  end

  def call!
    # Do some cool stuff
  end
end

person = PersonForm.new(age: "92", name: "Buzz Aldrin", birthday: "20-01-1930")

person.age
# => "92"

person.birthday
# => "20-01-2930"

person.favorit_color
# => nil

Com isso, já seria possível isolar coisas como: validações personalizadas; definir atributos padrões; normalizar parâmetros e etc.

Agora, é comum que você queira que o age seja um número inteiro e o birthday seja um objeto do tipo Date. Para isso, você pode fazer o cast dos dados manualmente:

require 'date'

class PersonForm
  attr_accessor :age, :name, :birthday, :favorit_color

  def initialize(params = {})
    @age = params[:age].to_i
    @name = params[:name]
    @birthday = Date.parse(params[:birthday])
    @favorit_color = params[:favorit_color]&.strip || 'red'
  end

  def call!
    # Do some cool stuff
  end
end

person = PersonForm.new(age: '92', name: ' Buzz Aldrin ', birthday: '20-01-1930')

person.age
# => 92

person.birthday
# => <Date: 1930-01-20 ((2425997j,0s,0n),+0s,2299161j)>

person.favorit_color
# => "red"

Também é comum que você queira validar os dados, por exemplo, garantir que o name não seja nil ou vazio. Para isso, você pode irá precisar adicionar métodos de validação, definir um formato de errors e aí que o ActiveModel pode te ajudar.

ActiveModel::Attributes

Se você está em um projeto Ruby on Rails, provavelmente, já tem o AvtiveModel::Attributes disponível para usar. Ele permite que você defina atributos com tipos específicos e validações out-of-the-box, tornando o processo de criação de POROs mais simples.

Uma maneira de fazer isso é incluindo o módulo ActiveModel::Model e ActiveModel::Attributes no seu PORO. Isso lhe dará acesso a funcionalidades como validações, atributos “tipados” e irá entregar uma estrutura que provavelmente você já está acostumado a trabalhar.

class PersonForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  validates :name, presence: true

  attribute :age, :integer
  attribute :name, :string
  attribute :birthday, :date, default: -> { Date.new }
  attribute :favorit_color, :string, default: 'red'

  def call!
    # Do some cool stuff
  end
end

person = PersonForm.new(age: '92', name: 'Buzz Aldrin', birthday: '20-01-1930')

person.age
# => 92

person.birthday
# => <Date: 1930-01-20 ((2425997j,0s,0n),+0s,2299161j)>

person.favorit_color
# => "red"

person = PersonForm.new(age: '92', name: nil, birthday: '20-01-1930')

person.valid?
# => false

person.errors.full_messages
# => ["Name can't be blank"]

Como pode ver, a gente ganha métodos como valid? e errors para validar os dados e obter mensagens de erro, além de poder definir atributos com tipos específicos, como :integer, :string, :date, etc.

Tem um framework no meu framework

Amarrar toda sua codebase em bibliotecas externas também é algo perigoso. O Ruby on Rails já entrega uma gama extraordinária de implementações de alto nível e mantido por desenvolvedores muito experientes, provavelmente, você já tem tudo o que precisa para resolver a maioria dos problemas comuns.

Fique atento para não criar uma sobrecarga desnecessária e transformar algo simples em algo complexo.

  • Faça funcionar primeiro Foque no simples, faça funcionar primeiro. Algo importante, e que é necessário ficar atento, é que não há problemas em, inicialmente, manter duplicidade de código. Lembre-se de que abstrações prematuras, ou mal elaboradas, podem acabar se transformando em armadilhas.

Conclusão

Embora seja tentador adicionar gemas e gemas é importante lembrar que a simplicidade é muitas vezes a chave. Se concentrar no negócio e pensar de forma simples, podemos evitar estar cavando um buraco mais fundo do que o necessário.

Referências