opsone
Contactez-nous contactcontact

  • 28 septembre 2017
  • Tech

Tuto. [Ruby On Rails] Structurez votre code métier avec des commandes

Alexandre, dev spécialisé Ruby On Rails, framework web libre appelé aussi RoR ou Rails, vous propose un tutoriel sur le concept de « commands ». Réutilisabilité, maintenabilité, simplicité à tester, structurer son code métier avec des « commands » présente de nombreux avantages.  Comment procéder ? Alexandre vous décrit les étapes pas à pas.

Bonjour à tous ! Je vais vous présenter dans cet article un concept de « commands » que nous ajoutons à nos projets Ruby On Rails, l’idée est d’extraire le code métier qui alourdit nos controllers et nos models pour l’isoler dans des commands.

Ce concept apporte trois principaux avantages :

  • Réutilisabilité : En refactorisant son code grâce à l’utilisation une même commande à plusieurs endroits.
  • Maintenabilité : Un avantage qui découle en toute logique du premier.
  • Simplicité à tester : Mettre en place vos tests unitaires devient un jeu d’enfant.

Les commands sont un concept que nous avons extrait et isolé de la gem Rectify dont nous saluons le travail effectué par son créateur. Cette gem apporte différents concepts et chacun d’entre eux peuvent être utilisés indépendamment (Form Objects, Commands, Presenters et Query Objects). Dans notre cas, nous n’allons pas utiliser la gem entière, nous allons simplement extraire la classe qui nous intéresse :

1
lib/rectify/command.rb

Afin de la ré-implémenter dans une nouvelle classe, dans notre projet rails :

1
app/commands/base_command.rb

Nous allons copier le contenu de la classe originale en changeant le module parent et le nom de la classe évidemment :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class BaseCommand

include Wisper::Publisher
  def self.call(*args, &block)
    command = new(*args)
    command.evaluate(&block) if block_given?
    command.call
  end

  def evaluate(&block)
    @caller = eval('self', block.binding)
    instance_eval(&block)
  end

  def transaction(&block)
    ActiveRecord::Base.transaction(&block) if block_given?
  end

  def method_missing(method_name, *args, &block)
    if @caller.respond_to?(method_name, true)
      @caller.send(method_name, *args, &block)
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    @caller.respond_to?(method_name, include_private)
  end
end

Vous constaterez que cette classe est vraiment légère, et qu’elle inclut une autre gem :

1
include Wisper::Publisher

Cette gem est une dépendance automatiquement incluse avec Rectify, dans notre cas nous devons ajouter à notre `Gemfile` la ligne suivante :

1
 gem 'wisper', '~> 2.0.0'

Quand à son utilité, nous allons rapidement y venir !
Nous allons donc pouvoir créer notre première command, et vous l’aurez compris, elle va hériter de la class `BaseCommand` :

1
app/commands/create_account.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CreateAccount < BaseCommand

  def initialize(user)
    @user = user
  end

  def call
  # Votre code métier ici

    return broadcast(:invalid, @user) if @user.invalid?

    create_payment_profil(@user)

    transaction do
    # Un bloc permettant d'annuler toutes les transations ActiveRecord si l'une d'elles génère une erreur
    # une simple implémentation d'ActiveRecord::Base.transation
 
      @user.save!
    end

    broadcast :ok, @user
    end

    def create_payment_profil(user)
      # ...
    end
end

Il y a deux fonctions importantes, `initialize` va vous permettre d’instancier votre command, vous pouvez passer autant de paramètres que nécessaire, nous les stockons dans des variables d’instance pour les utiliser au moment où nous appellerons la méthode `call`.

C’est dans la méthode `call` que vous allez pouvoir déplacer le code métier situé dans vos controllers et dans vos models. Deux éléments sont importants ici, tout d’abord le block `transaction` qui est une ré-implémentation d’ActiveRecord::Base.transaction, toutes les transactions ActiveRecord présentes dans ce block ne sont pas appliqués si l’une d’entre elles génère une erreur. Pratique lorsque vous devez sauvegarder plusieurs objets distincts sans possibilité de le faire en nested !

Ensuite, vous aurez remarqué la fonction broadcast qui est apporté par la gem Wisper, cette fonction va nous permettre de renvoyer des informations à notre controller tout en passant un ou plusieurs objets.
Maintenant que notre command est écrite nous allons pouvoir l’implémenter dans un controller :

1
app/controllers/admin/users_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Admin::UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    account = CreateAccount.new(@user)

    account.on(:invalid) do |user|
    render :new, alert: t('custom_message')
  end

  account.on(:ok) do |user|
    redirect_to admin_user_path(user), notice: t('custom_message')
  end

  account.call

    # Autre syntaxe possible
    #
    # CreateAccount.call(@user) do
      # on(:ok) do |user|
      # # ...
    # end
    #  on(:invalid) do |user|
      # # ...
      # end
    # end
  end
end

Rien ne nous empêche d’intégrer cette command aussi dans nos seeds, tasks… Vous comprenez maintenant le pourquoi du comment concernant la réutilisabilité et la maintenabilité.
Il nous reste un dernier point à aborder, comment écrire nos tests unitaires ? Tout d’abord, nous allons ajouter la gem wisper-rspec à notre Gemfile. Cette gem va simplement ajouter des matchers pour Rspec. Notez qu’il existe une gem équivalente pour Minitest.

1
gem 'wisper-rspec'

Nous allons ajouter le fichier de spec suivant :

1
spec/commands/create_account_spec.rb
1
2
3
4
5
6
7
8
9
10
require 'rails_helper'

RSpec.describe CreateAccount, type: :command do
  let(:user) { build(:user) }

  it 'can success' do
    command = CreateAccount.new(user)
    expect(command).to broadcast(:ok)
  end
end

Vous pouvez ainsi tester très simplement les réponses de votre command avec le matcher `broadcast` !
Vous avez apprécié ce tuto ? Vous souhaitez vous perfectionner sur Ruby On Rails avec nous ? Faites-nous part de vos commentaires et suggestions en nous contactant.
Je vous donne d’ores et déjà rendez-vous prochainement pour un tutoriel sur les presenters.

A très bientôt !

Les derniers articles

Le Flat Design, c'est du Material Design ?


29 août 2017

Interview vidéo : Noémie développeuse web et mobile


26 juin 2017