Pourquoi je ne fais plus de Ruby / Ruby on rails

Ruby a été une technologie qui a pas mal été traitée sur la chaîne mais c’est un sujet que j’ai un peu abandonné avec le temps et je voulais aujourd’hui vous donner quelques explications sur les raisons qui ont fait que j’ai abandonné cette technologie.

Mon historique avec Ruby et Rails

A l’époque où j’ai commencé à faire du back-end (vers 2010), j’entendais souvent que tel ou tel framework était inspiré de Ruby on Rails et à force d’entendre ça j’ai finit par me demander si l’herbe n’était pas plus verte ailleurs que PHP.

Faux départ

Ma première découverte de Ruby on rails (vers 2011) n’a pas été très concluante à cause de plusieurs facteurs.

Tout d’abord j’ai voulu “speedrun” mon apprentissage en apprenant Ruby on Rails sans comprendre le langage Ruby qui permet de le faire fonctionner. Sans comprendre le langage de base, les problèmes de compréhensions se sont rapidement accumulés et m’ont très rapidement freiné.

Le second problème était mon système d’exploitation. A l’époque j’étais sur Windows et pas mal de gem (dépendances sur Ruby) ne s’installaient pas correctement (comme la gem mysql par exemple). Docker ou le WSL n’existait pas encore et la seule alternative était l’utilisation d’une machine virtuelle (lourd à mettre en place et à utiliser).

Seconde chance

Pour lutter contre ma dépendance au jeu League of legends, je décide de changer de système d’exploitation et de passer sur Linux. Sur ce nouvel environnement je me dis que l’apprentissage de Ruby & Rails sera plus simple et je décide de lui redonner sa chance. Cette fois-ci je fais les choses bien en commençant par me former sur le langage Ruby avant d’attaquer Ruby on Rails.

Et là c’est la révélation ! Le langage Ruby est un langage très expressif avec lequel je prends un réel plaisir à écrire du code (surtout si on compare sa syntaxe à celle de PHP que j’utilisais jusqu’alors).

require 'net/http'
require 'json'

def list_usernames(url)
  uri = URI(url)
  response = Net::HTTP.get(uri)
  users = JSON.parse(response)
  users.map { |user| user['name'] }
end

puts "Liste des utilisateurs :"
list_usernames("https://jsonplaceholder.typicode.com/users").each do |name|
    puts "- #{name}"
end

Ruby on rails suit la même philosophie et cette fois l’apprentissage se passe beaucoup mieux avec de bonnes bases sur Ruby.

# Exemple de CRUD sur Ruby on Rails
class RecipesController < ApplicationController
  before_action :set_recipe, only: [:show, :edit, :update, :destroy]

  # GET /recipes
  def index
    @recipes = Recipe.all
  end

  # GET /recipes/1
  def show
  end

  # GET /recipes/new
  def new
    @recipe = Recipe.new
  end

  # GET /recipes/1/edit
  def edit
  end

  # POST /recipes
  def create
    @recipe = Recipe.new(recipe_params)

    if @recipe.save
      redirect_to @recipe, notice: 'Recipe was successfully created.'
    else
      render :new
    end
  end

  # PATCH/PUT /recipes/1
  def update
    if @recipe.update(recipe_params)
      redirect_to @recipe, notice: 'Recipe was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /recipes/1
  def destroy
    @recipe.destroy
    redirect_to recipes_url, notice: 'Recipe was successfully destroyed.'
  end

  private
    def set_recipe
      @recipe = Recipe.find(params[:id])
    end

    # Only allow a list of trusted parameters
    def recipe_params
      params.require(:recipe).permit(:title, :description, :instructions, :image)
    end
end

A l’époque Ruby on Rails était pas mal à la mode et j’ai rapidement pu rapidement trouver des missions freelance qui utilisait ce framework. De la même manière j’ai été convaincu par l’efficacité du framework et j’ai décidé de migrer le code source de Grafikart de CakePHP vers Ruby on Rails.

La fin de la lune de miel

Vu comment j’encense le framework on peut se demander pourquoi j’ai arrêter de traiter Ruby et Ruby on rails sur le site. Pourquoi abandonner une technologie que je trouve très bien ?

Pas de typage

Au début de ma carrière je voyais le typage comme un frein dans l’écriture de code mais avec le temps mon avis a changé et j’ai de plus en plus de mal à utiliser un langage avec un typage faible et qui est trop dynamique. Ce problème est amplifié par le fait qu’en freelance je suis amené à sauter de projet en projet et sans types il est difficile de savoir les méthodes qui sont disponibles sur le code qu’on explore.

Ruby est un langage dynamique qui utilise extensivement le duck typing

Si ça marche comme un canard et que ça fait “couac” comme un canard, alors c’est un canard

On ne s’intéresse pas au type des paramètres mais plutôt aux méthodes qu’ils implémentent pour savoir s’ils sont compatible avec la logique de notre fonction.

def maMethode (file)
   raise TypeError unless file.respond_to?(:read)
   # Le code...
end

Mais ce type de vérification est très succinct et laisse passer pas mal de problèmes.

Une initiative a été lancée par Stripe, nommée Sorbet, pour apporter une analyse statique dans Ruby. Mais cette solution reste un “hack” et n’est pas aussi efficace qu’un typage qui serait conçu au niveau du langage directement.

Le monkey patching

Ce problème est amplifié par le fait que dans le cadre de Ruby les classes sont ouvertes et peuvent être redéfinies.

# Je peux créer une classe Integer même si elle existe déjà
class Integer

  def double
    self * 2
  end

end

puts 2.double

On peut ainsi faire du Monkey Patching pour rajouter des méthodes à n’importe quel objet. Ce n’est pas forcément exclusif à Ruby (on peut faire la même chose en JavaScript) mais c’est une pratique qui est assez répandue dans rails. Par exemple, des méthodes sont ajoutés sur les entiers pour manipuler les dates :

1.days.from_now

Le problème est qu’un même objet peut avoir des méthodes différentes en fonction du projet sur lequel on travail. Avec la multiplication des projets, les inconvénients du Monkey patching dépassent les avantages.

Méthodes magiques

Un autre frein à l’analyse statique réside dans l’utilisation du meta-progamming qui permet à Ruby de modifier son code à l’exécution. Encore une fois ce n’est pas une spécificité de Ruby mais c’est une pratique qui est très utilisée dans Rails.

class User < ApplicationRecord
  attr_accessor :name, :email, :message
  validates :name, :email, :message, presence: true

  belongs_to :group
  has_many :posts
end

Par exemple, attr_accessor va créer des accesseurs et des mutateurs pour les différentes propriétés passées en paramètre. Les méthodes belongs_to et has_many permettent de définir des relations et vont aussi ajouter des méthodes à la volée.

Ruby on rails se base sur des conventions qui permettent de deviner les méthodes disponibles à partir des méthodes utilisées. Le problème de cette approche est qu’il faut justement avoir ses conventions en tête quand on travaille sur un projet et il n’est pas rare de les oublier quand elles commencent à s’accumuler.

Plus complexe à héberger

Un autre problème que j’ai rencontré avec Ruby on Rails était son hébergement.

La plupart des projets pour lesquels je travaillais à l’époque utilisaient des systèmes d’hébergements mutualisés où il suffisait de déplacer les fichiers pour mettre en ligne le site. La mise en ligne d’application Rails était plus complexe car nécessitait l’utilisation d’un serveur qu’il fallait savoir configurer (le “cloud” n’était pas aussi développé qu’aujourd’hui et les coûts étaient aussi plus important).

Ruby est un langage généraliste et il existe plusieurs implémentation de serveur HTTP :

De mon côté j’ai du pas mal expérimenter avant de m’arrêter sur Puma mais la configuration de ce serveur web constitue une complexité supplémentaire (surtout la première fois qu’on essaie de le mettre en place).

PHP c’est amélioré

En fait, beaucoup des choses qui m’ont fait aimer Ruby et Rails initialement sont devenues des défauts avec le temps. En parallèle PHP, le langage que j’avais quitté pour Ruby, a évolué dans un sens qui correspond plus à mes préférences actuelles avec une intégration de plus en plus profonde des types et moins de magie.

// Même sans connaitre le projet je comprend la forme des paramètres
function hashPassword (User $user, string $salt): string
{
  // ...
}

Aussi, la communauté tend vers une approche moins magique / dynamique avec l’injection de dépendance et une obligation de typage pour les librairies tiers qui s’aligne plus avec ma vision actuelle.

Pas de regret

Malgré tout ce que je viens d’énoncer je ne regrette absolument pas d’avoir essayé Ruby et Rails. Ce langage et ce framework m’ont permis de découvrir une autre manière de faire les choses et d’affiner aussi mes préférences personnels lors du choix d’une solution pour mes projets.