Ruby on Rails



Mon premier projet réel avec MongoDB (Rails + MongoMapper)



Ecrit par Anthony Heukmes le 05 janvier 2010 16:01

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!



Premiers pas



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).




Les documents imbriqués



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é :






Associations



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.




Quelques finders



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 :






Tester



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




My plugins



Je terminerai ce long article en signalant deux plugins Rails que j'ai récemment développé :



- mongo_admin : génère une interface d'administration pour vos modèles MongoMapper. Une démo est disponible ici.

- mongo_translation : vous permet de très facilement traduire vos modèles MongoMapper en plusieurs langues.

Reverse scaffold depuis une table existante



Ecrit par Anthony Heukmes le 04 août 2009 15:21

3 commentaires



Si comme moi vous travaillez régulièrement avec des bases de données existantes, le script reverse_scaffold devrait vous intéresser.


Il vous suffit de le télécharger et le copier dans le répertoire "script" de votre application Ruby on Rails avant de l'exécuter de la manière suivante :

$ ruby script/reverse_scaffold users user


Je suppose ici qu'une table "users" est présente dans votre base de données et qu'elle possède les champs ci-dessous :


  • id

  • pseudo

  • email

  • password




Dans ce cas, le script exécutera la commande :

ruby script/generate scaffold user --skip-migration id:integer pseudo:string email:string password:string


Vous l'aurez compris, le premier argument de reverse_scaffold est le nom de la table tandis que le second est le nom du modèle à générer.


Si vous ne souhaitez pas exécuter le scaffold automatiquement mais juste récupérer la commande, vous pouvez préciser l'option -V.

Si vous êtes un utilisateur assidu de RSpec, il vous suffit d'ajouter l'option -r afin de générer un rspec_scaffold.



Pour plus d'informations :

$ ruby script/reverse_scaffold -h


Intégrer Ruby on Rails en entreprise



Ecrit par Anthony Heukmes le 29 mai 2009 11:24

1 commentaire



Lors de son excellente keynote à la RailsConf 09 "What killed Smalltalk could kill Ruby too", Robert Martin a souligné que Rails devait s'ouvrir à l'entreprise.
Il y a de nombreuses bonnes pratiques et conventions dans la communauté Ruby on Rails et je pense sincèrement qu'il s'agit du meilleur framework pour le développement web.
Mais peut-être est-il maintenant temps de s'ouvrir au monde extérieur. Nos pratiques ne sont pas respectées partout. D'autres personnes ont des besoins et visions différentes mais ce n'est pas pour cela que Ruby on Rails ne doit pas leur convenir.

Un exemple simple est lié aux clés primaires dans une base de données. Par défaut, les tables d'une application Rails possèdent un champ ID auto incrémenté agissant comme une clé primaire.
Dans sa version de base, Rails n'inclut aucun support pour les clés primaires composées. Un plugin permet d'ajouter cette fonctionnalité mais elle ne fait pas partie intégrante de Rails.
Si vous demandez pourquoi, il y a de fortes chances qu'on vous réponde que créer des clés primaires composées n'est pas une bonne pratique.
Ces clés primaires sont pourtant omni-présentes dans le milieu professionnel. Milieu peuplé par des bases de données Oracle qui ne respectent aucune de nos conventions. Et celles-ci ne vont certainement pas changer pour s'adapter à un nouveau framework.

C'est à Ruby on Rails à s'adapter à ce type de besoins si il souhaite s'imposer en entreprise.
Et heureusement, Rails est de plus en plus "enterprise ready" version après version.

Je suis récemment parvenu à intégrer Rails dans une grosse entreprise qui n'utilisait jusqu'alors que le Java. L'objectif de cet article est de partager les difficultés que j'ai rencontrées. Peut-être vous aideront-elles à faire de même et au final, à utiliser Rails au quotidien. Quel bonheur!

Le devéloppement d'une application Rails sur un schéma Oracle existant était ma principale crainte. J'avais toujours développé avec Rails des applications où le schéma évoluait au fur et à mesure et respectait donc les conventions d'ActiveRecord.
Après quelques recherches je me suis cependant très vite rendu compte qu'il était très facile de configurer votre modèle. Vous pouvez découvrir comment procéder en lisant un de mes articles consacrés à ce sujet. Il est également très facile d'invoquer des procédures stockées et autre joyeusetés de ce genre.

Le terme "langage de script" fait peur, principalement l'idée de lenteur qui leur est automatiquement associée.
N'hésitez pas à rassurer vos clients en leur montrant que de nombreux sites utilisent Rails, qu'ils ont un traffic important et qu'ils n'ont pas de problème de performances! Voici une liste pour vous aider.
Un autre argument à mettre en évidence est JRuby on Rails qui me semble être une excellente solution pour faciliter l'intégration de Rails en entreprise (voir ci-dessous). Les clients sont rassurés par le fait de savoir que Rails tourne sur la JVM et qu'ils ne vont donc pas totalement quitter le monde J2EE.

Autre problème majeur, les connaissances en Ruby et en Rails. Si vous proposez un nouveau framework Java, il y a de fortes chances qu'il soit accepté facilement. Tous les développeurs de l'entreprise connaissent Java.
Ruby en revanche ne leur dit rien et cela signifie donc qu'ils vont perdre plus de temps en apprentissage. Il ne faut également pas perdre de vue que beaucoup de développeurs ne sont pas passionnés par leur travail et n'ont pas envie d'apprendre une nouvelle technologie.
S'ajoute à cela le problème du recrutement. Il n'est déjà pas facile de trouver un bon développeur Java alors que dire d'un développeur Ruby!

J'ai personnellement pu contrecarrer ces craintes en signalant que Ruby et Rails sont très faciles à apprendre. Préparez une formation attrayante pendant laquelle vous partagerez votre engouement et votre passion pour ce framework. J'ai donné de telles formations à mes collègues qui ont pu très rapidement se rendre compte des avantages de Rails et surtout, que c'est un framework facile à prendre en main. J'ai alors très rapidement trouvé des alliés et je n'étais plus le seul à venter les mérites de Rails!
J'ai très souvent entendu des "c'est génial!", lorsque mes collègues développaient leurs premières applications de test et ils y ont visiblement pris beaucoup de plaisir.
Il m'est également arrivé de comparer des extraits de code Java et Ruby devant un client afin qu'il s'aperçoive par lui-même que la syntaxe de Ruby est claire et facile et que donc, l'application résultante sera plus facile à maintenir.

Un autre problème est le développement Ruby sur Windows. Je travaille personnellement sur Mac mais tous les autres développeurs sont sur Windows.
J'ai bien essayé Aptana et Netbeans mais ce n'est vraiment pas agréable à utiliser. Heureusement, RubyMine a désormais pointé le bout de son nez. C'est une excellente solution tout à fait abordable pour une entreprise.

Un gros problème est que Windows est même utilisé sur les serveurs de production. Et nous savons tous que le déploiement d'applications Rails sur Windows est douloureux.
Les outils pour gérer et monitorer les applications sont également peu nombreux et considérés comme des jouets comparés aux nombreux outils disponibles dans le monde Java.
Raison pour laquelle je suis actuellement en train d'investiger sur JRuby. L'utilitaire warbler permet de créer un war depuis une application Rails et donc de la déployer très facilement sur un serveur d'application Java EE. Je parlerai de ce sujet dans un de mes prochains articles.

Qui dit entreprise et Java dit également très souvent portal. Et une question qui se pose naturellement est la suivante : "Est-il possible d'intégrer une application Rails dans une portlet?". Je n'ai pas encore essayé personnellement mais j'ai lu que c'était réalisable.

Le framework Java utilisé jusqu'à maintenant par notre client (Oracle ADF) était basé sur les JSF et les utilisateurs étaient donc habitués à utiliser des composants riches, par exemple une table avec le tri et la pagination.
J'ai pour cela développé un plugin Rails permettant de générer très simplement des datagrids jQuery possédant toutes les fonctionnalités requises (ajax, tri, pagination, filtres, insertion, suppression, ...).

Enfin, je vais terminer en donnant quelques arguments qui ont bien fonctionné pour moi.

RSpec a été très apprécié! J'ai simulé en live un développeur qui faisait échouer un test et la combinaison autotest-growl a alors eu tout son effet!
La génération des specs au format "documentation" a également eu le don de charmer l'audience.
Je n'utilisais pas encore Cucumber à l'époque mais je suis certain que lui aussi peut faire une forte impression.

J'ai également créé un template de construction pour les nouveaux projets. Celui-ci contient le layout par défaut utilisé par la société ainsi que d'autres fonctionnalités communes. Le fait de pouvoir générer une application déjà évoluée en une seule commande a fait bonne impression.

N'hésitez pas à partager vos expériences... je serai heureux de les lire!

EdgeRails : Nouveau routeur, protect_from_forgery et dépendances entre modules



Ecrit par Anthony Heukmes le 17 avril 2009 16:16

0 commentaire



Le chemin vers Rails 3 suit son cours et vous pouvez découvrir beaucoup de nouveautés sur GitHub ces derniers temps.

Première bonne nouvelle, Carl Lerche & Josh Peek collaborent sur un nouveau routeur (un middleware Rack, qui pourra donc être utilisé par d'autres frameworks que Rails). Selon Carl, les performances sont deux fois supérieures à celles du routeur de Merb (et donc 4x supérieures à Rails).

Plus d'informations sont disponibles sur le groupe Google de Rack.

Une autre bonne nouvelle, nous n'aurons plus besoin d'inclure un "authenticity token" dans nos requêtes Ajax écrites à la main!
Si vous chipottez avec Ajax, vous avez certainement eu le plaisir d'obtenir l'erreur "Invalid Authenticity Token" après avoir tentez d'envoyer une requête Ajax de type POST.
La solution est d'ajouter ce fameux token dans les paramètres et de votre requête ou de désactiver la vérification des attacks CSRF dans votre contrôleur en utilisant protect_from_forgery :except => :my_action.
Avec Rails 3, plus rien de cela ne sera nécessaire!

La méthode touch a également été ajoutée aux modèles ActiveRecord.
Une fois invoquée, elle sauvera l'enregistrement courant dans la base en mettant à jour l'attribut updated_at.

Cela peut également être utilisé en combinaison avec une association de type belongs_to.
Lorsque l'instance est sauvée/supprimée, le champs updated_at de l'enregistrement associé sera donc lui aussi "touched".


belongs_to :company, :touch => true


Si vous suivez l'évolution du code internet de Rails, vous avez probablement été surpris par des mots-clés tels que setup, depends_on ou encore use.
Vous pouvez voir un exemple dans ce commit.

Le code source peut être trouvé dans ActiveSupport (activesupport/lib/active_support/core_ext/module/setup.rb) :


class Module
attr_accessor :_setup_block
attr_accessor :_dependencies

def setup(&blk)
@_setup_block = blk
end

def use(mod)
return if self < mod

(mod._dependencies || []).each do |dep|
use dep
end
# raise "Circular dependencies" if self < mod
include mod
extend mod.const_get("ClassMethods") if mod.const_defined?("ClassMethods")
class_eval(&mod._setup_block) if mod._setup_block
end

def depends_on(mod)
return if self < mod
@_dependencies ||= []
@_dependencies << mod
end
end


Il s'agit d'une façon beaucoup plus propre de gérer l'inclusion et les dépendances des modules. Yehuda Katz a écrit un article à ce sujet.

Ajoutez des datagrids très facilement à vos applications Ruby on Rails



Ecrit par Anthony Heukmes le 13 avril 2009 14:53

89 commentaires



Insérer des datagrids dans une application Ruby on Rails en quelques lignes de code est désormais possible.

Testez la démo de mon nouveau plugin : 2dcJqgrid.

Les datagrids sont basés sur un plugin jQuery et supportent les fonctionnalités suivantes :

- Ajax
- Pagination
- Tri
- Recherche
- Sélections multiples
- Master-Details
- Manipulation des données (création, édition et suppression)
- Et plus encore ...