Création d'un Gem Ruby Complet pour la Conversion de Casse de Chaînes

code 28 août 2025

Ce quide vous accompagnera à travers la création d'un gem Ruby nommé string_case_converter qui ajoutera des méthodes de conversion camelCase, snake_case, PascalCase et "regular" (espace-séparé, première lettre majuscule) à la classe String. Nous couvrirons tous les aspects d'un gem professionnel : tests, Rake, YARD (avec @example), RuboCop, gem_release, statistiques de code (avec code_statistics), vérifications CVE avec Brakeman et Bundle Audit, ainsi que l'intégration continue (CI) avec GitHub Actions et l'analyse de code avec Code Climate.

Objectif du Gem

Le gem string_case_converter étendra la classe String de Ruby pour ajouter les méthodes suivantes :

  • to_camel_case: Convertit une chaîne en camelCase.
  • to_snake_case: Convertit une chaîne en snake_case.
  • to_pascal_case: Convertit une chaîne en PascalCase.
  • to_regular_case: Convertit une chaîne en une forme "régulière" (espace-séparée, première lettre majuscule).

Prérequis

Avant de commencer, assurez-vous d'avoir les éléments suivants installés :

  • Ruby (version 2.7 ou supérieure recommandée)
  • Bundler (gem install bundler)
  • Git (pour la gestion de version et gem_release)
  • Bundle Audit (gem install bundle-audit)
  • Un compte GitHub (pour l'intégration continue et l'hébergement du code).
  • Un compte Code Climate (optionnel, pour les badges de maintenabilité et de couverture).

Étape 1 : Initialisation du Gem avec Bundler

Remarque : pour réaliser une création jusqu'au bout, vous dever choisir un autre nom de gem et le faire avec du code que vous voulez réellement publier ( ne polluer pas Rubygems avec un gem fictif.


Bundler est l'outil standard pour créer la structure de base d'un gem.


Créez le squelette du gem :

bundle gem string_case_converter --test=rspec --rubocop --no-exe
   
  • --test=rspec: Configure RSpec comme framework de test.
  • --rubocop: Ajoute RuboCop pour le linting.
  • --no-exe: Nous n'avons pas besoin d'un exécutable binaire pour ce gem simple.

   cd string_case_converter
  • Mettez à jour les dépendances de développement et les informations du gem dans string_case_converter.gemspec
  • Ouvrez le fichier string_case_converter.gemspec.
# emacs string_case_converter.gemspec   
  • Ajoutez les dépendances suivantes dans la section spec.add_development_dependency:
# ...    
spec.add_development_dependency "rspec", "~> 3.0"    spec.add_development_dependency "rubocop", "~> 1.0"    spec.add_development_dependency "yard", "~> 0.9" # Pour la documentation    spec.add_development_dependency "simplecov", "~> 0.18" # Pour la couverture de code    spec.add_development_dependency "simplecov-lcov", "~> 0.3" # Pour générer des rapports LCOV pour Code Climate    spec.add_development_dependency "brakeman", "~> 5.0" # Pour l'analyse de sécurité du code    spec.add_development_dependency "bundle-audit", "~> 0.9" # Pour l'analyse de sécurité des dépendances    spec.add_development_dependency "code_statistics", "~> 0.1" # Pour les statistiques de lignes de code    spec.add_development_dependency "gem_release", "~> 2.0" # Pour la gestion des versions    # ...
  • Mettez à jour les informations du gem pour inclure la licence et les liens vers le dépôt (remplacez votre_utilisateur_github et votre_repo_github par vos informations réelles si vous comptez le publier) :
# ...    
spec.homepage = "https://github.com/votre_utilisateur_github/string_case_converter"    
spec.license = "MIT" # Spécifie la licence
spec.metadata["allowed_push_host"] = "https://rubygems.org" # Recommandé pour la publication
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md" # Optionnel, mais bonne pratique
# ...
  • Installez les dépendances :
   bundle install

Étape 2 : Ajout du README et de la Licence MIT

Un fichier README.md fournit des informations essentielles sur votre gem, et un fichier LICENSE.txt spécifie les conditions d'utilisation.

Créez le fichier LICENSE.txt


Dans le répertoire racine de votre gem (string_case_converter/), créez un fichier nommé LICENSE.txt et ajoutez le contenu suivant (remplacez [YEAR] et [COPYRIGHT HOLDER NAME] par les informations appropriées) :

MIT License

Copyright (c) [YEAR] [COPYRIGHT HOLDER NAME]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Créez le fichier README.md


Dans le répertoire racine de votre gem (string_case_converter/), créez un fichier nommé README.md et ajoutez le contenu suivant. Nous inclurons des badges qui seront mis à jour automatiquement par GitHub Actions et Code Climate.

# String Case Converter

[![Gem Version](https://badge.fury.io/rb/string_case_converter.svg)](https://badge.fury.io/rb/string_case_converter)
[![Build Status](https://github.com/votre_utilisateur_github/string_case_converter/actions/workflows/ruby.yml/badge.svg)](https://github.com/votre_utilisateur_github/string_case_converter/actions/workflows/ruby.yml)
[![Maintainability](https://api.codeclimate.com/v1/badges/YOUR_CODECLIMATE_MAINTAINABILITY_ID/maintainability)](https://codeclimate.com/github/votre_utilisateur_github/string_case_converter/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/YOUR_CODECLIMATE_COVERAGE_ID/test_coverage)](https://codeclimate.com/github/votre_utilisateur_github/string_case_converter/test_coverage)
[![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/string_case_converter)

Un gem Ruby léger qui étend la classe `String` pour ajouter des méthodes de conversion de casse (camelCase, snake_case, PascalCase, et regular case).

## Installation

Ajoutez cette ligne à votre `Gemfile` :

    gem 'string_case_converter'

Puis exécutez :

bundle install

Ou installez-le directement :

    gem install string_case_converter

## Utilisation

Le gem utilise les https://docs.ruby-lang.org/en/master/doc/syntax/refinements_rdoc.html de Ruby pour étendre la classe String. Vous devez donc using StringCaseConverter dans le scope où vous souhaitez utiliser les méthodes.

    require 'string_case_converter'

    # Active les refinements pour le scope actuel
    using StringCaseConverter

    # Conversion en snake_case
    puts "CamelCaseString".to_snake_case    #=> "camel_case_string"
    puts "PascalCaseString".to_snake_case   #=> "pascal_case_string"
    puts "kebab-case-string".to_snake_case  #=> "kebab_case_string"
    puts "Regular Case String".to_snake_case #=> "regular_case_string"
    puts "HTTPResponse".to_snake_case       #=> "http_response"

    # Conversion en camelCase
    puts "snake_case_string".to_camel_case  #=> "snakeCaseString"
    puts "PascalCaseString".to_camel_case   #=> "pascalCaseString"
    puts "kebab-case-string".to_camel_case  #=> "kebabCaseString"
    puts "Regular Case String".to_camel_case #=> "regularCaseString"
    puts "http_response".to_camel_case      #=> "httpResponse"

    # Conversion en PascalCase
    puts "snake_case_string".to_pascal_case #=> "SnakeCaseString"
    puts "camelCaseString".to_pascal_case   #=> "CamelCaseString"
    puts "kebab-case-string".to_pascal_case #=> "KebabCaseString"
    puts "Regular Case String".to_pascal_case #=> "RegularCaseString"
    puts "http_response".to_pascal_case     #=> "HttpResponse"

    # Conversion en regular case (espace-séparé, première lettre majuscule)
    puts "snake_case_string".to_regular_case #=> "Snake case string"
    puts "camelCaseString".to_regular_case   #=> "Camel case string"
    puts "PascalCaseString".to_regular_case  #=> "Pascal case string"
    puts "kebab-case-string".to_regular_case #=> "Kebab case string"
    puts "HTTPResponse".to_regular_case      #=> "Http response"

## Développement

Après avoir cloné le dépôt, exécutez bundle install pour installer les dépendances. Ensuite, exécutez bundle exec rake spec pour lancer les tests.

Vous pouvez également exécuter bin/console pour un shell interactif qui vous permettra d'expérimenter.

Pour installer ce gem sur votre machine locale, exécutez bundle exec rake install. Pour publier une nouvelle version, mettez à jour le numéro de version dans version.rb, puis exécutez bundle exec rake release, qui créera un tag Git pour la version, poussera les commits et le tag, et poussera le fichier .gem vers https://rubygems.org.
Tâches Rake Utiles

    bundle exec rake spec: Exécute les tests RSpec.
    bundle exec rake rubocop: Exécute RuboCop pour le linting du code (avec auto-correction).
    bundle exec rake yard: Génère la documentation YARD.
    bundle exec rake coverage: Exécute les tests avec un rapport de couverture de code.
    bundle exec rake stats: Affiche les statistiques de lignes de code (fourni par code_statistics).
    bundle exec rake brakeman: Scanne le code pour les vulnérabilités de sécurité.
    bundle exec rake audit: Scanne les dépendances pour les vulnérabilités connues.
    bundle exec rake security_check: Exécute brakeman et audit.

## Contribution

Les rapports de bugs et les pull requests sont les bienvenus sur GitHub à l'adresse https://github.com/votre_utilisateur_github/string_case_converter.
Licence

Le gem est disponible en tant qu'open source sous les termes de la https://opensource.org/licenses/MIT.
Note : Remplacez votre_utilisateur_github et votre_repo_github par vos informations réelles. Les YOUR_CODECLIMATE_..._ID seront remplacés à l'étape 10.

Étape 3 : Implémentation de la logique principale

Nous allons utiliser les refinements de Ruby pour ajouter nos méthodes à la classe String. C'est une approche plus propre que de modifier directement String, car les méthodes ne seront disponibles que là où le module est explicitement using.

Mettez à jour lib/string_case_converter/version.rb

(si vous le souhaitez, mais gem_release le gérera).

# lib/string_case_converter/version.rb
module StringCaseConverter
  VERSION = "0.1.0"
end

Ouvrez lib/string_case_converter.rb

Et remplacez son contenu par ce qui suit. Notez l'utilisation des tags @example pour les exemples de code.

# lib/string_case_converter.rb

module StringCaseConverter
  # Refinements pour ajouter des méthodes de conversion de casse à la classe String.
  refine String do
    # Convertit une chaîne en snake_case.
    #
    # @return [String] La chaîne convertie en snake_case.
    #
    # @example Convertit une chaîne camelCase en snake_case
    #   "camelCaseString".to_snake_case #=> "camel_case_string"
    # @example Convertit une chaîne PascalCase en snake_case
    #   "PascalCaseString".to_snake_case #=> "pascal_case_string"
    # @example Convertit une chaîne kebab-case en snake_case
    #   "kebab-case-string".to_snake_case #=> "kebab_case_string"
    # @example Convertit une chaîne avec des espaces en snake_case
    #   "Regular Case String".to_snake_case #=> "regular_case_string"
    # @example Gère les acronymes (ex: HTTPResponse)
    #   "HTTPResponse".to_snake_case #=> "http_response"
    # @example Gère les acronymes (ex: MyAPIKey)
    #   "MyAPIKey".to_snake_case #=> "my_api_key"
    # @example Une chaîne déjà en snake_case reste inchangée
    #   "already_snake_case".to_snake_case #=> "already_snake_case"
    # @example Gère une chaîne vide
    #   "".to_snake_case #=> ""
    def to_snake_case
      self
        .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') # Gère les acronymes (ex: HTTPResponse -> HTTP_Response)
        .gsub(/([a-z\d])([A-Z])/, '\1_\2')     # Gère les transitions minuscules/majuscules (ex: fooBar -> foo_Bar)
        .tr("-", "_")                          # Convertit les tirets en underscores (kebab-case)
        .gsub(/\s/, '_')                       # Convertit les espaces en underscores
        .downcase                              # Met tout en minuscules
    end

    # Convertit une chaîne en camelCase.
    #
    # @return [String] La chaîne convertie en camelCase.
    #
    # @example Convertit une chaîne snake_case en camelCase
    #   "snake_case_string".to_camel_case #=> "snakeCaseString"
    # @example Convertit une chaîne PascalCase en camelCase
    #   "PascalCaseString".to_camel_case #=> "pascalCaseString"
    # @example Convertit une chaîne kebab-case en camelCase
    #   "kebab-case-string".to_camel_case #=> "kebabCaseString"
    # @example Convertit une chaîne avec des espaces en camelCase
    #   "Regular Case String".to_camel_case #=> "regularCaseString"
    # @example Gère les acronymes (ex: HTTP_Response)
    #   "http_response".to_camel_case #=> "httpResponse"
    # @example Gère les acronymes (ex: my_api_key)
    #   "my_api_key".to_camel_case #=> "myApiKey"
    # @example Une chaîne déjà en camelCase reste inchangée
    #   "alreadyCamelCase".to_camel_case #=> "alreadyCamelCase"
    # @example Gère une chaîne vide
    #   "".to_camel_case #=> ""
    def to_camel_case
      self
        .to_snake_case # Commence par convertir en snake_case pour une base cohérente
        .gsub(/_([a-z])/) { Regexp.last_match(1).upcase } # Met en majuscule la lettre après un underscore
        .sub(/^[a-z]/) { |match| match.downcase } # S'assure que la première lettre est en minuscule (camelCase)
    end

    # Convertit une chaîne en PascalCase (première lettre de chaque mot en majuscule).
    #
    # @return [String] La chaîne convertie en PascalCase.
    #
    # @example Convertit une chaîne snake_case en PascalCase
    #   "snake_case_string".to_pascal_case #=> "SnakeCaseString"
    # @example Convertit une chaîne camelCase en PascalCase
    #   "camelCaseString".to_pascal_case #=> "CamelCaseString"
    # @example Convertit une chaîne kebab-case en PascalCase
    #   "kebab-case-string".to_pascal_case #=> "KebabCaseString"
    # @example Convertit une chaîne avec des espaces en PascalCase
    #   "Regular Case String".to_pascal_case #=> "RegularCaseString"
    # @example Gère les acronymes (ex: http_response)
    #   "http_response".to_pascal_case #=> "HttpResponse"
    # @example Gère les acronymes (ex: my_api_key)
    #   "my_api_key".to_pascal_case #=> "MyApiKey"
    # @example Une chaîne déjà en PascalCase reste inchangée
    #   "AlreadyPascalCase".to_pascal_case #=> "AlreadyPascalCase"
    # @example Gère une chaîne vide
    #   "".to_pascal_case #=> ""
    def to_pascal_case
      self
        .to_snake_case # Commence par convertir en snake_case
        .gsub(/_([a-z])/) { Regexp.last_match(1).upcase } # Met en majuscule la lettre après un underscore
        .sub(/^[a-z]/) { |match| match.upcase } # S'assure que la première lettre est en majuscule (PascalCase)
    end

    # Convertit une chaîne en une forme "régulière" (mots séparés par des espaces, première lettre majuscule).
    #
    # @return [String] La chaîne convertie en forme régulière.
    #
    # @example Convertit une chaîne snake_case en forme régulière
    #   "snake_case_string".to_regular_case #=> "Snake case string"
    # @example Convertit une chaîne camelCase en forme régulière
    #   "camelCaseString".to_regular_case #=> "Camel case string"
    # @example Convertit une chaîne PascalCase en forme régulière
    #   "PascalCaseString".to_regular_case #=> "Pascal case string"
    # @example Convertit une chaîne kebab-case en forme régulière
    #   "kebab-case-string".to_regular_case #=> "Kebab case string"
    # @example Gère les acronymes (ex: HTTPResponse)
    #   "HTTPResponse".to_regular_case #=> "Http response"
    # @example Gère les acronymes (ex: MyAPIKey)
    #   "MyAPIKey".to_regular_case #=> "My api key"
    # @example Une chaîne déjà en forme régulière reste inchangée (si capitalisée)
    #   "Regular case string".to_regular_case #=> "Regular case string"
    # @example Gère une chaîne vide
    #   "".to_regular_case #=> ""
    def to_regular_case
      self
        .to_snake_case # Commence par convertir en snake_case
        .gsub('_', ' ') # Remplace les underscores par des espaces
        .capitalize    # Met la première lettre de la phrase en majuscule
    end
  end
end

Étape 4 : Écriture des Tests avec RSpec

Les tests sont cruciaux pour s'assurer que votre code fonctionne comme prévu et pour prévenir les régressions.

Ouvrez spec/string_case_converter_spec.rb

Et remplacez son contenu par :

# spec/string_case_converter_spec.rb
require "string_case_converter"

# Active les refinements pour les tests
using StringCaseConverter

RSpec.describe StringCaseConverter do
  describe String do
    context "#to_snake_case" do
      it "converts camelCase to snake_case" do
        expect("camelCaseString".to_snake_case).to eq("camel_case_string")
      end

      it "converts PascalCase to snake_case" do
        expect("PascalCaseString".to_snake_case).to eq("pascal_case_string")
      end

      it "converts kebab-case to snake_case" do
        expect("kebab-case-string".to_snake_case).to eq("kebab_case_string")
      end

      it "converts regular case to snake_case" do
        expect("Regular Case String".to_snake_case).to eq("regular_case_string")
      end

      it "handles acronyms correctly" do
        expect("HTTPResponse".to_snake_case).to eq("http_response")
        expect("MyAPIKey".to_snake_case).to eq("my_api_key")
      end

      it "returns snake_case string as is" do
        expect("already_snake_case".to_snake_case).to eq("already_snake_case")
      end

      it "handles empty string" do
        expect("".to_snake_case).to eq("")
      end
    end

    context "#to_camel_case" do
      it "converts snake_case to camelCase" do
        expect("snake_case_string".to_camel_case).to eq("snakeCaseString")
      end

      it "converts PascalCase to camelCase" do
        expect("PascalCaseString".to_camel_case).to eq("pascalCaseString")
      end

      it "converts kebab-case to camelCase" do
        expect("kebab-case-string".to_camel_case).to eq("kebabCaseString")
      end

      it "converts regular case to camelCase" do
        expect("Regular Case String".to_camel_case).to eq("regularCaseString")
      end

      it "handles acronyms correctly" do
        expect("http_response".to_camel_case).to eq("httpResponse")
        expect("my_api_key".to_camel_case).to eq("myApiKey")
      end

      it "returns camelCase string as is" do
        expect("alreadyCamelCase".to_camel_case).to eq("alreadyCamelCase")
      end

      it "handles empty string" do
        expect("".to_camel_case).to eq("")
      end
    end

    context "#to_pascal_case" do
      it "converts snake_case to PascalCase" do
        expect("snake_case_string".to_pascal_case).to eq("SnakeCaseString")
      end

      it "converts camelCase to PascalCase" do
        expect("camelCaseString".to_pascal_case).to eq("CamelCaseString")
      end

      it "converts kebab-case to PascalCase" do
        expect("kebab-case-string".to_pascal_case).to eq("KebabCaseString")
      end

      it "converts regular case to PascalCase" do
        expect("Regular Case String".to_pascal_case).to eq("RegularCaseString")
      end

      it "handles acronyms correctly" do
        expect("http_response".to_pascal_case).to eq("HttpResponse")
        expect("my_api_key".to_pascal_case).to eq("MyApiKey")
      end

      it "returns PascalCase string as is" do
        expect("AlreadyPascalCase".to_pascal_case).to eq("AlreadyPascalCase")
      end

      it "handles empty string" do
        expect("".to_pascal_case).to eq("")
      end
    end

    context "#to_regular_case" do
      it "converts snake_case to regular case" do
        expect("snake_case_string".to_regular_case).to eq("Snake case string")
      end

      it "converts camelCase to regular case" do
        expect("camelCaseString".to_regular_case).to eq("Camel case string")
      end

      it "converts PascalCase to regular case" do
        expect("PascalCaseString".to_regular_case).to eq("Pascal case string")
      end

      it "converts kebab-case to regular case" do
        expect("kebab-case-string".to_regular_case).to eq("Kebab case string")
      end

      it "handles acronyms correctly" do
        expect("HTTPResponse".to_regular_case).to eq("Http response")
        expect("MyAPIKey".to_regular_case).to eq("My api key")
      end

      it "returns regular case string as is (if already capitalized)" do
        expect("Regular case string".to_regular_case).to eq("Regular case string")
      end

      it "handles empty string" do
        expect("".to_regular_case).to eq("")
      end
    end
  end
end

Exécutez les tests

bundle exec rspec
Tous les tests devraient passer.

Étape 5 : Configuration de Rakefile pour les Tâches

Le Rakefile est le cœur de la gestion de votre gem. Il permet d'automatiser les tâches courantes.

Ouvrez Rakefile

Et ajoutez les lignes suivantes :

# Rakefile
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rubocop/rake_task"
require "yard/rake/yardoc_task"
require "simplecov" # Pour la couverture de code
require "simplecov-lcov" # Pour générer des rapports LCOV
require "brakeman" # Pour l'analyse de sécurité du code
require "bundler/audit/task" # Pour l'analyse de sécurité des dépendances
require "code_statistics/rake_task" # Pour les statistiques de lignes de code

# Tâche RSpec
RSpec::Core::RakeTask.new(:spec)

# Tâche RuboCop
RuboCop::RakeTask.new(:rubocop) do |task|
  task.options = ["--auto-correct-all"] # Optionnel: corrige automatiquement les problèmes
end

# Tâche YARD pour la documentation
YARD::Rake::YardocTask.new

# La tâche 'stats' est automatiquement ajoutée par 'code_statistics/rake_task'
# Elle affichera des statistiques détaillées sur le code.

# Tâche pour la couverture de code (nécessite l'exécution des tests)
desc "Exécute les tests avec couverture de code et génère un rapport LCOV"
task :coverage do
  # SimpleCov est démarré dans spec/spec_helper.rb
  # Nous configurons SimpleCov pour générer un rapport LCOV
  SimpleCov::Formatter::LcovFormatter.config do |c|
    c.report_with_single_file = true
    c.single_report_path = 'coverage/lcov.info'
  end
  SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
  SimpleCov.start do
    add_filter "/spec/"
    add_filter "/vendor/"
  end

  Rake::Task["spec"].invoke
  puts "Rapport de couverture généré dans ./coverage/index.html et ./coverage/lcov.info"
end

# Tâche pour la vérification des CVE avec Brakeman (code de l'application)
desc "Exécute Brakeman pour vérifier les vulnérabilités dans le code de l'application"
task :brakeman do
  # Brakeman est principalement pour les applications Rails, mais peut trouver des problèmes génériques.
  # Utiliser --no-exit-on-error pour ne pas échouer la tâche Rake si des avertissements sont trouvés.
  sh "bundle exec brakeman -q -w1 -o brakeman_report.html --no-exit-on-error ."
  puts "Rapport Brakeman généré dans brakeman_report.html"
end

# Tâche pour la vérification des CVE avec Bundle Audit (dépendances)
desc "Exécute bundle audit pour vérifier les vulnérabilités des dépendances"
task :audit do
  sh "bundle audit check --update" # --update pour s'assurer que la base de données des advisories est à jour
end

# Tâche pour exécuter toutes les vérifications de sécurité
desc "Exécute toutes les vérifications de sécurité (brakeman et bundle audit)"
task :security_check => [:brakeman, :audit]

# Tâche par défaut (exécute les tests et rubocop)
task default: %i[rubocop spec]

Vérifiez les tâches disponibles

$ bundle exec rake -T
Vous devriez voir toutes les tâches que nous avons définies, y compris rake audit, rake security_check et rake stats (fournie par code_statistics).

Étape 6 : Linting du Code avec RuboCop

RuboCop est un linter et un formateur de code Ruby. Il aide à maintenir un style de code cohérent.

Exécutez RuboCop

bundle exec rubocop

Si des problèmes sont trouvés, vous pouvez les corriger manuellement ou utiliser :

bundle exec rubocop --auto-correct-all

Ou simplement bundle exec rake rubocop comme vous avez configuré l'option --auto-correct-all dans le Rakefile à l'étape 5

Personnalisation de RuboCop


Le fichier .rubocop.yml a été créé par Bundler. Vous pouvez le modifier pour adapter les règles à vos préférences. Par exemple, pour ignorer certains fichiers ou désactiver certaines règles.

Étape 7 : Documentation avec YARD

YARD est un générateur de documentation pour Ruby. Il utilise des commentaires spéciaux dans votre code (y compris les tags @example) pour générer une documentation HTML.
Remarque : Les commentaires YARD ont déjà été ajoutés dans lib/string_case_converter.rb à l'étape 3, incluant les descriptions, @param, @return et surtout les @example.

Visualisez la documentation

bundle exec yard server --reload

Ouvrez votre navigateur à l'adresse http://localhost:8808 pour voir la documentation. Vous pourrez naviguer vers les méthodes et voir les exemples de code formatés et mis en évidence.

Générez la documentation

bundle exec rake yard

Cela créera un répertoire doc/ contenant la documentation HTML.

Étape 8 : Statistiques du Code (LOC et Couverture)

Lignes de Code (LOC) avec code_statistics

Le gem code_statistics fournit des statistiques détaillées sur votre code Ruby, y compris les lignes de code, les classes, les méthodes, etc.

Exécutez la tâche :

bundle exec rake stats

Cela affichera un tableau récapitulatif des statistiques de votre code.

Remarque : l'outil code_statistics est déjà configuré dans votre Rakefile à l'etape 5

Couverture de Code avec SimpleCov

SimpleCov est un outil de couverture de code. Il vous indique quelle partie de votre code est couverte par vos tests.

Configurez SimpleCov
Ouvrez spec/spec_helper.rb et ajoutez les lignes suivantes tout en haut du fichier :

# spec/spec_helper.rb
require "simplecov"
require "simplecov-lcov" # Ajouté pour les rapports LCOV

# Configure SimpleCov pour générer un rapport LCOV pour les services CI
SimpleCov::Formatter::LcovFormatter.config do |c|
  c.report_with_single_file = true
  c.single_report_path = 'coverage/lcov.info'
end

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
  SimpleCov::Formatter::HTMLFormatter,
  SimpleCov::Formatter::LcovFormatter
])

SimpleCov.start do
  add_filter "/spec/" # Exclut le répertoire des tests du rapport de couverture
  add_filter "/vendor/" # Exclut les dépendances
end

require "bundler/setup"
require "string_case_converter"

RSpec.configure do |config|
  # ... (reste de la configuration RSpec)
end

Exécutez la tâche de couverture

bundle exec rake coverage

Cela exécutera vos tests et générera un rapport de couverture dans le répertoire coverage/. Ouvrez coverage/index.html dans votre navigateur pour voir le rapport détaillé. Un fichier coverage/lcov.info sera également généré, utile pour les services CI.

Remarque : l'outil SimpleCov est déjà initialisé dans votre Rakefile à l'etape 5

Étape 9 : Intégration Continue (CI) avec GitHub Actions

L'intégration continue est cruciale pour automatiser les tests, le linting et les vérifications de sécurité à chaque push ou pull request.

Initialisez un dépôt Git et poussez-le sur GitHub

Si cela n'a pas déjçà été fait
git init
git add .
git commit -m "Initial commit of string_case_converter gem with basic structure"
# Créez un dépôt sur GitHub et ajoutez-le comme remote
git remote add origin https://github.com/votre_utilisateur_github/string_case_converter.git
git push -u origin main # Ou master, selon votre branche par défaut

Créez le fichier de workflow GitHub Actions

Dans le répertoire racine de votre gem, créez le dossier .github/workflows/ et à l'intérieur, un fichier nommé ruby.yml :

mkdir -p .github/workflows
touch .github/workflows/ruby.yml

Ajoutez le contenu suivant à .github/workflows/ruby.yml

# .github/workflows/ruby.yml
name: Ruby CI

on:
  push:
    branches: [ "main", "master" ]
  pull_request:
    branches: [ "main", "master" ]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        ruby-version: ['2.7', '3.0', '3.1', '3.2'] # Testez sur différentes versions de Ruby

    steps:
    - uses: actions/checkout@v3 # Récupère le code du dépôt
    - name: Set up Ruby ${{ matrix.ruby-version }}
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{ matrix.ruby-version }}
        bundler-cache: true # Installe les dépendances avec Bundler

    - name: Run RuboCop
      run: bundle exec rake rubocop

    - name: Run RSpec tests with coverage
      run: bundle exec rake coverage # Exécute les tests et génère le rapport LCOV

    - name: Run Brakeman security scan
      run: bundle exec rake brakeman

    - name: Run Bundle Audit security scan
      run: bundle exec rake audit

    # Étape optionnelle pour uploader la couverture de code vers Code Climate
    # Nécessite la configuration de Code Climate à l'étape suivante
    - name: Upload coverage to Code Climate
      if: success() # N'exécute que si les étapes précédentes ont réussi
      env:
        CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} # Variable d'environnement secrète
      run: |
        curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
        chmod +x ./cc-test-reporter
        ./cc-test-reporter format-coverage -t lcov -o coverage/codeclimate.json coverage/lcov.info
        ./cc-test-reporter upload-coverage -i coverage/codeclimate.json

Commitez et poussez le workflow

git add .github/workflows/ruby.yml
git commit -m "Add GitHub Actions CI workflow"
git push

Rendez-vous sur l'onglet "Actions" de votre dépôt GitHub pour voir le workflow s'exécuter.

Étape 10 : Services d'Analyse de Code (Code Climate)

Remarque : L'usage de Code Climate n'est pas obligatoire et cela vous demande un compte, si vous voulez l'eviter, vous devrez supprimer les badges du README.md correspondants.

Code Climate fournit des métriques de maintenabilité et de couverture de code, et les affiche sous forme de badges.

  1. Inscrivez-vous sur Code Climate :
    Rendez-vous sur codeclimate.com et connectez-vous avec votre compte GitHub. Ajoutez votre dépôt string_case_converter.
  2. Récupérez les IDs des badges :
    Une fois votre dépôt ajouté, Code Climate vous fournira des IDs pour la maintenabilité et la couverture.
    • Pour la maintenabilité, vous trouverez un badge avec un ID comme YOUR_CODECLIMATE_MAINTAINABILITY_ID.
    • Pour la couverture, vous devrez peut-être configurer l'upload (voir l'étape suivante), mais l'ID sera similaire : YOUR_CODECLIMATE_COVERAGE_ID.
  3. Mettez à jour le README.md :
    Remplacez les placeholders YOUR_CODECLIMATE_MAINTAINABILITY_ID et YOUR_CODECLIMATE_COVERAGE_ID dans votre README.md avec les IDs réels fournis par Code Climate.
  4. Configurez la variable d'environnement CC_TEST_REPORTER_ID sur GitHub :
    Pour que GitHub Actions puisse uploader les rapports de couverture à Code Climate, vous devez stocker votre CC_TEST_REPORTER_ID (fourni par Code Climate après avoir ajouté votre repo) comme secret GitHub.Maintenant, chaque fois que votre workflow GitHub Actions s'exécutera, il enverra les données de couverture à Code Climate, et vos badges se mettront à jour.
    • Dans votre dépôt GitHub, allez dans Settings > Secrets and variables > Actions > New repository secret.
    • Nommez le secret CC_TEST_REPORTER_ID.
    • Collez la valeur de votre CC_TEST_REPORTER_ID (obtenue depuis Code Climate) dans le champ "Secret".
    • Cliquez sur "Add secret".

Étape 11 : Gestion des Versions avec gem_release

gem_release simplifie le processus de versioning, de taggage Git et de publication de votre gem.

Utilisation de gem_release : gem_release ajoute des tâches Rake pour gérer les versions.

Mettre à jour une version patch (0.1.0 -> 0.1.1)

bundle exec rake release:patch

Mettre à jour une version mineure (0.1.0 -> 0.2.0)

bundle exec rake release:minor

Mettre à jour une version majeure (0.1.0 -> 1.0.0)

bundle exec rake release:major

Principe

Chacune de ces commandes :

  • Met à jour le numéro de version dans lib/string_case_converter/version.rb.
  • Crée un commit Git pour le changement de version.
  • Crée un tag Git (ex: v0.1.1).
  • Pousse le commit et le tag vers votre dépôt distant (si configuré).
  • Construit le gem (.gem file).
  • Publie le gem sur RubyGems.org (si vous êtes connecté et autorisé).
Important : Pour publier sur RubyGems.org, vous devez avoir un compte et être connecté via gem push. Pour un premier test, vous pouvez simplement utiliser rake release:patch et annuler la publication si vous ne voulez pas le rendre public tout de suite.

Étape 12 : Vérification des Vulnérabilités (CVE) avec Brakeman et Bundle Audit

Ces deux outils sont complémentaires pour la sécurité de votre projet Ruby.

Vérification du Code de l'Application avec Brakeman

Brakeman est un scanner de vulnérabilités statique pour les applications Ruby on Rails. Bien qu'il soit principalement conçu pour Rails, il peut détecter certaines vulnérabilités génériques dans le code Ruby.

  1. Interprétation :
    Pour un gem comme celui-ci, qui ne gère pas d'entrées utilisateur complexes, de bases de données ou de sessions, Brakeman trouvera probablement peu ou pas de vulnérabilités. C'est normal. L'objectif est de montrer comment l'intégrer.

Exécutez Brakeman :

bundle exec rake brakeman

Cela exécutera Brakeman et générera un rapport HTML (brakeman_report.html) dans le répertoire racine de votre gem.

Vérification des Dépendances avec Bundle Audit

bundle audit scanne votre fichier Gemfile.lock pour identifier les dépendances qui contiennent des vulnérabilités connues, en se basant sur la base de données Ruby Advisory Database.

  1. Interprétation :
    • Si aucune vulnérabilité n'est trouvée, vous verrez un message comme "No vulnerabilities found".
    • Si des vulnérabilités sont détectées, bundle audit listera les gems concernées, la version vulnérable, la version corrigée (si disponible) et un lien vers l'advisory de sécurité. Il est crucial de mettre à jour ces dépendances dès que possible.

Exécutez Bundle Audit :

bundle exec rake audit

La première fois, il téléchargera la base de données des advisories. Ensuite, il vérifiera vos dépendances.

Exécuter toutes les vérifications de sécurité

Vous pouvez exécuter les deux outils en une seule commande :

bundle exec rake security_check

Cette tâche est également exécutée automatiquement par votre workflow GitHub Actions.

Étape 13 : Construction et Publication du Gem

Bien que gem_release gère la publication, il est bon de connaître les commandes manuelles.

Publier le gem sur RubyGems.org


Assurez-vous d'avoir un compte RubyGems.org et d'être connecté (gem push).

bundle exec rake release

Cette commande (fournie par gem_release) construira, taggera, poussera et publiera votre gem.

Installer le gem localement

bundle exec rake install

Cela installera le gem sur votre système local, vous permettant de le require dans d'autres projets sans le publier.

Construire le gem

bundle exec rake build

Cela créera le fichier .gem (ex: string_case_converter-0.1.0.gem) dans le répertoire pkg/.

Conclusion

Félicitations ! Vous avez créé un gem Ruby complet et professionnel, intégrant toutes les bonnes pratiques modernes :

  • Fonctionnalités de conversion de casse robustes.
  • Tests unitaires avec RSpec.
  • Documentation claire avec YARD et des exemples intégrés.
  • Linting du code avec RuboCop.
  • Statistiques de code (LOC et couverture) avec code_statistics et SimpleCov.
  • Intégration Continue avec GitHub Actions pour automatiser les vérifications.
  • Analyse de code avec Code Climate pour la maintenabilité et la couverture.
  • Gestion des versions simplifiée avec gem_release.
  • Vérifications de sécurité approfondies avec Brakeman (code) et Bundle Audit (dépendances).
  • Un README.md informatif et une licence MIT claire.

Ce didacticiel vous fourni une base solide pour développer, maintenir et distribuer des gems Ruby de haute qualité et sécurisés.

Mots clés

Romain GEORGES

Open Source evangelist & Ruby enthousiast