Comment Rails gère le pluriel des mots

Publié le 3 septembre 2015 par Nicolas Le Chenic | back

Cet article est publié sous licence CC BY-NC-SA

Quand je pense “magie de Rails”, la gestion du nombre d’un mot me vient tout de suite en tête. Si vous avez déjà essayé de faire un scaffold avec “people” vous voyez surement de quoi je veux parler !

Toute cette couche est contenue dans une partie d’ActiveSupport dédié aux inflexions (“inflector” en anglais). Les inflexions regroupent toute un panoplie de méthodes destinées à gérer la casse, le nombre et les séparateurs de vos chaînes de caractères. Ces méthodes sont par exemple très utile lorsque vous générez un contrôleur ou un modèle via la commande rails.

Dans cet article on ajoutera des règles de pluriel à une application Rails. Cela vous sera utile si vous souhaitez créer une application en Français. On décortiquera ensuite la gem Rails pour comprendre sa gestion des pluriels !

Débutons par un scaffold

Nous allons commencer par un simple scaffold avec le mot “people” qui devient “person” au singulier.

❯ rails g scaffold people name:string

On observe dans l’arborescence de notre projet que Rails prend parfaitement en compte la gestion du nombre de nos fichiers.

arborescence

Plus fort que la lévitation d’un éléphant, Rails reconnaît le singuliers de people et attribut le bon nom à chaque fichier. D’ailleurs au moment de l’exécution, Rails nous avertit avec un warning :

[WARNING] The model name 'people' was recognized as a plural, using the singular 'person' instead. Override with --force-plural or setup custom inflection rules for this noun before running the generator.
      invoke  active_record
      create    db/migrate/20150826075951_create_people.rb
      create    app/models/person.rb
      invoke    test_unit
      create      test/models/person_test.rb
      create      test/fixtures/people.yml
      invoke  resource_route
       route    resources :people
      invoke  scaffold_controller
      create    app/controllers/people_controller.rb
      invoke    erb
      create      app/views/people
      ...

Lorsqu’on tape une commande Rails, on passe par les railties de notre gem. Cette partie va notamment gérer les commandes console de Rails. Ainsi, dans le générateur de ressource de Rails on trouve bien :

railties/lib/rails/generators/resource_helpers.rb

assign_controller_names!(controller_name.pluralize)

Qui fait donc appel à pluralize qu’on verra en détail plus bas. Cette méthode est accessible depuis n’importe ou dans votre application Rails. Si je lance la console Rails :

❯ rails c
Loading development environment (Rails 4.2.3)
irb(main):001:0> "person".pluralize
=> "people"
irb(main):002:0> "dog".pluralize
=> "dogs"
irb(main):003:0> "cats".singularize
=> "cat"

Cette méthode est aussi accessible depuis nos views dans notre application :

app/views/people/index.html.erb

<%= "person".pluralize %>
<%= "dog".pluralize %>
<%= "cats".singularize %>

Notre page affichera alors :

pluriels anglais

Hiboux, choux, cailloux…

Malgré sa puissance, cette magie reste faillible. En effet celle-ci se limite par défaut à la plupart des mots anglophones ce qui pourrait poser problème à nous autres mangeurs de grenouilles. Heureusement, votre application Rails vous laisse la possibilité d’ajouter facilement vos propres inflexions :

config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:fr) do |inflect|
  inflect.plural(/$/, 's')
  inflect.plural(/(hib|ch|bij|caill|p|gen|jouj)ou$/i, '\1oux')
end

Ici nous définissons qu’en Français, le pluriel prend un “s” et que dans le cas où l’on souhaite mettre hibou, chou, bijou… au pluriel, notre inflexion remplacera le “s” par “x”. Testons ce code depuis la console :

❯ rails c
Loading development environment (Rails 4.2.3)
irb(main):001:0> "hibou".pluralize(:fr)
=> "hiboux"
irb(main):002:0> "matou".pluralize(:fr)
=> "matous"

Sous le capot

Avant de détailler pluralize, sachez que les deux fonctions de nombre sont très similaires, seule la valeur de référence et de remplacement change, ainsi que le nom de la méthode appelée. Ainsi en détaillant pluralize il vous sera facile de comprendre singularize. Lorsqu’on écrit "user".pluralize dans Rails, on fait appel à la méthode pluralize définit sur la classe string d’ActiveSupport.

active_support/core_ext/string

def pluralize(count = nil, locale = :en)
  locale = count if count.is_a?(Symbol)
  if count == 1
    self.dup
  else
    ActiveSupport::Inflector.pluralize(self, locale)
  end
end

Ce premier pluralize nous permet d’appliquer la méthode directement sur notre chaîne en faisant un simple "string".pluralize plutôt que de la passer en paramètre.

Dans un premier temps, si on précise une langue (avec un symbole donc) sans préciser de nombre ex: "chien".pluralize(:fr), alors la valeur de local devient la valeur du premier paramètre count — ici le symbole :fr. C’est une petite astuce sympa lorsque l’on a deux paramètres facultatifs.

Ensuite, si on précise que count est égal à 1 ou que notre chaîne est vide, alors on retourne une copie de la valeur de notre instance, sinon on fait appel aux inflexions d’ActiveSupport.

active_support/inflector/methods

def pluralize(word, locale = :en)
  apply_inflections(word, inflections(locale).plurals)
end

La méthode pluralize nous sert ici à définir le type d’inflexion que recevra la méthode apply_inflections(). Dans ce cas l’inflexion sera inflections(locale).plurals. La méthode singularize changera simplement le .plurals en .singulars.

active_support/inflector/methods

def apply_inflections(word, rules)
  result = word.to_s.dup

  if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
    result
  else
    rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
    result
  end
end

Ici, on stocke dans result la valeur de word qu’on a d’abord pris soin de convertir en chaîne de caractère. Puis, on retourne result si la chaîne est vide ou si elle fait partie de la liste des invariables (uncountables en anglais). Dans le cas contraire, on boucle sur rules qui fait appel à la liste des inflexions pour remplacer la valeur sortante par son pluriel dans notre cas.

Par défaut, la liste d’inflexions de la gem Rails se présente comme ceci :

active_support/inflections

Inflector.inflections(:en) do |inflect|
  inflect.plural(/$/, 's')
  

  inflect.singular(/s$/i, '')
  

  inflect.irregular('person', 'people')
  

  inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
end

Bien entendu, beaucoup de règles remplacent les . Comme vous pouvez le voir, ces règles ont une forme similaire à celles définies dans notre application Rails. C’est en réalité la forme que reçoit la fonction .sub!() qui substitue nos chaînes grâce aux regexp. On remarque aussi que plusieurs types d’inflexions sont définis comme des irréguliers avec notre fameux person qui devient people. Pour en ajouter à votre application, il suffira de faire :

config/initializers/inflections.rb

inflect.irregular('singulier', 'pluriel')

Enfin, on retrouve les invariables qu’on ajoutera à la liste des uncountable en les séparant par un simple espace :

config/initializers/inflections.rb

inflect.uncountable(%w(beaucoup printemps temps))

Pour aller au bout de notre exploration, sachez que inflect.plural() est défini comme ceci :

active_support/inflector/inflections

def plural(rule, replacement)
  @uncountables.delete(rule) if rule.is_a?(String)
  @uncountables.delete(replacement)
  @plurals.prepend([rule, replacement])
end

@uncountables et @plurals sont des tableaux regroupant les règles définies par la liste des inflexions. Dans un premier temps la méthode supprime les doublons dans le tableau des invariables. Pour l’illustrer, si j’ajoute un invariable sur un mot ayant une définition au pluriel comme ceci:

active_support/inflector/inflections

ActiveSupport::Inflector.inflections(:fr) do |inflect|

  inflect.plural(/$/, 's')
  inflect.plural(/(hib|ch|bij|caill|p|gen|jouj)ou$/i, '\1oux')

  inflect.uncountable(%w(hibou))

end

Alors, la règle de pluriel l’emportera. Ici, “hibou” prendra toujours un “x”. Finalement, on ajoute rule et replacement dans notre tableau @plurals contenant toutes nos règles de pluriel traitées par la fonction .sub!().

Maintenant que vous connaissez “le truc”, il ne vous reste plus qu’à le partager en cliquant frénétiquement sur les boutons facebook, twitter et google+ !

L’équipe Synbioz.

Libres d’être ensemble.