Ruby on Rails: Enum

Tutoriais - 03/Jun/2020 - por André Kanamura

Os ActiveRecord Enums são uma ferramenta muito interessante para lidar com dados no nosso banco, já que possuem a flexibilidade e legibilidade de strings e a eficiência de integers nas consultas aos banco. Além disso, como estamos trabalhando com Ruby on Rails, o framework automaticamente cria métodos muito úteis que facilitam nosso trabalho. Essas afirmações ficarão mais claras até o final deste artigo.

Vamos utilizar um exemplo semelhante ao descrito na documentação oficial do Ruby on Rails sobre enum. Imagine uma aplicação de um blog em que temos publicações em situações diferentes publicadas, rascunhos e arquivadas. Uma forma de resolver esse problema seria criar um atributo boolean para cada tipo e modificar para verdadeiro ou falso dependendo da situação. Mas isso cria uma série de problemas, como por exemplo termos que criar métodos para cada tipo e ainda lidar com mudanças múltiplas de estados. Não faz sentido uma publicação estar publicada e ser um rascunho ao mesmo tempo, por exemplo.

Para o nosso contexto, uma maneira muito simples de resolver esse problema é utilizar um Enum. Vamos fazer um passo a passo para isso, começando pela criação da migration que adiciona o atributo status ao model Article:

$ rails generate migration add_status_to_publication status:integer

Caso o model ainda não tenha sido criado, basta incluir o atributo no comando gerador do model: rails generate model publication status:integer.

Mas antes de rodar o rails db:migrate, vamos adicionar mais um passo, editando o arquivo de migration gerado adicionando default: 0. No nosso exemplo ficaria assim:

class AddStatusToPublication < ActiveRecord::Migration[6.0]
  def change 
    add_column :publication, :status, :integer, default: 0
  end

Isso adiciona uma opção padrão para quando não estipulamos um status na criação de um objeto. Agora podemos rodar rails db:migrate. Então, se criar um article com Article.new() sem indicação de status, por padrão ele será 0. Vamos ver melhor como isso funciona em breve.

No arquivo do model article.rb vamos implementar o status como um enum:

class Publication < ApplicationRecord
  enum status: { draft: 0, published: 1, archived: 2 }
end

Agora, quando criamos um Article, podemos atribuir um status a ele:

  publication = Publication.new(status: :archived)
  other_publication = Publication.new(status: 10)
  another_publication = Publication.new()

  publication.status
  # => "archived"

  other_publication.status
  # => "published"

  another_publication.status
  # => "draft"

Podemos configurar o status usando symbol ou integer e, quando nenhum valor é atribuído, o padrão que incluímos na migration foi automaticamente atribuído.

Uma vantagem de usarmos enums é que ganhamos métodos que facilitam muito nosso trabalho. Por exemplo, aproveitando o bloco de código anterior:

    publication = Publication.new(status: :archived)

    publication.archived?
    # => true

    publication.draft?
    # => false

    publication.draft!
    publication.status
    # => "draft" 

Cada status ganhou um método com ! e ?. O método draft!, por exemplo, altera o status da publicação e salva o objeto. Já o draft?, verifica se o status do objeto é :draft e retorna verdadeiro ou falso.

Os enums também nos permitem fazer queries caso esses métodos não atendam às necessidades:

Publication.where(status: [:draft, :archived])
Publication.where.not(status: :published)

Um cuidado importante é que não devemos usar o mesmo nome para diferentes enums:

class Product < ApplicationRecord
  enum status: { delivered: 0, in_stock: 10 }
  enum stock: { out_of_stock: 0, in_stock: 10 }
end

Isso vai gerar erros no ActiveRecord.

É possível também construir os enums na forma de arrays:

class Publication < ApplicationRecord
  enum status: [:draft, :published, :archived]
end

A desvantagem dessa implementação é que você perde a flexibilidade de hashes.

Conclusão

Finalmente, agora deve ser fácil de compreender como os Active Record Enums possuem a legibilidade de strings e a eficiência de integers nas consultas ao banco de dados, já que mapeiam valores como integers, mas também podem responder a queries como hash. Por isso, seus maiores benefícios são na performance do sistema e na legibilidade do código que se torna muito fácil de compreender. :)

Referências

Foto de perfil do autor
André Kanamura

Dev na Campus Code