4 commentaires
Cet article n'est pas une introduction à MongoDB ou MongoMapper.
Il s'agit plutôt d'un retour d'expérience après avoir finalisé un premier projet réel et conséquent reposant sur une base de données MongoDB.
Vous y trouverez différentes astuces vous permettant de faciliter votre apprentissage.
J'ai travaillé récemment sur un projet pour un client qui souhaitait vendre des véhicules d'occasion sur son site Internet. Il s'agit d'un cas assez classique, les visiteurs peuvent s'inscrire et poster une annonce décrivant le véhicule dont ils souhaitent se séparer.
Différents types de véhicules sont proposés : voitures, motos, camions et autres.
Chaque annonce est donc caractérisée par de nombreux attributs et ceux-ci sont variables en fonction du type de véhicule sélectionné.
Vous pouvez retrouver des informations générales telles que la marque et le kilométrage de l'engin mais aussi des renseignements sur son équipement (GPS? Radio?), son propriétaire, des photos, etc.
Il va de soi que l'équipement d'un camion n'est le même que celui d'une moto!
J'ai réfléchi un certain temps à la meilleure façon de structurer mon modèle SQL pour qu'il respecte au mieux ces exigences. Aucune des solutions imaginées ne m'offrait pleine satisfaction, elles étaient soit trop complexes à implémenter, à maintenir, ou ne me paraissait tout simplement pas suffisamment élégantes.
Etant accro aux nouvelles technologies, j'ai directement sauté sur l'occasion pour rejoindre le mouvement NoSQL et faire mes premières armes avec MongoDB, une base de données orientée "documents" de plus en plus utilisée par la communauté Ruby on Rails.
Quand on y pense, je trouve beaucoup plus élégant de représenter mes annonces comme des documents, contenant différentes fiches (informations générales, équipement, contact, photos...) plutôt que sous forme d'enregistrements dans différentes tables d'une base SQL.
Petite remarque en passant, mon projet Ruby on Rails utilise à la fois MongoMapper (MM) et ActiveRecord (AR) car j'y ai intégré mon CMS, le tout fonctionne parfaitement!
MongoMapper est très facile à prendre en mains car il respecte majoritairement l'API d'ActiveRecord. Vous ne serez donc pas jeté dans une monde inconnu car vous pourrez manipuler vos documents MM tout comme vous le faites avec vos enregistrements AR.
Que ce soit au niveau de la persistence ou de la sélection, vous pourrez toujours utiliser vos create, save, update_attributes, find, etc. Il en va de même pour les associations, les callbacks (before_create, ...) et la validation. Les vues et contrôleurs générés par un scaffold sont par exemple utilisables sans modification avec MongoMapper (MM).
Ne croyez cependant pas que l'adaptation de MM va s'effectuer en 1h car le problème est que votre cerveau est formaté pour utiliser du SQL.
Durant le développement de votre projet vous allez vous poser des questions auxquelles vous n'aurez pas immédiatement les réponses.
Vous allez par exemple vouloir effectuer une recherche sur un champs texte. En SQL vous savez que vous devez utiliser un LIKE, mais comment procéder avec MongoDB?
Il vous faudra souvent chercher pour trouver ces réponses car MongoMapper est un projet récent et toujours en développement intensif. Il n'existe donc pas de documentation exhaustive, pas de livre et rares sont les blogs à traiter le sujet. Le meilleur endroit pour trouver réponse à vos questions est le groupe Google.
Une fois que vous aurez appréhendé la bête, il y a de fortes chances pour que vous ne vouliez plus la quitter.
Après deux mois intensifs de Mongo j'ai du me remettre à travailler sur un projet ActiveRecord et c'est probablement seulement à cet instant que j'ai vraiment réalisé à quel point MongoMapper était agréable.
Le plus difficile étant de se rappeler la syntaxe des très ennuyeuses migrations! Car oui, avec MongoMapper, elles sont devenues totalement inutiles!
L'inconvénient majeur de MongoMapper est probablement que vous perdez la plupart de vos plugins spécifiques à ActiveRecord. J'utilise par exemple toujours authlogic dans mes différents projets mais j'ai ici du m'en passer. Je sais cependant que l'auteur de ce plugin travaille actuellement pour le rendre compatible. Certains plugins marcheront quant à eux sans problème, c'est par exemple le cas de will_paginate.
Certaines fonctions d'ActiveRecord désormais très utilisées ne sont pas non plus disponibles, les named_scope par exemple (mais c'est au programme).
MongoDB vous permet de créer des documents imbriqués. Dans mon cas, une annonce peut par exemple contenir plusieurs photos :
Vous risquez au début de vous demander quand utiliser un document imbriqué ou non.
Pour vous aider à répondre à cette question, je vais surtout vous indiquer quand ne pas utiliser un tel document.
Il est important de savoir qu'un EmbeddedDocument possède des fonctionnalités très limitées.
Vous ne pouvez par exemple pas invoquer la méthode find sur un tel objet.
Ainsi, les deux instructions suivantes ne sont pas valides :
Si vous souhaitez donc avoir la possibilité de manipuler un document de manière indépendante, il ne faut absolument pas le créer en imbriqué.
Voici du code valide vous permettant de manipuler des documents imbriqués. Vous remarquerez qu'il s'agit de code Ruby pure, rien de bien spécifique à MongoMapper.
Pour la création :
Pour sélectionner des enregistrements suivants différents critères :
a.pictures retourne un tableau Ruby classique, la méthode select est donc celle de Ruby et n'est pas particulière à MongoMapper.
Pour modifier un document imbriqué :
Remarquez qu'il faut appeler le save sur le document principal (ici l'annonce).
Enfin, pour supprimer un document imbriqué :
Vous risquez de vous arracher les cheveux si vous spécifiez vos "clés étrangères" comme étant des String.
Vous trouverez de nombreux exemples pratiquant comme cela, mais ils ne sont plus valides avec les nouvelles versions de MongoMapper.
Vous devez à présent utiliser le type ObjectId.
Par exemple :
Cette association ne marchera pas si vous utilisez un String à la place d'un ObjectId.
Vous risquez également de chercher après une association de type "has_one" mais elle n'existe pas.
La raison est simple, MongoMapper permet de définir des attributs de n'importe quel type.
Ainsi, si vous souhaitez spécifier qu'un utilisateur possède un profil (has_one), il vous suffit de définir votre clé comme suit :
La classe Profile étant par exemple ici un EmbeddedDocument.
Revenons-en à une de mes premières questions, comment effectuer une recherche de type LIKE avec MongoMapper?
En utilisant les expressions régulières!
Tous les utilisateurs possédant "thony" dans leur adresse email seront ainsi retournés.
Le /i permet de signaler que nous souhaitons être insensible à la casse.
Vous l'aurez compris, si vous révisez vos expressions régulières en Javascript, vous pourrez effectuer des recherches très poussées!
Si vous souhaitez utiliser une date et récupérer par exemple tous les utilisateurs qui ont été créés aujourd'hui, vous pouvez utiliser $gt (greater than) et $lt (lower than) :
Enfin, imaginons que vous souhaitiez récupérer tous les articles dont le titre OU la description contiennent le mot recherché.
Nous souhaitons donc ici générer un OR plutôt qu'un AND :
J'utilise abondemment Machinist pour créer des données de test dans mes specs.
Fort heureusement, un adaptateur existe pour MongoMapper et vous pouvez le trouver sur GitHub.
Il vous suffit d'installer la gem (gem install machinist_mongomapper) puis de modifier votre fichier blueprints.rb pour y charger le bon adaptateur (require 'machinist/mongomapper').
Vous aurez aussi remarqué que les IDs utilisés par MongoDB ne sont pas de simples chiffres.
Si vous avez besoin d'un ID valide dans vos tests, la méthode suivante est faite pour vous : Mongo::ObjectID.new
Je terminerai ce long article en signalant deux plugins Rails que j'ai récemment développé :