Rails 4.1 : la méthode "enum" d'ActiveRecord

Publié il y a plus de 2 ans
Clément Joubert
Votre formateur
Clément Joubert

Développeur web spécialisé Ruby on Rails, conseil en UX / UI / webmarketing.

Où nous retrouver ?

Catégories : Développement

Rails 4.1 apporte la méthode "enum" à ActiveRecord, pour enregistrer une valeur textuelle sous forme d'entier dans votre base de données. A la clé un code concis et particulièrement explicite associés à d'excellentes performances au niveau de la base de données.

Ce screencast vous présente cet outil par l'exemple, et vous guide au travers des différentes méthodes procurées par "enum".

Afficher le transcript complet de la vidéo

ActiveRecord enums

Rails 4.1 apporte des solutions élégantes et rapides à des problèmes fréquemment rencontrés lors du développement. La méthode enum d'ActiveRecord 4.1 est l'une d'entre elle.

Avant, il fallait coder manuellement

Vous avez peut-être déjà codé de quoi gérer, plus ou moins élégamment, un attribut à plusieurs états. Par exemple, pour une commande on peut définir que son statut a 3 états possibles : "en cours", "terminée" ou "annulée".

Avant Rails 4.1 on aurait pu utiliser plusieurs attributs booléen pour l'état : un attribut "annulée" qui peut être vrai ou faux, un autre pour "terminée", etc. Ce n'est ni élégant, ni très logique. On préférait en général utiliser un seul attribut texte dont la valeur varie pour chaque état : "en cours", "terminée" et "annulée" par exemple. Ce n'était pas optimal côté utilisation de la base de données, ni très performant en général. On a donc jusqu'à présent utilisé un nombre associé à chaque état : 0 pour "en cours", 1 pour "terminée", 2 pour "annulée". Il fallait ajouter à cela des validations côté Rails, des méthodes pour accéder à la valeur, des méthodes booléennes pour demander à une commande si elle est terminée ou non, des méthodes pour attribuer un nouvel état, et éventuellement des scopes pour demander à ActiveRecord de retourner toutes les commandes "en cours", par exemple.

La méthode "enum"

ActiveRecord 4.1 met à disposition la méthode "enum" qui évite ces lourdeurs et répétitions.

On commence par créer un champ dans la base de données avec une migration :

rails generate migration add_state_to_order state:integer:index 

Au passage, on oublie pas de spécifier "index" pour pouvoir filtrer de manière performante nos enregistrements.

Ensuite il suffit d'ajouter une ligne notre modèle ActiveRecord :

class Order << ActiveRecord::Base
    enum state: [:in_progress, :completed, :canceled]
end

ActiveRecord nous met à disposition une méthode du nom de l'attribut pour obtenir son état :

order = Order.new
order.state
# => nil

On peut attribuer un nouvel état très simplement :

order.state = :canceled
order.canceled?
# => true

Mais aussi une méthode du nom de chaque état possible, retournant une valeur booléenne :

order.completed?
# => false

On peut même changer la valeur et enregistrer les modifications en tapant le nom de l'état suivi d'un point d'exclamation :

order.completed!

Des scopes pour filtrer automatiquement

Enfin, des scope ActiveRecord sont automatiquement créés pour chaque état :

Order.canceled
Order.completed
Order.in_progress

On peut les utiliser pour créer, par exemple, une nouvelle commande avec l'état en cours :

Order.in_progress.create

Si l'on désire une valeur par défaut, il suffit de le spécifier directement dans la base de données : dans notre cas, la première valeur (en cours) se trouve en première position, soit la valeur 0.

On créée la migration :

rails generate migration add_default_value_to_order_state

On rajoute le paramètre default égal à 0 :

change_column :order, :status, :integer, default: 0

On lance la migration :

rails db:migrate

Désormais la valeur par défaut - la première de la liste des valeurs possibles - est utilisée :

order = Order.new
order.state
# => "in_progress"

Ne jamais changer l'ordre des valeurs

Vérifiez bien à ne pas changer l'ordre des valeurs définies pour votre attribut. Rails utilise cet ordre pour y associer les différentes valeurs possibles. Pour une commande "annulée", par exemple, c'est la valeur 2 qui est enregistrée dans la base de données (seconde position dans la liste des valeurs possibles) :

order = Order.create(state: :canceled)
order.state
# => "canceled"
order.state_before_type_cast
# => 2

Si vous n'ajoutez pas votre nouvelle valeur à la fin de la liste, cela va décaler tous les états.

enum state: [:in_progress, :delivered, :completed, :canceled]

La commande précédemment "annulée" est devenue "terminée" (nouvelle valeur en troisième position).