Bonjour !
Il y a quelques jours a eu lieu la première faille critique de Ruby (quel produit informatique n'en a pas ?) publiée alors que je disposais d'un serveur faisant tourner une appli Ruby on Rails en production. Il m'a donc fallu apprendre rapidement comment mettre à jour Ruby sur le dit serveur.
Chouette chouette chouette…
Pour ceux qui comme moi se trouveraient face à cette situation pour la première fois, je me suis dit qu'il pourrait être utile de partager le détail de la manoeuvre que j'ai faite sur mon serveur.
Il existe certainement de meilleures procédures, notamment à base de Chef/Puppet/Docker ou je ne sais quoi d'autre, mais je préfère encore faire les choses de manière artisanale pour le moment - et puis, je ne suis pas encore un expert du Ruby :-)
Plutôt que de lister bêtement les commandes à taper, j'explique un peu à quoi sert chacune ; mais si on est pressé on peut juste taper les commandes l'une après l'autre, en adaptant seulement le numéro de patch de Ruby 2.0 :-)
Le tout est à faire avec l'utilisateur avec lequel on a installé rbenv (ici “admin”). On aura besoin de “sudo” uniquement à la fin, pour éditer 2 fichiers de conf d'Apache puis le redémarrer.
Au lieu d'utiliser la version de Ruby proposée par les dépôts Debian, qui ne sont pas très à jour et qui peuvent poser problème si on a plusieurs applis Ruby ayant des dépendances différentes, j'ai utilisé un des 2 standards de Ruby en la matière : rbenv.
Rbenv sert à pouvoir installer plusieurs versions de Ruby sur une seule machine, chacune isolée dans son coin avec ses propres libs etc.
Il est également muni d'un plugin “ruby-build” qui sert à télécharger, compiler et installer différentes versions de Ruby.
Si une nouvelle faille critique est découverte, donc.
A priori le correctif de Ruby proposé via le plugin “ruby-build” sera disponible très vite (le correctif de cette faille-ci, par exemple, a été disponible pour installation via ruby-build dans les heures qui ont suivies la publication de la faille).
La première chose à faire est de donc mettre à jour ce plugin “ruby-build”, ce qui aura aussi pour effet de mettre à jour sa liste des versions de Ruby dispos :admin@prod:$ cd ~/.rbenv/plugins/ruby-build/
admin@prod:$ git pull
On vérifie que la version corrigeant la faille de sécurité est disponible :admin@prod:$ rbenv install -l
Cette commande affiche toutes les versions dispos (y en a pas mal). La version de Ruby qui permet de faire fonctionner mon application actuelle est soit une 1.9.x, soit une 2.0. Cette dernière étant plus performante, on va rester sur une “2.0.”.
Pour la 2.0, donc, le correctif d'aujourd'hui est par exemple le patch 353, comme expliqué dans la note de ruby-lang.org révélant la faille de sécurité.
On vérifie donc qu'on a bien “2.0.0-p353” qui s'affiche dans la liste.
C'est le cas ? Super, alors on va pouvoir demander à “ruby-build” de télécharger son code source, de la compiler et de l'installer :admin@prod:$ rbenv install 2.0.0-p353
On va se faire un café, un thé, on règle la facture d'électricité dont on procrastinait le paiement depuis une semaine, ou l'on vaque à toute autre occupation prenant le temps d'une compilation (sur mon serveur prod ça a pris 6-7 minutes).
On vérifie que cette nouvelle version est bel et bien disponible en local à présent :admin@prod:$ rbenv versions
Et là normalement on voit notre nouvelle mouture de Ruby parmi la liste des versions installées en local. La petite astérisque indiquant la version active par défaut est toujours devant la version de Ruby précédente.
Ainsi, si on demande à Ruby d'afficher sa version, on a toujours la précédente qui s'affiche pour le moment :admin@prod:$ ruby -v
ruby 2.0.0p247 (2013年06月27日 revision 41674) [i686-linux]
On se rend dans le répertoire de l'appli Ruby on Rails.admin@prod:$ cd ~/my-wonderful-rails-website---prod
On dit à rbenv qu'on souhaite toujours que ce soit cette version qui soit active dès lors qu'on est dans ce répertoire :admin@prod:$ rbenv local 2.0.0-p353
Cela crée un fichier “.ruby-version” dans le répertoire, qui est détecté par “rbenv”. Si on redemande la version de Ruby alors qu'on se trouve dans ce répertoire, on a alors bien la nouvelle version qui s'affiche :admin@prod:$ ruby -v
ruby 2.0.0p353 (2013年11月22日 revision 43784) [i686-linux]
On va à présent installer les bibliothèques Ruby requises par l'appli Rails. Pour cela on commence par installer la gem (bibliothèque Ruby) “bundler”, qui permet d'installer d'autres gems d'après une liste de gems spécifiées dans un fichier “Gemfile” (un peu l'équivalent de Composer et de son fichier “composer.json” en PHP) :admin@prod:$ gem install bundler
Puis on demande à Bundler d'installer les gems de notre appli Ruby on Rails, d'après le contenu du fichier Gemfile qui se trouve dans notre répertoire “admin-rails-prod” :admin@prod:$ bundle install
Ces gems étaient bien sûr déjà présentes sur le système, mais… uniquement pour notre version précédente de Ruby ! Grâce à “rbenv” les différentes versions sont bien cloisonnées, et elle ont chacune leurs propres gems. Il faut donc réinstaller les gems dont dépend l'appli Rails chaque fois qu'on installe une nouvelle version de Ruby.
Là normalement on voit plein de messages “installing XXX” défiler, puis le shell nous rend la main : toutes nos gems sont de nouveau installées sur notre environnement Ruby flambant neuf !
Tiens tant qu'on y est, on va dire à “rbenv” que la version par défaut de Ruby activée où qu'on se trouve sur le serveur sera notre nouvel opus :admin@prod:$ rbenv global 2.0.0-p353
C'est bientôt fini. Il faut encore mettre à jour Phusion Passenger, a.k.a. “mod_rails”. C'est lui qui me permet de faire tourner l'appli Rails directement depuis Apache, sans avoir à utiliser un serveur d'application spécifique.
Mon premier projet Rails devant s'interfacer avec un site existant tournant en environnement LAMP, il fallait que je garde Apache comme base. Et même si j'ai vu Phusion Passenger décrié ça et là, en ce qui me concerne je suis très satisfait par le service rendu - je l'ai d'ailleurs utilisé pour des petits projets Node.js et Django en parallèle, depuis que je l'ai découvert grâce à Ruby on Rails :-)
On a mis à jour Ruby sur la machine, c'est super, mais Passenger continue donc lui de tourner avec l'ancienne version. Damn!
On suit donc la procédure d'install de Passenger, qu'on réapplique dans notre nouvel environnement Ruby. On commence par aller chercher le code source de Passenger :admin@prod:$ gem install passenger
Puis on effectue la compilation du module pour notre nouveau Ruby :admin@prod:$ passenger-install-apache2-module
On appuie sur “Entrée” pour confirmer, et attend la fin du bouzin. C'est beau comme une compilation de programme C/C++ (hem…). Si vous avez envie d'un deuxième café, c'est maintenant.
Normalement toutes les dépendances de Passenger sont déjà installées sur le serveur, ça devrait rouler. Dans le cas contraire, il nous signale quels packages Debian on doit installer avant de relancer cette commande.
Quand il a fini, il nous donne 3 lignes à copier-coller dans nos fichier de conf Apache. Là par exemple, ça donne :
The Apache 2 module was successfully installed.
Please edit your Apache configuration file, and add these lines: LoadModule passenger_module /home/admin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/passenger-4.0.25/buildout/apache2/mod_passenger.so PassengerRoot /home/admin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/passenger-4.0.25 PassengerDefaultRuby /home/admin/.rbenv/versions/2.0.0-p353/bin/ruby
After you restart Apache, you are ready to deploy any number of Ruby on Rails applications on Apache, without any further Ruby on Rails-specific configuration!
On édite donc nos 2 fichier de conf Apache:admin@prod:$ sudo vi /etc/apache2/mods-available/passenger.load
On y colle la première ligne que nous avait donnée Passenger, celle commençant par “LoadModule”.
On réitère avec l'autre fichier :admin@prod:$ sudo vi /etc/apache2/mods-available/passenger.load
Et on y met les 2 lignes suivantes, commençant par “PassengerRoot” et “PassengerDefaultRuby”.
Et voilà, y a plus qu'à croiser les doigts et redémarrer Apache :admin@prod: sudo service apache2 restart
Ca paraît un peu long comme ça, mais en réalité c'est assez vite fait ; le plus long c'est la compilation de Ruby puis celle de Passenger :-)
Hop! this helps!
Bonjour !
Après ce long silence de quelques mois, me revoici. Mon mutisme n'était pas dû à un abandon de mon projet de me mettre à Ruby, puisque bien au contraire c'est le rythme trépidant de mon premier projet Rails qui m'a fait délaisser le récit de mon retour d'expérience.
Cinq mois après avoir commencé mon apprentissage, et après avoir travaillé à temps plein sur Rails pendant tout ce temps, je peux à présent faire un retour plus complet et peut-être moins naïf de l'expérience.
Certes, les premiers pas avec Ruby n'ont pas forcément été des plus aisés. C'est que le bougre est tout de même assez différent des languages que j'avais principalement pratiqués jusqu'alors, qui hormis TCL/Tk avaient tous plus ou moins les mêmes règles syntaxiques.
Ruby ne constitue pas une rupture totale avec les langages “à la C” que sont PHP, JavaScript ou ActionScript, comment peuvent l'être des langages comme Lisp, mais il y a quand même un temps d'adptation à prévoir.
Qui plus est, l'API de base du langage est extrêmement fournie ! Là où JavaScript fournit quelque chose comme une trentaine de méthodes sur les chaînes de caractères, Ruby nous en propose pas loin d'une centaine (je n'ai pas compté, c'est mon estimation à la grosse louche :-). Ajoutez à cela une bibliothèque standard très fournie, et vous êtes bons pour plusieurs mois de pratiques avant de commencer à avoir une connaissance raisonnable de l'API de Ruby.
Concernant la syntaxe, j'ai pu m'adapter sans trop de peine grâce à mon utilisation antérieure de CoffeeScript - celui-ci étant lourdement inspiré par Ruby, cela aide grandement.
So… Après 5 mois passés à coder en Ruby à longueur de journée, après plus d'une dizaine d'années de développement PHP, où en suis-je de ma perception de ce langage ?
Eh bien pour ma part, je suis totalement conquis ! ♥
J'ai justement retouché à du code PHP il y a quelques jours, et j'ai bien eu du mal à me remettre à la logique “langage orienté objet mais à l'ADN fonctionnel” de PHP !
Comme en JavaScript, il me paraît tellement plus naturel de faire un maChaine.downcase pour mettre une chaîne de caractères en minuscules que de devoir recourir à un strtolower($maChaine) !
De même, j'ai passé de nombreuses années à sagement mettre des points-virgules à la fin de chacune de mes lignes de code - y compris en JavaScript, où ceux-ci sont facultatifs dans l'immense majorité des cas. Mais après 5 mois de développement passés sur Ruby pour le back-end et CoffeeScript pour le front-end, j'ai trouvé soudain si lourd de devoir utiliser partout ces points-virgules en revenant sur du code PHP !
La syntaxe de Ruby, son API et ses bonnes pratiques sont vraiment un grand plaisir à utiliser au quotidien. Utiliser ce langage de pair avec CoffeeScript est pour moi une réelle joie. Ceci est tout-à-fait subjectif, et d'autres personnes pourront continuer à préférer coder en PHP, mais pour ma part je suis pleinement séduit. ❀
Et pourtant, avant de m'y mettre CoffeeScript comme Ruby me paraissaient foncièrement hideux lorsque je tombais sur du code écrit dans ces langages. Mais je suis bien content aujourd'hui d'avoir pu dépasser ces premières impressions :-)
Je vous parlais de la richesse de l'API Ruby, mais celle-ci n'est que peu de choses comparée à celle de Ruby on Rails ! Le framework est immense, et la portée du champ fonctionnel qu'il couvre est démultipliée par les nombreuses bibliothèques Ruby (les Gems) que l'on ne manque pas de lui associer.
J'avoue avoir passé énormément de temps sur StackOverflow et consorts les 2 premiers mois, tellement j'étais largué. Les guides Ruby on Rails sont excellents, à mon humble avis largement du même niveau que des modèles du genre comme ceux de Symfony 2 ou Zend Framework. Mais le framework est tellement riche que conjugué à l'apprentissage simultané de Ruby les débuts ne sont vraiment pas faciles.
Créer ses premiers Contrôleurs, Modèles et Vues est pourtant aisé, tant cela est abondamment expliqué sur des ressources comme les Guides Rails ou le Rails Tutorial.
De plus, si comme moi vous êtes déjà habitués à la logique MVC de frameworks comme Symfony, Laravel ou Zend Framework (tous largement inspirés dans leurs premières versions par Rails, rappelons-le ;-), cela ne devrait pas vous poser trop de problème.
La partie la plus difficile à maîtriser est le Modèle, puisque si vous avez une base de données fournie (ce qui était mon cas, puisque mon premier projet Rails devait inteagir avec un projet PHP existant comptant plus d'une centaine de tables MySQL) il va falloir éplucher les documentations pour découvrir comment faire telle ou telle jointure spécifique avec l'ORM de Rails.
Mention spéciale à ce dernier, en passant : j'ai beau avoir pratiqué des ORM PHP comme Doctrine ou celui de Zend Framework, j'ai trouvé l'ActiveRecord de Ruby on Rails bien plus agréable et concis dans on utilisation. Cela est sûrement dû en grande partie aux mécanismes spécifiques permis par Ruby en termes de méta-programmation et de concision du code, qui ont permis aux concepteurs de Ruby on Rails de créer un outil puissant et très agréable à utiliser une fois qu'on commence à s'y retrouver.
Je profite également de ce débriefing “5 mois plus tard” pour revenir sur l'avis fort mitigé que j'avais émis au sujet du système de rendu HTML HAML, et je lui adresse aujourd'hui mes publiques excuses.
A l'usage, il est réellement très bien foutu, et permet une productivité démentielle par rapport à l'écriture de code HTML classique.
J'avais à l'origine cherché un système de templating proche de Twig, étant habitué à celui-ci dans mes derniers projets Silex et Symfony, et je m'étais finalement rabattu sur HAML par dépit.
Aujourd'hui pourtant, il me paraît tellement poussif de devoir gérer le code HTML comme on le fait dans des moteurs comme Twig ! Je ne jette pas la pierre à Twig, attention ; je pense toujours que celui-ci est un très bon outil de templating, plein de bonnes idées.
Je fais du HTML depuis 1998, et je ne pensais pas dire cela un jour, mais aujourd'hui je ne me vois plus à présent devoir rédiger à la main mon code HTML !
Avec des moteurs comme HAML, Slim ou Jade en Node.js (dont il existe d'ailleurs une implémentation PHP), finies l'écriture des chevrons “<” et “>”, les balises de fermeture et tout le toutim. On a juste à écrire les ouvertures de balises, et leurs imbrications sont uniquement gérées par l'indentation.
Une fois habitué, le code est bien plus lisible, et il est surtout bien plus souple ! Il suffit de déplacer des lignes et de changer leur indentation avec les raccourcis clavier de son IDE préféré pour ré-agencer sa mise en page, de manière bien plus rapide qu'on ne le ferait en HTML “traditionnel”.
Les débuts sont déconcertants, et j'étais le premier à être réfractaire à cette manière de faire de l'HTML, mais aujourd'hui je recommande chaudement ce système. L'essayer, c'est ne plus revenir en arrière !
Tâcher de découvrir les bonnes pratiques pour tout le code autour de ces Contrôleurs, Modèles et Vues m'a donné pas mal de fil à retordre, et mille fois sur le métier j'ai remis mon ouvrage - dit plus prosaïquement, je me suis tapé pas mal de refactorisations :-)
En Symfony 2 par exemple on a tout un système de Services, accessibles via un Service Locator omniprésent. On a également un système d'annotations, et des fonctionnalités d'injections de dépendances.
Avec Ruby on Rails, ces design patterns ne sont pas utilisés, aussi faut-il apprendre à composer autrement. Heureusement, en lisant des ressouces comme les Rails Casts (certains screencasts sont payant, mais cela vaut vraiment le coup, et je ne regrette pas d'avoir suivi les recommendations des Humans Coders Matthieu et Camille lors de ma formation Ruby chez eux :-) et en épluchant le code source de nombreuses applications basées sur Ruby on Rails sur GitHub, on arrive à prendre ses marques.
J'ai également complété mon apprentissage avec les fantastiques ressources de Code School, dont le mantra est “learn by doing”. Les premières leçons interactives sont gratuites, à l'instar de la fameuse Rails for Zombies qui m'avait convaincue de tenter l'aventure Ruby on Rails. Mais je ne regrette pas les quelques dizaines d'euros consacrés à un abonnement payant donnant accès à toutes les ressources de Code School. Elles permettent un solide apprentissage de bonnes pratiques.
Je travaille seul sur ce premier projet Rails, aussi n'ai-je pas de recul ni l'avis d'un Rubyiste chevronné, mais je crois que j'ai malgré tout aujourd'hui une structure applicative assez propre :-)
Je ne prétends pas avoir fait le projet Ruby on Rails le plus propre du monde, et peut-être un vétéran Rails trouverait-il moult à redire à ce que j'ai fait, mais de mon humble point de vue je suis assez satisfait de l'organisation de mon application, et j'ai appris à ne plus architecturer mon code au moyen de Services Locators omniprésents comme je m'étais habitué à le faire avec Silex ou Symfony 2.
Après ces 5 premiers mois passés à l'apprentissage de Ruby et de Rails, et même si mon premier projet Rails n'est pas encore terminé, je peux déjà dresser un premier bilan :
return false if user.notFound, mais avec Ruby je me suis habitué à les utiliser et elles me semblent aujourd'hui tellement naturelles que je regretterai cruellement leur abscence dans des langages comme PHP ou JavaScript.J'étais initialement très réticent à l'idée de me mettre à Ruby, notamment parce que sa syntaxe me rebutait, et j'ai tout de même passé pas mal de temps sur StackOverflow les premiers mois.
Mais ce ce qui me concerne l'effort en valait réellement la peine, et je ne saurais que recommander chaudement à n'importe quel confrère développeur PHP de tenter l'aventure !
Bonjour !
Aujourd'hui je m'attaque à la découverte et à la personnalisation de Sprockets, le gestionnaire d’assets de Ruby on Rails : celui-ci permet d'ajouter des traitements automatisés aux fichiers CSS et JavaScript de nos application Rails. C'est la “Asset Pipeline”.
Si vous avez déjà travaillé avec Symfony 2 vous vous êtes déjà très probablement frotté à Assetic, son équivalent PHP. Je ne suis pour ma part pas un grand fan à la base de ce genre de solutions, puisque je préfère généralement maîtriser la chaîne des assets de A à Z.
Mais puisque Rails 3 est livré par défaut avec Sprockets activé (ce ne sera apparemment plus le cas pour Rails 4, où Sprockets sera devenu une entité optionnelle à part entière) et que j'ai l'intention de découvrir dans les détails les rouages de RoR, je me suis prêté au jeu.
Le premier avantage de Sprockets est de pouvoir utiliser très facilement des surcouches à JavaScript et CSS, sans avoir à configurer quoi que ce soit.
Concernant JavaScript, il y a encore 2 écoles, et celle de la “surcouche”, qu'elle se nomme CoffeeScript, TypeScript, Dart ou que sais-je encore est loin d'avoir gagné la bataille.
Libre à chacun donc d'utiliser le “vrai” langage JavaScript ou l'une de ses surcouches. Pour ma part, je suis passé à CoffeeScript il y a un peu plus d'un an, et je trouve que ma productivité y a vraiment gagné.
Pour les feuilles de style en revanche, il semblerait que plus personne aujourd'hui n'utilise encore le langage CSS tel quel. Le succès stratosphérique du kit de démarrage Twitter Boostrap et son utilisation de LESS y sont probablement pour beaucoup.
Jusqu'ici j'utilisais LESS, mais puisque SASS est le choix privilégié des Rubyistes (il est programmé en Ruby :-), qu'il n'est pas dénué d'avantages et qu'il est doté de sa propre surcouche sympathique (Compass) je vas donc opter pour SASS.
Avec Sprockets on gère ses assets (fichiers JavaScripts, feuilles de styles et images) dans le dossier “app/assets” et ses 3 sous-dossiers “images/”, “javascripts/” et “stylesheets/”. Ces 3 dossiers sont transparents : ils ne sont là que pour nous permettre d'organiser proprement nos assets, et ne seront pas visibles dans l'URL.
Ainsi, un fichier “app/assets/javascripts/config.js” sera accessible à l'URL “/assets/config.js”, et une feuille de style “app/assets/stylesheets/common/footer.css” à l'URL “/assets/common/footer.css”.
On écrit pas ces URLs en dur, mais on utilise à la place les helpers de Vue que Rails met à notre disposition pour cela, comme stylesheet_link_tag, javascript_include_tag ou image_tag. L'intérêt de la manoeuvre, comme avec Assetic en PHP, est d'avoir chaque asset appelé séparément en mode “development” et de les voir compactés en un seul fichier lorsqu'on est en mode “production” sans avoir à changer la moindre ligne de code.
Tout ceci est expliqué en détail sur le guide RoR correspondant.
Ho, j'en profite pour signaler une astuce très intéressante que j'ai découverte récemment. Il se trouve que je bosse en ce moment depuis l'étranger, et que j'aime me plonger dans l'atmosphère locale en allant bosser dans un café.
Ce café dispose d'une connexion WiFi mais celle-ci est de médiocre qualité, et a une fâcheuse tendance à se couper de manière impromptue au moment où j'avais un besoin crucial de la documentation Rails.
Grâce à RubyMine je peux circuler avec fluidité dans les classes RoR, à coups de “Ctrl+clic” sur les différents méthodes qui m'intéressent, et me servir de l'auto-documentation des fonctionnalités dont j'ai besoin. Mais la synthèse des Guides Rails est parfois tout de même bien utile.
Alors que je m'étonnais qu'il n'existe pas de version téléchargeable de ces guides (hormis au format e-book), j'ai compris grâce à l'irremplaçable duo Google/StackOverflow qu'on pouvait tout simplement les générer en local !
Pour cela, Rails a besoin de la gem RedCloth. J'ajoute donc celle-ci à la section “development” de mon fichier Gemfile, je l'installe avec la commande bundle install, et je n'ai plus qu'à demander la génération des Guides Rails dans le dossier “doc/guides” de mon application :
rake doc:guides
Yee-ah!
Tout ceci est fort pratique, mais n'est que la base de Sprockets , qui permet donc d'utiliser des surcouches comme SASS ou CoffeeScript de manière très simples, grâces à différents engines.
Chacun va en gros lire le fichier demandé, l'interpréter et renvoyer le résultat afin qu'il soit traité par l’engine suivant si nécessaire.
Chaque engine est déclenché automatiquement par Sprockets lorsque telle ou telle extension de fichier est trouvée. Ainsi, pour faire du CoffeeScript on nommera notre fichier “my_widget.js.coffee” : cela indique à Sprockets qu'on souhaite qu'il déclenche le moteur CoffeeScript pour ce fichier avant de le livrer au navigateur. De même, une feuille de style “my_widget.css.scss” sera automatiquement passé à la moulinette SASS.
Et on peut même combiner différents moteurs sur un fichier ! Un fichier “config.js.coffee.erb” verra ainsi son contenu traité par ERB, ce qui permet de faire une passerelle avec Ruby, puis le résultat est transféré au moteur CoffeeScript.
L'URL de l'asset, bien sûr, ne change pas : mon fichier “app/assets/javascripts/app/config.js.coffee.erb” sera toujours accessible via l'URL “/assets/app/config.js”.
Bien sûr, utiliser ERB partout dans ses fichiers Javascript ou CSS n'est pas une bonne idée, et il vaut mieux utiliser le mécanisme avec parcimonie.
Pour ma part j'ai ajouté l'extension “.erb” à 2 fichiers seulement : un “app/config.js” qui me permet de passer certaines variables de config Ruby à JavaScript (je gère ces variables via la gem Gaston, simple et efficace), et un “app/i18n.js” afin de passer certains textes à JavaScript.
J'ai malheureusement rencontré un bug assez ennuyeux avec ce dernier, qui m'a tenu en haleine pendant pas loin de 2 heures. J'ai donc un fichier “app/assets/javascripts/amd-modules/app/admin/i18n.js.coffee.erb”, avec le contenu suivant :
define [], () ->
i18n = {}
i18n.title = <%= I18n.t('title').to_json %>
i18n.app = {}
i18n.app.admin = <%= I18n.t('emailing.admin').to_json %>
i18n
Lorsque je l'appelle via son URL “/assets/amd-modules/app/admin/i18n.js”, j'ai malheureusement le vilain résultat suivant au lieu du contenu de mon fichier :
throw Error("Encoding::InvalidByteSequenceError: \"\\xC3\" on US-ASCII ...
Je ne sais qui d'ERB, CoffeeScript ou de Sprockets est responsable de cela, mais d'après ce qu'en comprends il y a un soucis avec le fait que mon fichier CoffeeScript n'utilise aucun caractère accentué (il est donc visiblement considéré comme encodé en “US-ASCII”) alors qu'avec ERB j'injecte dedans des chaînes de caractères qui elles vont être encodées en UTF-8 comme il se doit.
La solution que j'ai trouvée après deux heures d'investigation manque de panache, mais elle est simple et résout le problème : ajouter une chaîne de caractère accentuée au fichier CoffeeScript, afin de forcer l'encodage du fichier lui-même en UTF-8 :
define [], () ->
# forces UTF-8 encoding of the file (2 hours spent on this damned bug!!!)
# hé !
i18n = {}
# ... ici le même code que précédemment
i18n
La chaîne de caractères “hé” est un commentaire CoffeeScript, et n'apparaîtra même pas en commentaire dans le fichier JavaScript envoyé au navigateur. J'y retrouve bien à présent mes textes au format JSON.
La mode est aux frameworks JavaScript MVC lourds comme Angular, mais je suis déjà en train d'ingurgiter beaucoup de choses avec mon apprentissage de Ruby et de Rails et je ne vais pas m'y mettre tout de suite. Je reste donc pour l'instant sur ce que je pratique depuis quelque temps, à savoir la combinaison Backbone.js pour le MVC et RequireJS pour la modularité et la gestion automatisée des dépendances.
J'ai en ce qui me concerne l'habitude d'utiliser un schéma simple basé sur RequireJS : certains de mes éléments HTML sont dotés d'un attribut “data-amd-widget”, dont la valeur est le chemin d'un module RequireJS.
Pour chacun de ces éléments HTML, le module correspondant est chargé, puis une méthode “createWidget()” de ce module est lancée, avec en paramètre l'élément HTML en question, enveloppé par jQuery.
J'avais découvert cette approche via ce billet de blog, et j'en suis assez fan : c'est simple, modulaire, et cela permet le lazy loading des fonctionnalités d'une page HTML.
Concrètement j'ai donc mon module RequireJS principal, chargé en fin de page, qui va déclencher la “widgets factory” :
# fichier "app/assets/javascripts/amd-modules/app/main.js.coffee"
define (require, exports, module) ->
$ = require "jquery"
logger = require "util/logger"
myDebug = !false
myDebug && logger.debug "#{module.id} ::", "On the bridge, captain!"
moduleActivator = require('util/widgets-factory')
$(document).ready () ->
moduleActivator.execute()
exports
Et la “widgets factory”, donc :
# fichier "app/assets/javascripts/amd-modules/util/widgets-factory.js.coffee"
##
# @see http://paceyourself.net/2011/05/14/managing-client-side-javascript-with-requirejs/
##
define (require, exports, module) ->
$ = require "jquery"
logger = require "util/logger"
myDebug = !false
myDebug && logger.debug "#{module.id} ::", "on the bridge, captain!"
loadModule = ($jqElement) ->
moduleName = $jqElement.data("amd-widget")
require [moduleName], (module) ->
module.createWidget($jqElement)
exports.execute = ($widgetsContainer) ->
$widgetsContainer = $widgetsContainer || $("html")
$dataModules = $("[data-amd-widget]", $widgetsContainer)
nbDataModules = $dataModules.length
myDebug && logger.debug "#{module.id} ::", "#{nbDataModules} widgets found."
i = 0
while i < nbDataModules
loadModule( $dataModules.eq(i) )
i++
exports
Chaque module de “widget” peut ensuite à sa guise implémenter sa fonction “createWidget()” sous la forme d'une Factory de Vue Backbone, ou bien de code JavaScript brut, etc. C'est une approche qui se veut pragmatique.
J'ai tout de même un soucis avec cette approche : comme vous le voyez j'utilise la syntaxe “Common JS” des modules RequireJS, et l'identité de chaque module est déterminée automatiquement par RequireJS au runtime, selon le chemin du module. Cette manière de faire comporte 2 inconvénients :
rake assets:precompile.Pour y remédier, et également dans l'optique de mieux comprendre les rouages de Sprockets, j'ai donc créé mon propre moteur d'assets Sprockets.
Donner ici le code source du moteur lui-même n'a que peu d'intérêt, et ce d'autant plus que le code n'est pas très propre. Si j'en ai le temps plus tard, je le nettoierai et l'empaquetterai sous forme de Gem.
Mon propos est plutôt ici de montrer la démarche telle que je l'ai comprise à travers différentes sources sur le Net, afin de faire gagner du temps à ceux nouveau venus sur Rails comme moi qui voudraient aux aussi pouvoir utiliser ce type de mécanisme :
# fichier "lib/acme/sprockets/requirejs_template.rb"
##
# Based on https://github.com/sstephenson/sprockets/blob/master/lib/sprockets/ejs_template.rb
##
require 'tilt'
module Acme
module Sprockets
class RequireJSTemplate < ::Tilt::Template
self.default_mime_type = 'application/javascript'
CJS_DEFINE_REGEXP = /define\(\s*function\s*\(require,\s*exports,\s*module\s*\)\s*\{/
CJS_REQUIRE_REGEXP = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/ # from r.js
# Check to see if RequireJS env is loaded
def self.engine_initialized?
@initialized
end
# Setup RequireJS library.
def initialize_engine
@initialized = true
end
def prepare
end
# Converts a "CommonJS-style" RequireJS module into a flat one
#
# Something like
#
# define(function(require, exports, module) {
# var config = require("app/config");
# var _ = require("vendor/lodash");
# var logger = require("app/logger");
# // ...
# }
#
# is converted into
#
# define('my/module/path', ['require', 'exports', 'module' , 'config', 'vendor/lodash', 'app/logger'], function(require, exports, module) {
# // same function body
# }
#
def evaluate(scope, locals, &block)
# Ici mon traitement, lui aussi à grands coups d'expressions régulières.
# L'intérêt est que chaque module va être agrémenté de son identifiant, même lorsque tout
# le monde est compacté en un unique fichier JavaScript pour la production, et que les dépendances
# sont exposées, ce qui évite à RequireJS de les déterminer au runtime.
#
# Ce traitement est effectué à chaque requête en mode "development", mais ne l'est pas
# en mode "production" puisque chaque asset y est pré-généré via la commande
# "rake assets:precompile".
#
# Pour le traitement on a accès au fichier ciblé via des variables comme "scope.logical_path" ou
# "scope.pathname"
#
end
end
end
end
# fichier "config/initializers/sprockets.rb"
require 'sprockets'
require 'acme/sprockets/requirejs_template'
AcmeRails::Application.assets.register_engine '.reqjs', ::Acme::Sprockets::RequireJSTemplate
Et voilà ! Je n'ai plus qu'à ajouter l'extension en question à chacun de mes modules RequireJS, dans le dossier “app/assets/javascripts/amd-modules/”. Cela donne des fichiers “.js.reqjs.coffee”, et le traitement sera effectué automatiquement pour chacun d'eux.
Yeah!
Pour finir, quelque chose d'un peu plus trivial mais qui peut servir. SASS étant codé en Ruby, il est très facile d'y ajouter ses propres fonctionnalités depuis son application Rails.
Dans mon cas, j'ai besoin de faire référence à une URL externe, celle de l'application PHP avec laquelle je dois interagir, et chez qui je vais piocher certains assets. Plutôt que de mettre cette URL en dur dans mes fichiers CSS, je vais donc créer une nouvelle fonction SASS, reliée à une méthode Ruby :
# fichier "config/initializers/acme_sass_helpers.rb"
module Sass
module Rails
module Helpers
def typolight_admin_asset_url(path)
Sass::Script::String.new("url(#{Gaston.acme.typolight_admin_assets_path}#{path.value})")
end
def rails_asset_url(path)
Sass::Script::String.new("url(#{Gaston.acme.rails_assets_path}#{path.value})")
end
end
end
end
// ...
header {
border-bottom: 1px solid #ccced0;
background: #fff typolight-admin-asset-url("images/bgBody.gif") repeat-x;
}
// ...
.save-bt {
background-image: rails-asset-url("common/icons/document-save.png");
}
// ...
C'est simple et bigrement pratique :-)
J'ai vu les fonctionnalités de Sprockets assez fortement décriées ça et là, ce qui je pense a eu pour conséquence son retrait de la future version 4 de Ruby on Rails : il sera toujours possible de l'utiliser, bien entendu, mais elle ne sera pas incluse et activée par défaut comme c'est le cas avec la version 3.
Pour ma part, j'avoue que même si conceptuellement il n'est peut-être pas des plus élégant de lier ainsi des fonctionnalités ou des données d'assets à des fonctionnalités Ruby, il est en pratique bien commode d'avoir un tel couteau suisse sous la main.
Mes premiers assets ont d'ailleurs été trop chargés en fonctionnalités ERB ça et là, et je me suis vite aperçu qu'il n'était pas forcément judicieux de coller de l'ERB partout : c'est certes bien tentant, mais il faut résister à l'envie d'en mettre partout afin de conserver une séparation des responsabilités au sein de l'application.
Je me suis donc limité à l'utilisation d'ERB dans mes assets pour 2 fichiers JavaScript seulement (la config et l'internationalisation), et pour le reste j'utilise les moteurs Sprockets et la possibilité d'ajouter des fonctionnalités SASS. Je pense que c'est là un bon équilibre entre praticité, productivité et séparation des couches applicatives.
Même si des gems existent pout utiliser des fonctionnalités comme Yeoman dans Rails, j'ai le sentiment qu'il est préférable de choisir entre un outil comme Yeoman ou Sprockets, mais que combiner les 2 peut donner naissance à quelque chose de bien compliqué.
Dans ce cadre, je ne sais si je continuerai à utiliser Sprockets à l'avenir, mais aujourd'hui il résout bien mes problématiques et c'est tout ce que je demande à un tel outil.
Je continuerai donc allègrement de lui confier mes assets pour ce premier projet Ruby on Rails ! :-)
Bonjour !
J'ai à présent les 2 mains dans le cambouis, développant ma première application Ruby on Rails pour mon client 8 heures par jour. Les débuts ont été assez hésitants, et j'ai passé beaucoup de temps à faire des recherches sur Internet. Heureusement il existe de très nombreuses ressources concernant Ruby et Ruby on Rails, et j'arrive toujours à me débloquer d'une manière assez honorable.
Je m'en voudrais de “saloper” ma première réalisation Rails, aussi j'essaie de faire les choses proprement ! :-)
Après avoir un peu avancé sur l'intégration HTML/CSS du projet, je me suis cependant vite retrouvé devant un dilemne à résoudre : quel moteur de template choisir ?
Le moteur de template livré par défaut avec Rails est ERB, dont le fonctionnement est détaillé ici.
Il n'y a pas grand-chose à reprocher à ERB, il fait le job comme il faut : on bénéficie de la souplesse et de la beauté du langage Ruby dans les Vues, afin de structurer la logique d'affichage dans des balises <% .... %>, et on affiche le contenu de variables avec les balises <%= .... %>.
Si ça vous rappelle furieusement les balises <?php .... %> et <?= .... %> de PHP, c'est normal, c'est exactement le même principe.
On dispose dans ERB de tout un tas de helpers mis à notre disposition par le framework Ruby on Rails, qui permettent de simplifier l'affichage HTML d'images, de liens, de feuilles de styles… La syntaxe ERB est très simple, facile à apprendre, et on est instantanément à l'aise avec sa syntaxe lorsqu'on vient de PHP.
On dispose du système de “layout” et de “blocs” que l'on peut avoir avec Twig par exemple, ici appellé des yielding regions , ce qui permet beaucoup de souplesse.
Mais alors, que lui reproche-t-on ? S'il est le moteur de template par défaut de RoR et qu'on se pose la question de choisir un autre moteur, c'est a priori que quelque chose cloche.
D'après ce que j'ai pu comprendre, le principal reproche que lui font les rubyistes est qu'avec ERB on reste dans la logique verbeuse du HTML : on ouvre un <div>, on ferme le </div>, on doit ajouter des commentaires HTML sur certaines fermetures de balises pour retrouver quel élément elles ferment, etc. Ruby est un langage qui permet une extrême concision, et j'ai l'impression que ses utiliateurs aimeraient rester dans cet esprit lorsqu'ils font l'intégration de leurs pages Web.
A titre personnel je suis habitué à de l'HTML “classique” depuis 15 ans, aussi je me suis dit que j'aurais peut-être un résistance au changement trop importante pour passer à un autre système. Et puis je suis déjà en train d'apprendre un nouveau langage de programmation et un nouveau framework, c'est déjà pas mal pour mon pauvre cerveau. Aussi m'étais-je résolu à rester sur ERB, malgré le fait que ce système de templating soit visiblement très peu utilisé actuellement par les rubyistes.
Seulement voilà, même en PHP on n'utilise plus guère la syntaxe PHP “pure” pour le templating (même si c'est à la base la grand force du langage : le système de templating est inhérent au langage), et j'ai pris mes petites habitudes avec Twig.
Même lorsque j'ai fait du Node.js, je suis resté sur un système de template qui colle au plus près de l'HTML, avec la version de TJ Holowaychuck (un des gurus de Node.js) de EJS : https://github.com/visionmedia/ejs
Celle-ci est plus ou moins un portage d'ERB (et donc de la syntaxe PHP) en Javascript, mais elle y ajoute un bien chouette système, les filtres. Avec les filtres la syntaxe devient moins verbeuse, plus fluide, et leur absence m'a vite manqué en pratiquant ERB.
Me voici donc résolu à aller chercher un autre moteur de templating. Habitué au Twig de Symfony 2, je me suis donc mis à la recherche d'un système analogue en Ruby. Après tout, Twig est à la base inspiré du système de templates du framework Python Django, et je me suis dit que si des développeurs PHP avaient décidé de s'inspirer de ce système il y aurait de fortes chances que ce soit également le cas de développeurs Ruby.
C'est ainsi que j'ai installé et essayé… Liquid !
Liquid est donc un système de templating lui aussi inspiré de celui de Django, développé apparemment par Shopify, qui l'a publié en Open Source sur son espace GitHub. Si vous êtes comme moi habitué à Twig et que vous parcourez la documentation de Liquid, vous devriez être en terrain connu : la syntaxe est peu ou prou la même et on y retrouve le même système de filtres.
Chouette chouette chouette, me suis-je dit, je vais pouvoir rester dans un système proche de celui auquel je suis habitué, et qui a fait ses preuves. De plus, léquipe avec qui je travaille sur ce premier projet Rails bosse de son coté sur Symfony 2, ça simplifiera la mutualisation des ressources si besoin est.
Malheureusement, je me suis retrouvé confronté à 2 obstacles majeurs :
Pour ces 2 raisons, je ne vais pas choisir Liquid pour ma part, même si sa ressemblance avec Twig aurait été bien commode.
Un tour dans la catégorie “template engines” de Ruby Toolbox, qui élabore des degrés de “popularités” de bibliothèques logicielles Ruby en fonction de plusieurs critères (dont le nombre de téléchargements depuis RubyGems ), permet de voir quels autres moteurs de template on a à notre disposition avec Ruby.
Le plus populaire de cette liste, et de loin, est “erubis”. Cette bibliothèque est apparemment un ERB codé en C, avec amélioration des performances, mais malgré ce très haut indice de popularité sur Ruby Toolbox je n'ai vu aucun site Rails l'utiliser parmi ceux dont j'ai parcouru le code source. La dernière mise à jour de “erubis” date d'il y a 2 ans, ça sent le sapin.
Tilt est le deuxième de la liste, mais il n'est pas en lui-même un moteur de template : c'est plutôt, d'après ce que j'ai compris, une couche d'abstraction permettant avec la même API d'interragir avec plusieurs moteurs différents. Si vous avez travaillé avec Node.js, cela vous rappellera peut-être le projet consolidate.js de TJ Holowaychuck (eh oui, encore lui :-)
On retrouve également Mustache dans ces solutions de template. Celui-ci a l'avantage d'être simple, performant, et d'être décliné à l'identique dans plus de 20 langages de programmation (!). Mais sa logique “logic-less” est assez gênante lorsqu'on est habitué à des moteurs de template riches comme Smarty ou Twig, aussi ai-je passé mon tour en ce qui me concerne.
J'en viens donc au troisième énergumène de notre liste : HAML. Je me doutais bien que je me retrouverais un jour coincé dans le même ascenceur que HAML, et j'avais juqu'ici tenté de l'éviter, mais me voici au pied du mur. En effet, hormis RedMine (qui accuse aujourd'hui un certain âge, même si cela n'enlève rien à la qualité du produit) tous les sites Web basés sur Rails dont j'ai pu parcourir le code source ont basé leurs templates sur HAML. Et lorsqu'au cours de la formation Ruby j'avais demandé à Matthieu Segret quel moteur de template était utilisé en ce moment par les rubyistes, il m'avait confirmé ce que je craignais : HAML règne sans partage.
Pour ma part, je n'étais absolument pas chaud pour m'y mettre : HAML s'éloigne fortement de la syntaxe HTML, et le code des templates qui en résulte est assez déboussolant lorsqu'on y est pas habitué.
J'avais joué un peu avec le moteur de templates Node.js Jade (dont je vous laisse deviner l'auteur), qui est un peu dans le même esprit (même si en Ruby une autre technologie, Slim, se revendique plus proche de Jade qu'HAML). Effectivement, les débuts avaient été difficiles puisqu'il m'avait fallu perdre les automatismes de l'HTML, mais après quelques heures j'étais finalement rentré dans la logique et je m'étais rendu compte qu'il est effectivement bien commode de pouvoir gérer son code sans fermeture de balise, uniquement avec une structure par indentation.
J'avais donc fait l'effort de me mettre à un moteur de template éloigné de la syntaxe HTML avec Jade lorsque je travaillais avec Node.js, mais par fainséantise j'avais voulu éviter de m'y replonger pour HAML. Las ! Il semble bel et bien que ce soit bien lui que la communauté Ruby on Rails plébiscite, et il va bien falloir que je m'y mettre !
Malgré le fait que sa syntaxe ne m'enchante guère, et malgré certains retours d'expérience négatifs (notamment celui de Brandon Keepers, ingénieur chez GitHub) qui ne m'ont pas rassuré, j'ai donc converti les quelques templates ERB que j'avais réalisés jusqu'ici sur ma première application Rails en HAML.
La première demie-heure fut poussive, mais après ce temps d'adaptation la conversion a été assez rapide, et je me suis vite retrouvé avec tous mes templates raccourcis d'un facteur d'environ 50% par rapport à leur version “HTML/ERB”. La concision d'HAML est bien réelle.
J'ai notamment trouvé particulièrement pratique de pouvoir modifier très facilement l'arborescence du document HTML simplement en faisant remonter ou redescendre un bloc puis en le réindentant correctement. Les “balises” HAML ne sont pas fermées comme en HTML, et de ce coté-là c'est un vrai plaisir de disposer d'une telle souplesse.
Là où j'ai clairement déchanté, en revanche, c'est sur la gestion de gros blocs de texte intégrés dans une page HTML. A terme, tous les textes de mon appli Rails seront bien sûr soit en base de données dans mes différents Modèles, soit dans les fichiers YAML d'internationalisation gérés par Rails (ce système d’i18n est fort simple et très pratique, d'ailleurs, et permet quelques techniques de ninja comme celle-ci ).
Mais puisque j'en suis aux premiers tests de mise en page, j'aurais aimé pouvoir coller tel quel mon pavé de texte dans mon template.
Malheureusement HAML n'est vraiment pas fait pour ça, et le système choisi pour intégrer du texte multiligne n'est vraiment pas pratique - là où Jade par exemple se contentait d'une simple indentation pour ce faire.
On a certes le filtre “:plain” pour cet usage, mais dans mes tests une partie de l'indentation structurelle de mon texte se retrouvait appliquée dans le code source final de la page. Ce texte ayant vocation à être partiellement converti en Markdown, cette indentation me gêne assez.
J'ai finalement pu résoudre mon problème, en externalisant ce long texte ailleurs et en y faisant référence dans mon template HAML sous forme de variable - ce qui serait de toute façon arrivé tôt ou tard, puisque normalement aucun texte ne sera laisé tel quel dans les templates.
Mais j'ai eu sur ce coup-là l'impression d'être davantage contraint que libéré par la syntaxe de HAML. Comme le dit Brandon Keepers dans son article, ce dernier permet effectivement de rendre le code source HTML plus concis, mais sans pour autant apporter de réelle valeur ajoutée, contrairement à ce que peuvent faire SASS et CoffeeScript pour les feuilles de style et le JavaScript.
Je vais continuer à utiliser HAML parce que c'est visiblement le moteur de template le plus utilisé avec Ruby on Rails (et de loin) et qu'il fait assez bien le boulot qu'on attend de lui, mais… j'avoue rester assez circonspect envers lui en ce qui me concerne. Le coup de coeur n'est clairement pas là.
Tant pis. La route du Ruby on Rails ne peut pas non plus être couverte de jasmin tout au long du chemin.
Je vous retrouve bientôt pour la suite de ma découverte du développement RoR !
Bonjour !
C'est inéluctable, il n'y aura jamais d'article consacré au “jour 7”, puisque je passe directement du sixième au huitième. Un peu comme l'album 5 de Gaston Lagaffe - toute proportion gardée dans la qualité des contenus respectifs.
C'est qu'hier soir je me suis rendu avec nos 2 formateurs Ruby Matthieu et Camille à un Human Talk dans les locaux de Viadeo - fort intéressant par ailleurs - , aussi n'ai-je pas eu le temps de coucher sur papier mes apprentissages Ruby de la journée.
Nous avons commencé par étudier la structure d'une Gem (une bibliothèque Ruby), et nous avons enchaîné avec la création dans les règles de l'art de notre propre Gem.
Ca tombe bien, car je me disais que ce serait rudement bien d'empaqueter sous forme de Gem certaines des fonctionnalités que je vais devoir créer pour ma première appli Rails.
C'est qu'en tant que développeur PHP, j'ai pris l'habitude avec Zend Framework et Symfony 2 d'utiliser certains outils que je n'ai pas tous retrouvés dans la profusion de fonctionnalités offertes par Ruby, sa librairie standard, Rails ou les Gems. Je vais donc m'atteler à les transposer en Ruby afin de pouvoir rester dans les “bonnes pratique Rails” tout en ajoutant quelques “PHP-iseries” auxquelles je me suis habitué, et qui me causeraient bien du chagrin si je ne pouvais plus les utiliser.
Les empaqueter sous forme de Gem me permettrait de pouvoir réutiliser ces fonctionnalités facilement et proprement dans de futurs projets, et ça pourrait rendre service à d'autres puisque je les publierais en Open Source sur GitHub, comme je l'ai fait avec mon ptit module Node-DBI pour Node.js.
Mais puisque j'allais devoir compenser mon manque d'expérience par un rythme soutenu sur ce premier projet, je m'étais dit que je n'aurais malheureusement pas le temps d'apprendre à créer à une Gem proprement. Eh bien grâce à cette formation la création de Gems m'a été démystifiée, et hormis les tests unitaires qui seront consommateurs de temps quoi qu'il arrive, je pourrai rapidement et sans appréhension m'atteler à ce chantier.
Il me tarde de publier ma première Gem sur RubyGems ! :-)
Certains des langages de programmation que j'ai pu pratiquer jusqu'ici, comme PHP ou ActionScript 3, permettent l’introspection. Dans des frameworks comme Symfony 2, d'ailleurs, l'introspection a une grande importance - notamment avec la programmation par annotations. Je pense donc que j'aurais pu sans trop de mal trouver mes marques en la matière en Ruby si le besoin s'était fait sentir.
Pour l’intercession, en revanche, c'est une autre histoire. L'intercession (j'ignorais moi-même l'existence de ce mot jusqu'à ce matin :-), dixit Wikipédia, est la capacité d'un programme à modifier son propre état d'exécution ; c'est qui permet par exemple à une classe de se modifier elle-même au runtime. A part en TCL/Tk, à vrai dire, pour ma part je crois n'avoir à peu près jamais utilisé cette fameuse intercession.
Certes, on a bien en PHP les méthodes magiques qui permettent de s'en approcher, mais ça reste du bricolage à mon sens, puisque jamais on ne crée réellement de méthode au runtime : on se contente de les émuler.
Avec Ruby, de ce coté c'est la fête ! Le langage est vraiment fait pour ça, ce qui encourage à s'en servir même pour des choses triviales ne faisant pas appel à des design patterns de haute voltige. Si j'avais continué seul sur la Voie du Ruby ™, je suis à peu près certain qu'il ne me serait jamais venu à l'idée d'utiliser cette forme de métaprogrammation, alors que Matthieu et Camille ont pu nous expliquer en quoi c'était une chose naturelle en Ruby, et dans quels types de contextes on pouvait l'utiliser.
Je pense que je serai vite confronté à une situation où utiliser la métaprogrammation en Ruby me fera gagner du temps et/ou de la simplicité dans le code de mon appli Rails. Je ne manquerai pas de le détailler ici, afin de partager cette connaissance en d'illustrant à quoi cela peut servir.
Voilà un autre domaine dont j'avais pas mal entendu parler dans les différents tutoriaux Ruby et Rails que j'ai pu parcourir : les DSL.
Alors de ce coté-là, c'est simple : je suis sûr à 100% que je n'aurais jamais utilisé ce truc de mon propre chef :-) J'en avais une vision approximative, je trouvais pas ça super utile et n'ayant jamais utilisé de langage de programmation rendant cela aussi fluide que Ruby, ça n'est absolument pas une façon de faire à laquelle j'aurais pensé pour résoudre un problème.
Eh bien force est de reconnaître que c'est bien utile, ces petites bêtes. Avec les fonctionnalités et la syntaxe de Ruby, où parenthèses et points-virgules sont optionnels, on peut très facilement créer un DSL - en gros, un langage de script simplifié, dédié à une usage précis, en surcouche de Ruby.
Matthieu nous a montré quelques exemples de DSL utilisés dans le monde du Ruby, puis nous l'avons suivi pour créer notre propre DSL. En quelques minutes seulement nous pouvions configurer tous les paramètres de notre application de test avec un langage de script simplifié élaboré spécialement pour elle. C'est vraiment très chouette.
Voilà un autre domaine de Ruby que je n'aurais jamais utilisé par moi-même, et qui je pense me sera très vite utile en pratique. Là aussi je tâcherai de partager ce savoir fraîchement acquis, en détaillant ici ma première DSL lorsque le besoin d'en créer une se présentera.
En attendant, je ne peux que vous inviter à aller trouver un tutoriel à ce sujet, si comme moi vous n'avez jamais utilisé de DSL et que vous ne voyez pas ce que ça peut vous apporter. Ca vaut le coup.
La formation est déjà finie, ces 3 jours sont passés très vite.
Hormis les grandes lignes que j'ai résumées ici, Matthieu et Camille ont pu répondre à mes (trop) nombreuses questions, et m'ont permis d'être aujourd'hui à l'aise avec Ruby, ce que je n'étais absolument pas jusqu'ici puisque j'étais passé directement à la case Rails sans m'attarder sur la case Départ.
Ce qui est bien dommage, puisque je ne touchais pas 20.000 francs : je restais en surface du langage, me contentant d'utiliser les équivalents de ce à quoi j'étais habitué en PHP/JavaScript/ActionScript et reproduisant les mêmes schémas, alors que Ruby a une versatilité au runtime très élevée, qui lui confère des possibilités et des design patterns inédits.
Si comme moi vous débutez en Rails, je ne peux que vous conseiller de faire tout le contraire de ce que j'avais entrepris initialement. Je n'avais fait que survoler les bases du langage, alors qu'il est tellement agréable à utiliser et à découvrir en détail que cela vaut réellement le coup de bien potasser Ruby avant de se lancer dans RoR, avec un exemple pratique histoire de pouvoir mettre tout de suite les mains dans le cambouis.
Si vous n'avez pas d'inspiration pour créer un premier projet en Ruby à but didactique, vous pouvez par exemple vous monter rapidement un petit jeu simple, comme nous l'avons fait avec Matthieu, en vous appuyant sur la librairie Ruby Gosu. Elle n'a je pense pas vocation à être utilisée pour des cas “réels”, mais pour pratiquer les bases de Ruby tout en s'amusant c'est un très bon outil - combiné à des ressources graphiques comme celles-ci, c'est un très bon moyen d'explorer le langage, puisqu'en une petite heure vous pouvez avoir votre personnage dirigé au clavier qui zigzague pour éviter les ennemis, tout en ayant potassé Ruby :-)
Je finirai cet article en ne manquant pas de remercier une nouvelle fois Matthieu et Camille : ces quelques jours de formation Ruby vont me faire gagner un temps fou. Au-delà même du simple temps, il y a certains domaines comme les DSL et la méta-programmation que je n'aurais probablement jamais exploré par moi-même, et ça c'est inestimable.
Merci les gars !
Je tâcherai de publier ici mes usages de ce que j'ai pu apprendre lors de cette formation, afin que ceux qui comme moi n'auraient pas utilisé de leur propre chef ces possibilités de Ruby puissent eux aussi se faciliter la vie… et se faire plaisir.
Car c'est là aussi l'un des enseignements que j'ai pu tirer de cette formation : coder avec Ruby c'est fun, vraiment fun !
A bientôt !
Bonjour !
Aujourd'hui pas de palpitantes (?) découvertes dans mon apprentissage de Rails à raconter ici, puisque je suis revenu aux fondamentaux avec…. une formation Ruby donnée par des pros, j'ai nommé Human Coders !
Cette première journée était consacrée aux bases du langage, que j'avais déjà pas mal potassées en ce qui me concerne. Aussi connaissais-je déjà sur le papier la plupart des points que nous avons vu ensemble pour ce premier jour.
Cependant à la lumière de cette première séance j'ai pu constater qu'à travers mon auto-formation à grand renfort de tutos et de Stack Overflow il m'a tout de même manqué une utilisation immédiatement mise en pratique des bases du langage. J'étais en effet tellement impatient de me mettre à Ruby on Rails que je m'étais contenté d'effleurer le langage Ruby lui-même.
Après cette première journée je vois que j'étais passé à coté de certains choses, que l'API de Ruby contient des méthodes super pratiques un petit peu partout que je ne connaissais pas par exemple - lorsqu'on s'y met tout seul on a tendance à seulement rechercher dans la doc de l'API les équivalents des méthodes qu'on connaît dans d'autres langages, ce qui m'avait fait passer à coté de chouettes trucs comme la méthode sample de la classe Array.
De même, comment aurais-je pu soupçonner que Ruby comportât une méthode Math.hypot pour calculer une hypoténuse ? Cette fonction, essentielle la plupart du temps lorsqu'on programme un jeu par exemple, n'est pas présente même dans un langage quasiment dédié au jeu comme ActionScript… mais elle existe bel et bien en Ruby :-)
A la lumière du bien humble savoir qu'est le mien aujourd'hui sur Ruby, mon impression d'un langage orienté vers le pragmatisme s'est renforcée.
Là où PHP par exemple essaie depuis quelques années de se rapprocher du Java avec des design patterns dans tous les sens, des imports par dizaines dans chaque classe et la programmation par annotations (que je critique absolument pas, pour ma part j'aime bien), j'ai le sentiment que Ruby ne s'embarrasse pas des concepts les plus abstraits de la Programmation Orientée Objet.
Pas de classes abstraites, pas d'Interfaces, pas ou peu d'utilisation de l'injection de dépendances… Ruby se concentre sur une API riche et orientée vers des méthodes qui rendent service au quotidien, et se contente pour la POO de l'Héritage et de l'utilisation des Mixins - qui sont, ai-je d'ailleurs appris aujourd'hui à travers la formation, moins simplistes qu'ils semblent l'être.
Une classe peut ainsi se retrouver bardée de fonctionnalités uniquement via l'utilisation de Mixins (ce qu'on constate dans Rails, d'ailleurs), et le développeur est invité à ne pas de prendre la tête sur des questions comme “Vais-je utiliser une classe asbtraite ou une interface pour cette fonctionnalité ?”, pour se concentrer sur la réutilisation simple et maximale de fonctionnalités avec les Mixins.
- tout ceci n'est bien sûr que mon impression en tant que débutant.
Au-delà de ces petites découvertes de l'API, la formation est bien progressive et portée par des exercices pratiques ludiques, qui m'ont permis de mettre en pratique tout ce que j'avais découvrir sur le Net. Lire et retenir est une chose, mettre en application correctement en est une autre.
Matthieu est à l'écoute des questions, et a pu répondre à mes nombreuses (j'ai dû être lourd à la longue, je vais tâcher de moins l'interrompre demain :-) questions sur “Ruby en pratique” : en pratique, qu'utilise un “Rubyiste” lorsqu'il doit implémenter telle ou telle fonctionnalité, alors que Ruby propose tel moyen d'y parvenir mais aussi tel autre ? Tout un pan que je pouvais acquérir avec de l'auto-formation. J'ai même pu caser quelques questions sur Rails !
…et puis un homme qui vous accueille par un petit déjeuner avec viennoiseries et jus d'orange avant de débuter la journée de formation ne peut assurément pas être un mauvais bougre :-)
Bref, je suis impatient de voir ce que les journées suivantes me réservent. Pour ma part j'attends particulièrement la partie “Créer ses propres Gems”, ainsi que de celle sur “Créer son propre DSL”. Wait and see…
A bientôt !
Bonjour !
Maintenant que tout ce qui concerne l'installation et la configuration d'un environnement Ruby on Rails est terminée, je peux m'adonner à plein temps au vif du sujet, l'utilisation de Rails.
Après une journée passée à débuter ma première application RoR, voici à chaud (et un peu en vrac) mes premières impressions sur l'utilisation du langage Ruby et du framework Rails.
Je commence par la surface : la lisibilité du code. Pour ma part je n'ai jamais fait de C ou de C++, mais je suis cependant habitué à des langages qui en ont la syntaxe : PHP, JavaScript, ActionScript, Perl, Java… Hormis quelques applications que j'ai réalisées en TCL/Tk par-ci par-là, je ne me suis guère aventuré en territoire inexploré de ce coté-là.
Par conséquent, j'ai encore un peu de mal à lire du code Ruby : mon oeil continue de chercher ses familières accolades ouvrantes et fermantes et ses parenthèses. Lors de l'écriture du code Ruby, en revanche, pas de problème : il est même très plaisant d'utiliser un langage débarrassé de toutes ces décorations, et on se concentre vraiment sur les méthodes elles-mêmes.
Cela étant, je pense que c'est uniquement une question d'habitude (j'ai bien réussi à être presque totalement à l'aise avec CoffeeScript), et j'imagine qu'avec le temps la lecture de code Ruby me sera aussi naturelle que celle des autres langages auxquels je me suis habitué.
Une autre “prise” manque encore pour accrocher mon regard dans la compréhension du code que j'ai sous les yeux : les return. Le mot-clef est présent en Ruby, mais on l'utilise beaucoup moins qu'ailleurs. En effet, une méthode retourne toujours la dernière expression évaluée en son sein. En conséquence de cela, on omet bien souvent le return lors des retours de méthodes “normaux”, et on ne l'utilise généralement que pour mettre fin au déroulement d'une méthode de manière anticipée. Par exemple :
def groups
return @groups if defined? @groups
if serialized_groups.nil? #no group, stop right here dude!
@groups = []
return @groups
end
logger.debug "*************** let's PHP-unserialize the user groups..."
@groups = ::PHP.unserialize serialized_groups
@groups.map!(&:to_i) #we only want integers in our groups IDs :-)
end
Cette méthode, directement copiée-collée d'un de mes premiers Modèles Rails (soyez indulgents, amis Rubyistes, s'il vous paraît peu adapté aux us et coutumes du langage :-), utilise le mot-clef return deux fois pour interrompre le déroulé de la méthode groups.
Dans le cas où on exécute tout le code de la méthode, en revanche, vous pouvez voir qu'il n'y a pas de return final : c'est donc la valeur de la variable d'instance @groups qui va être retournée. en même temps que celle-ci est modifiée par la méthode map! (par convention, le nom de la plupart des méthodes qui changent la valeur de l'objet sur lequel elles sont utilisées finit par un point d'exclamation ; ça fait un peu bizarre au début, mais à l'usage c'est vraiment super).
En relisant mon code à l'occasion de la rédaction de cet article, je m'aperçois d'ailleurs que j'ai encore laissé traîner des return par-ci par-là en fin de méthodes : ah, on ne change pas ses habitudes en un jour :-)
Le langage Ruby en lui-même est vraiment très agréable à utiliser, et assez souvent le “principe de moindre surprise” voulu par son créateur fonctionne assez bien : on a pas souvent besoin d'aller examiner la documentation de l'API du langage, tant les noms de méthodes sont logiques et cohérents (alors que, sans vouloir troller, après plus de 10 ans de PHP je ne me souviens pas toujours si c'est utf8encode() ou utf8_encode() :-)
Il m'a fallu cependant passer au “snake_case” après des années de bons et loyaux services en “camelCase”, mais ça n'est pas franchement traumatisant.
A noter d'ailleurs : si Ruby utilise la “snake_case” pour les noms de fichiers et les noms de méthodes, on utilise bien la “camelCase” pour les noms de modules et de classes. C'est un peu étrange.
Je peux comprendre que le créateur de Ruby on Rails ait décidé de baser le projet Basecamp sur ce langage alors que rien ou presque n'existait alors en Ruby pour faire du Web : dans le livre Getting Real, il explique ce choix au chapitre “Choose tools that keep your team excited and motivated”. Et en effet, Ruby me paraît être un bon choix pour avoir du fun en codant.
Une des premières choses qui m'a surpris dans l'utilisation de Ruby on Rails : les design patterns utilisés ne sont pas les mêmes que ceux auxquels on a pu s'habituer lorsqu'on vient de frameworks comme Symfony 2, Silex, Zend Framework 2 ou Laravel.
Les 3 frameworks susdits s'architecturent en effet beaucoup sur les design patterns “Service Locator” et “Dependency Injection”, et puisqu'ils sont à la base plus ou moins directement inspirés de Ruby on Rails je m'attendais à les retrouver dans ce dernier.
Eh bien pas du tout, d'après ce que j'ai pu voir jusqu'ici, “Service Locator” et “Dependency Injection” ne sont pas utilisés dans RoR.
Au lieu de cela, j'ai l'impression que le framework est massivement basé sur une fonctionnalité offerte par Ruby, les mixins (équivalents des Traits en PHP 5.4+).
Lorsque j'ai une fonctionnalité à ajouter à mon appli Rails, donc, je ne vais pas créer un “service” accessible via un “service locator”, comme je le ferais avec un de ces frameworks PHP. Au lieu de cela, d'après ce que j'ai pu comprendre il y a 2 choix possibles :
Créer un Gem pour ma fonctionnalité, et l'ajouter au fichier “Gemfile” de mon application. Cela revient à peu près en PHP à créer un module publié via Composer, et à l'ajouter au “composer.json” du projet. Cette approche nécessite cependant d'avoir pas mal de temps de temps devant soi, puisque le code va être assez indépendant de l'appli et qu'il faut “packager” sa Gem comme il faut, avec tests unitaires et tout le toutim. Ca permet certes d'être sûr que le code est propre, mais en phase de prototypage ou de livraison-à-rendre-pour-hier on a pas forcément ce luxe.
Créer sa fonctionnalité dans un module à part, placé dans le répertoire “lib/” de son appli Rails. Il faut alors activer ce code au démarrage de l'appli Rails, via un initializer.
C'est cette dernière approche que j'ai pour le moment choisi. Pour la première fonctionnalité annexe que j'ai dû créer (un pont applicatif entre mon appli Rails et le code PHP pré-existant) , j'ai donc ajouté un fichier “lib/acme/php_bridge.rb”, avec la structure suivante :
module Acme
module PhpBridge
def check_php_bridge
logger.debug "check_php_bridge()" if Rails.env.development?
# ici du code...
end
private
# ici mes méthodes privées
# ...
# ...
end
end
Pour rendre cette fonctionnalité disponible dans mes Contrôleurs, j'ai ajouté un fichier “require_acme_libs.rb” dans le répertoire “config/initializers/”, avec ce contenu :
require 'acme/php_bridge'
EDIT : lors de ma formation Ruby chez Human Coders, j'ai pu lors d'une pause demander à Matthieu quelle était la bonne pratique en la matière. Plutôt que de faire manuelle ce “require”, donc, il m'a plutôt conseillé d'ajouter la ligne suivante au fichier “config/application.rb” de mon appli Rails. C'est un peu bourrin mais très efficace :-)
# fichier "config/application.rb", aux environs de la ligne 20
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
Pour utiliser ma fonctionnalité “check_php_bridge”, enfin, je n'ai plus qu'à ajouter le code suivant en début de mes Contrôleurs visés :
class MyController < ApplicationController
# Acme custom actions
include Acme::PhpBridge
before_filter :check_php_bridge
end
Et d'après ce que j'ai pu comprendre, dans RoR tout fonctionne ainsi, utilisant au mieux la possibilité qu'offre le langage Ruby, à travers les mixins et la possibilité qu'ont les classes de se modifier elles-mêmes au runtime pendant leur définition même (comme on peut le faire avec JavaScript, d'ailleurs).
Pour le “PHPiste” que je suis, cependant, cela ne va pas sans me poser pour le moment un léger soucis de compréhension du code, je l'avoue. J'ai en effet le sentiment qu'il y a beaucoup de “magie” dans Rails.
Je me souviens de mes premiers pas avec Zend Framework (première édition) en 2009 :
“Mais là, on appelle la méthode ”$this->getUser()“ dans le Contrôleur alors que cette méthode n'existe pas ?
- C'est normal, c'est le fonctionnement de ZF, elle va être résolue en PHP via les méthode magique, qui vont déléguer ça automatiquement à un Helper”
J'étais abasourdi. Les différents composants applicatifs étaient presque tous reliés par des méthodes magiques, et si vous n'aviez pas la documentation du Zend Framework en livre de chevet il était impossible de s'y retrouver.
Depuis j'ai bossé sur de nombreux projets avec le ZF et je m'y suis habitué, ça va mieux merci :-)
Avec Symfony 2 ily a également une part de magie, mais elle est assez facilement pistable. Lorsqu'on utilise $this->get('mailer'), il n'est pas forcément évident de savoir de prime abord quelle instance de quelle classe va être retournée. Heureusement, la console Symfony 2 permet de voir le mapping de tout ce petit monde :
php app/console container:debug
Dans Ruby on Rails, en revanche, je n'ai pas trouvé d'outil analogue. La console Rails est fantastique (j'en parlerai plus bas), mais puisqu'il n'y a pas de “Service Container” centralisé elle est bien incapable d'après ce que j'ai pu voir de nous donner un listing équivalent.
Pour moi qui aime bien savoir comment fonctionnent grosso-modo les outils que j'utilise, il n'est pas facile de savoir à quels éléments on se réfère lorsqu'on utilise toutes ces fonctionnalités issues des dizaines (centaines ?) de mixins utilisés par Rails - auxquels viennent s'ajouter ceux des différentes Gem utilisées, ainsi que ceux développés par mes soins pour les besoins propres de l'application.
J'ai pour ma part fait l'achat d'un licence de RubyMine, qui est “Rails-aware” comme Jean-Claude, et qui me permet fort sympathiquement d'accéder au code source d'une méthode de mixin par un simple “Ctrl+clic” sur la méthode en question.
Mais sans RubyMine, je serais bien en galère je pense pour naviguer dans le code source de Rails et des Gems que j'utilise. Lorsque je vois la méthode “before_filter” par exemple, abondamment utilisée dans Ruby on Rails, comment savoir de quelle classe Ruby vient diable cette fonctionnalité ?
Vous me répondez peut-être par un lapidaire “RTFM”, et vous n'aurez pas tort. Mais ça prend toujours un peu de temps, et même une fois l'animal localisé il n'est pas toujours évident de bien le comprendre : http://apidock.com/rails/AbstractController/Callbacks/ClassMethods/before_filter.
Heureusement, les “Guide Rails”, dont je parlais dans un précédent article, sont bien faits et assez exaustifs, ce qui pallie en partie au problème.
Mais pour ma part, j'avoue que pour le moment j'ai tout de même une vision “wild wild west” du code Rails, dans la mesure où l'utilisation intensive des mixins provenant de dizaines de classes rend difficile la tracabilité du code pour le développeur PHP que je suis.
J'imagine qu'avec la connaissance du framework ça s'arrangera, tout comme je me suis fait (et que j'ai vite appris à aimer) à Zend Framework à l'usage, mais en attendant je suis bien content d'avoir RubyMine :-)
(et non, je n'ai pas d'actions chez JetBrains)
Un petit mot également sur les documentation Ruby et Rails.
Celle de Ruby est un peu austère à mon goût lorsqu'on débarque sur la page d'accueil, mais on est pas là pour s'amuser me direz-vous. Hormis cela, elle n'est pas aussi complète que celle de PHP (qui reste à mon goût une référence en la matière, et qui m'a redonné goût à la vie lorsque je l'ai découverte après avoir tant galéré sur celle de Perl :-) mais elle fait le taf.
Ah tiens, tant que j'y suis, petit apparté : faites attention si comme moi vous pensez naïvement pouvoir faire un “chercher-remplacer” dans une chaîne de caractères avec la méthode replace : c'est un faux ami, et elle n'a rien à voir avec son homonyme JavaScript ou la “str_replace” de PHP.
Pour faire cela en Ruby, d'après ce que j'ai pu voir on utilise soit my_string["hello"] = "world" pour remplacer la première occurrence de “hello”, ou my_string.gsub(/hello/, "world") pour y remplacer toutes les occurrences.
C'est le seul exemple de violation du “principe de moindre suprise” que j'ai constaté pour le moment.
Fin de l'aparté.
Pour Rails, je me réfère plutôt aux “Guides Rails” qu'à l'API, trop confuse à mon goût (toujours à cause de la multiplication des mixins).
Et pour tous les petits détails qui ne sont pas dans les guides, Stack Overflow - où les Rubyistes sont bien présents - est toujours notre ami.
Il reste malgré tout quelques zones d'ombres que je n'ai pas encore pu éclaircir. Par exemple, je n'ai pu trouver nulle part la liste des variables accessibles depuis les Vues Rails (au sein des fichiers de templates ERB par exemple, ou dans les Helpers de Vue). J'ai apparemment accès au Contrôleur via la variable/helper “controller”, à son nom avec “controller_name”, aux paramètres via “params”, etc.
Mais c'est en fouillant Stack Overflow que j'ai pu extraire ces quelques informations, et ça m'embête pas mal de devoir ainsi aller à la pêche aux données sans avoir une référence simple me détaillant ce à quoi j'ai accès depuis les Vues.
EDIT : un lecteur m'a donné en commentaire de nombreuses “bonnes pratiques” sur la gestion des variables transmises aux Vues. Merci à lui ! Si comme moi vous tâchez de vous mettre à Rails, je ne peux que vous conseiller de lire ses conseils.
Je finirai ces premières impressions par une très bonne surprise : la console Rails !
On lance la bête en tapant une simple commande :
rails console # ou "rails c", en version abrégée
On se retrouve alors dans une console interactive Ruby, semblable en tout point à celle livrée par défaut avec Ruby, irb. Sauf qu'ici on a accès à nos classes Rails directement, ce qui permet de les tester à loisir.
Ceci est notamment très utile pour le Modèle, afin de vérifier les requêtes SQL qui sont envoyées à la base de données lorsqu'on utilise les classes ActiveRecord de RoR : toutes les requêtes SQL sont en effet affichées en direct dans la console Rails chaque fois que l'une d'elle part vers la base.
Grâce à cette console j'ai pu vérifier très rapidement que la solution de cache que j'avais mise en place pour la table des “groupes d'utilisateurs” de mon application (dont le contenu n'a pas bougé d'un iota depuis des années dans l'application PHP à laquelle je vais greffer ma première application Ruby on Rails) fonctionnait bel et bien :
class UserGroup < ActiveRecord::Base
attr_accessible :name
# le nom de la table a été préfixé dans l'appli PHP
self.table_name = :tl_user_group
# table non gérée par Rails ; on désactive (à regret) les "timestamps" automatiques
self.record_timestamps = false
# la table contient beaucoup d'infos superflues, dont je ne veux pas par défaut
default_scope :select=> 'id, name'
# quelques constantes pour la logique applicative dans les autres Modèles
GROUP_SUPER_ADMIN = 1
GROUP_ADMIN = 2
GROUP_USER = 3
GROUP_USER_ADVANCED = 4
# clef pour le cache des données
USER_GROUPS_CACHE_KEY = :tl_user_group_cache_key
# cette constante n'a pas vocation à être utilisée ailleurs que dans cette classe
private_constant :USER_GROUPS_CACHE_KEY
# ici, l'utilisation du cache
# @see http://guides.rubyonrails.org/caching_with_rails.html
def self.all
@@all ||= Rails.cache.fetch(USER_GROUPS_CACHE_KEY) do
logger.debug "*************** let's retrieve the users groups... (they will be stored in a cache afterwards)"
super
end
end
end
Ici je procède à un double cache des données : un géré par Rails, l'autre par le fait que je stocke le résultat du “ActiveRecord::Base#all()” dans une variable statique de la classe (indiqué par le préfixe “@@”). Lorsque les applis RoR sont sur le serveur de production les classes sont réutilisées d'un client HTTP à l'autre, contrairement à PHP par exemple.
Avec la console Rails j'ai pu vérifier que le premier appel à “UserGroup.all” lançait la requête SQL, et que tous les appels suivants utilisaient le cache - y compris lorsque je supprime la valeur du cache Rails.
En réalité je pense supprimer à terme le cache applicatif via la variable statique de classe ; même si cette approche me semble plus performante (pas de passage par Redis), elle me paraît moins souple, car si exceptionnellement le contenu de ma table change, le seul moyen dont je disposerai pour réinitialiser ma variable statique sera de redémarrer l'application Rails.
Mais il était didactiquement intéressant de mettre cela en place :-)
Et par ailleurs, j'aime beaucoup la possibilité offerte par cette syntaxe :
def some_data
@computed_data ||= compute_data
end
Grâce à elle, on peut gérer un cache de données le temps de la requête HTTP avec beaucoup de concision, tout en restant clair.
Ici, la méthode “compute_data” ne sera exécutée que la première fois que la méthode “some_data” sera appellée. Pour tous les appels consécutifs, c'est la valeur de la variable d'instance “@computed_data” créée lors du premier appel qui sera directement retourné.
Voilà qui clôt cet article. Pour résumer mes premiers pas avec Ruby on Rails, je dirais donc :
Sur ce je vous laisse, je retourne faire joujou avec mes Modèles dans la console Rails :-)
A bientôt !
Bonjour !
Dernière étape aujourd'hui avant d'attaquer le code proprement dit, et puis j'enchaîne sur mes premiers Contrôleurs, et la gestion du cache du code avec Ruby on Rails. C'est qu'après toutes ces ingestions de tutoriels Ruby/Rails et ces installations diverses de logiciels sur ma machine, je suis impatient à présent de faire enfin mes premières armes concrètes avec RoR !
La précédente journée j'ai préparé mon environnement Ruy on Rails sur une machine virtuelle (gérée via Vagrant), et je l'ai connecté à mon serveur MySQL.
J'ai donné vie une première fois à mon application avec le serveur Web intégré à Ruby on Rails, WEBrick, qui m'a permis de voir en local sur le port 3000 la page d'accueil par défaut proposée par RoR, et un affichage d'infos dynamiques relatives à mon installation Rails (via une requête Ajax proposée par cette même page). Bien.
Mais WEBrick n'est destiné à être utilisé qu'à des fins de tests en local, et il me faudra bien trouver une autre manière de faire vivre RoR sur un site Web réel, en production.
Par ailleurs, il se trouve que la réalisation de ma première vraie application RoR va se dérouler dans un contexte un peu particulier, puisque qu'elle devra s'intégrer à un site Web existant, développé en PHP et connecté à une base de données MySQL déjà bien remplie, avec déjà plus de 200 tables au compteur.
Dans ce contexte, je ne vais a priori pas me baser sur le serveur Rails qui m'a semblé être le plus utilisé, d'après ce que j'ai pu voir sur le Net (et comme un lecteur me l'a confirmé dans les commentaires :-) : Unicorn.
Ce serveur applicatif est notamment celui utilisé par GitHub, qui en détaille ses bénéfices dans cet article, ou encore par Twitter, qui là aussi chantait en 2010 ses louanges. Il semble être un bon choix pour qui veut des performances et de la souplesse dans les mises à jour (entre autres choses, Unicorn permet apparemment de redémarrer son appli Rails à chaud, sans perdre aucune des requêtes clients qui était en cours de traitement au moment du redémarrage).
En PHP le processus de mise à jour est assez simple : on modifie un fichier PHP, et la modification sera automatiquement effective pour toutes les requêtes HTTP qui vont suivre.
(Je parle là du principe de base de PHP, de celui dont l'extrême simplicité a fait le succès. C'est bien sûr un peu plus intello que cela dès qu'on a des solutions de cache qui rentrent en piste, que ce soit sur l'opcode PHP avec APC ou avec la gestion des ETags avec des logiciels comme Varnish.)
Mais le principe fondateur de PHP reste que pour chaque requête HTTP le serveur Web va grosso-modo créer un nouveau thread dédié à ce client, qu'il va regarder quel fichier PHP est demandé et qu'il va passer la main (via mod_php par exemple) au moteur PHP pour ré-analyser et ré-exécuter le code PHP du fichier en question - ainsi que toutes ses éventuelles dépendances. Lorsqu'on a une modification à apporter, on remplace le fichier concerné et la modification sera effective dès la prochaine créeation de script PHP par le serveur Web. C'est simple et c'est assez efficace.
Avec Ruby on Rails, le processus est nettement différent. D'après ce que j'ai compris, on va lancer une bonne fois pour toute une (ou plusieurs, en production) instance de l'application Rails, puis pour chaque requête le serveur applicatif déclenchera une instruction précise de ce processus, avec en paramètre les différents informations attentante à la requête HTTP. Ce serveur attendra en retour que cette instruction, une fois déclenchée, retourne un corps de réponse HTTP et des entêtes HTTP (ou, a minima, un code de retour HTTP).
Cela est formalisé par l'interface Rack, volontairement très simple d'approche. Cette interface, inspirée du monde du Python (avec WSGI), vous est peut-être familière si vous avez déjà travaillé avec le micro-framework PHP Slim.
En effet, son auteur a eu la bonne idée de reprendre à son tour ce principe de WSGI/Rack, et de le transposer en PHP. Vous pouvez le voir décrit en PHP sur cette page de la documentation de Slim.
On a donc une application Rails qui s'initialise, se lance, puis attend qu'on lui transmette des demandes de requêtes HTTP auxquelles elle devra répondre, suivant le protocole Rack. Mais puisque l'application est lancée une bonne fois pour toutes, lorsqu'on a une modification à faire et qu'on met à à jour un fichier Ruby sur le serveur cela ne lui fera ni chaud ni froid. Elle aura déjà analysé ce fichier Ruby, dont elle garde une copie compilée en mémoire vive, et elle ne se soucie plus des éventuelles modifications du fichier sur le disque dur.
Si vous avez déjà travaillé avec Node.js, cela doit vous être familier puisqu'on retrouve exactement le même fonctionnement.
Je détaillerai plus bas comment on peut dans ce contexte mettre à jour sans que ce soit trop pénible notre application chaque fois qu'on a une modification à y apporter (ce qui, pendant le développement, se produit à peu près 200 fois par jour).
Comme je le disais avant cette parenthèse sur le pourquoi des serveur applicatifs en Ruby, pour faire le passe-plat entre les requêtes HTTP brutes et notre applicatif en Rack/Rails on dispose d'un choix assez varié de serveurs applicatifs Ruby. Parmi ceux-ci, Unicorn semble être l'un des plus utilisés, mais je ne vais a priori pas pouvoir faire appel à lui en ce qui me concerne.
En effet, mon appli Rails devra être intégrée comme je l'expliquais à un site PHP existant, tournant sous Apache, et il serait bougrement pratique, pour ne pas rajouter à la complexité d'avoir 2 langages de programmation totalement différents sur un unique site Web, de pouvoir greffer mon application RoR à Apache, de la même manière qu'on y greffe PHP via mod_php.
Eh bien ça tombe bien, un à-peu-près-équivalent de mod_php existe pour Rack (et donc pour Rails, qui se base sur l'interface Rack) : il s'agit de Phusion Passenger (site sur lequel vous tomberez également si vous tapez “http://modrails.com” :-).
Celui-ci ne semble pas avoir que des avantages, comparé à des concurrents comme Unicorn, mais il a au moins pour lui la simplicité de sa mise en place, et sa bonne intégration à Apache et Nginx.
On a juste à appliquer les 2 étapes expliquées ici :
gem install passenger
passenger-install-apache2-module
(ici sans utiliser sudo, puisque j'utilise rbenv )
Cette dernière commande nous indique éventuellement quels packages on doit ajouter via apt-get install pour mener à bien l'installation de Phusion Passenger. On les installe, puis on relance cette commande.
Dans mon cas par exemple, sur une Ubuntu fraîchement installée il a fallu ceci :
sudo apt-get install build-essential libcurl4-openssl-dev zlib1g-dev apache2-prefork-dev libapr1-dev libapr1-devLorsque tout est fini, il n'y a plus qu'à ajouter les 3 lignes de conf Apache que nous dicte le script. Soit on y va comme un bourrin et on le colle dans /etc/apache2/apache2.conf, soit on la joue plus fine et on suit les recommandations de ce site :
on se rend dans le répertoire /etc/apache2/mods-available/
on y crée le fichier passenger.load, et on y colle la directive “LoadModule” que nous a donné le script d'install de Passenger - dans mon cas :
LoadModule passenger_module /home/vagrant/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/passenger-3.0.19/ext/apache2/mod_passenger.soPassengerRoot /home/vagrant/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/passenger-3.0.19
PassengerRuby /home/vagrant/.rbenv/versions/1.9.3-p392/bin/rubyAvec ça on a un beau module Apache, qu'on a plus qu'à activer proprement :
sudo a2enmod passenger
sudo service apache2 restartPour le reste, tout est très bien expliqué dans la documentation de Phusion Passenger.
Dans mon cas par exemple le site Ruby on Rails sera accessible depuis une “sous-URL” du site PHP, ce qui me donne une addition à mon VHost Apache de ce genre :
RackBaseURI /rails
<Directory /vagrant_php_app/rails>
RackEnv development # en local je force le mode "development" de Ruby on Rails
Options -MultiViews
</Directory>
Le répertoire “/vagrant/rails” est un lien symbolique vers le dossier “public/” de mon application Ruby on Rails, comme précisé dans la doc de Passenger sur le “déploiement vers des sous-URI” :
ln -s /vagrant_rails_app/public /vagrant_php_app/rails
Comme je l'expliquais plus haut, contrairement à PHP on a avec Rails une application qui se lance, qui analyse (pour simplifier) tous les scripts Ruby de notre appli Rails une bonne fois pour toute, puis qui attend qu'on lui envoie des requêtes HTTP à traiter. Dans ce contexte, comment gérer la mise à jour des fichiers, puisqu'une fois l'instance Rails lancée elle ne se souciera plus a priori de nos éventuelle modifications sur nos fichier Ruby ? Doit-on se taper le redémarrage du serveur WEBrick/Apache/Unicorn/whatever à chaque fois qu'on modifie le moindre fichier ??
Heureusement non, car lorsque l'application est en mode development (ce qui est le cas par défaut avec WEBrick, et ce que j'ai forcé manuellement dans la config de mon VHost Apache avec Passenger) Ruby on Rails va recharger toutes les classes de Contrôleurs, Modèles et Vues de l'application à chaque requête HTTP.
Chouette alors, grâce à ça on aura donc jamais besoin de redémarrer le serveur pour qu'il prenne en compte nos modifications !
Eh bien si, dans certains cas il le faudra pourtant bien. Déjà, lorsqu'on touche à un composant applicatif qui ne relève ni des Contrôleurs, ni des Modèles, ni de la Vue. Par exemple, dès qu'on modifie un initialiseur ou une directive de configuration (et il y a de quoi faire en la matière, comme détaillé ici par exemple).
Mais aussi dans certains cas que je n'ai pas tout-à-fait saisi. Par exemple, dans un de mes Contrôleurs j'ai un code précédant ma classe, qui fait appel à un composant logiciel que j'ai placé dans le répertoire “lib/” de mon application Rails :
require 'acme/php_bridge.rb'
class MyController < ApplicationController
include Acme::PhpBridge
before_filter :check_php_bridge
# .. le code de mon Contrôleur ...
endSi vous avez lu comme moi les dfférents tutos Rails dont je vous parlais, vous voyez qu'ici je fais appel à un module de mon crû, “PhpBridge”, qui se trouve dans un “namespace” (en Ruby, un module) - nommé pour l'exemple “Acme” afin de respecter la vie privée de mon client :-)
J'inclus ce module à mon Contrôleur en tant que mixin, comme on le ferait en PHP 5.4+ avec un Trait, puis je demande à Rails de lancer la fonction “check_php_bridge” de mon mixin à chaque fois que le Contrôleur doit être déclenché.
La première requête HTTP que je lance avec ce code fonctionne très bien, que ce soit avec WEBrick ou Passenger, mais à partir de la deuxième je récolte une vilaine erreur me disant que la route (!) “Acme::PhpBridge” n'a pas été trouvée. Je n'ai pas d'autre choix alors que de redémarrer le serveur.
Je n'ai pas vraiment compris le pourquoi de ce problème, je l'avoue - même si je me doute que cela doit avoir un rapport avec le fait que RoR va recharger le code de ma classe à chaque requête, puisque je suis en mode development, et qu'à partir de ce moment-là le code qui est en-dehors de la classe elle-même (mon “require”) n'est plus déclenché comme il faut.
Pour remédier à cela, ne sachant trop que faire puisqu'étant un total néophyte en Rails, j'ai pris le parti de mettre mon “require” dans un initializer Rails, tel que je l'ai vu faire dans le code de LinuxFr, ici.
(je savais bien que ça me servirait rapidement, d'avoir gardé le code source de ces quelques applis Rails existantes sous le coude :-) .
Seulement maintenant, mon code n'est plus dans aucune des 3 composantes MVC que Rails recharge automatiquement en mode development ! A chaque fois que je modifie mon module “Acme::PhpBridge”, donc, je dois redémarrer le serveur.
Sous WEBrick, je stoppe le serveur avec Ctrl+C puis je le relance avec la commande abrégée rails s, et sous Passenger je fais comme on me l'a dit dans la doc :
touch tmp/restart.txt #redémarre Passenger
Ce n'est pas très long à faire, heureusement, mais c'est tout de même un peu pénible de devoir faire cela à chaque fois que je vais ajouter ou modifier une classe qui ne fera pas partie des Contrôleurs, du Modèle ou de la Vue.
Je pourrai survivre avec cela, mais si un pro du Ruby a un conseil à me donner sur ce point précis je suis preneur :-)
Voilà, j'ai un environnement Ruby et Ruby on Rails prêt à l'emploi, un Passenger qui va me servir mon appli via Apache, et je crois avoir à peu près compris quand il allait falloir que je redémarre le “serveur Rack” manuellement.
A partir du prochain jour, je ne devrais plus avoir à faire qu'à du code Ruby on Rails proprement dit ! Je tâcherai de vous remonter les points qui m'auront surpris, ravi ou posé problème, en tant que développeur néophyte Ruby/Rails venant du monde PHP / Javascript :-)
A bientôt !
Bonjour !
Eh bien voici Ruby et Rails installés. Je me suis auto-formé rapidement sur les bases de Ruby et sur celles de Rails, il n'y a plus qu'à lancer la bête !
Au premier jour de mon auto-initiation j'avais cru comprendre en parcourant différentes ressources sur Internet qu'il était de bon ton d'installer Ruby via RVM. Ce que je fis.
Entre-temps des lecteurs avertis m'ont cependant révélé que RVM c'était bien-mais-pas-top. Notamment parce qu'une fois installé, la commande cd (celle-là même qui permet de changer de répertoire) de l'utilisateur est remplacée par une version “RVM-ienne”, qui se charge de changer automatiquement la version de Ruby utilisée si on débarque dans un dossier où se trouve un fichier “.rvmrc”, comme expliqué ici. L'idée ne semble pas mauvaise, mais sa mise en pratique via le remplacement d'une commande système aussi essentielle est effectivement sujette à débat.
Et j'avoue par ailleurs - à titre tout-à-fait subjectif - que si cela peut me permettre de ne plus avoir à aller sur le site Web de RVM, dont le design m'arrache une larme chaque fois que j'ai besoin d'aller vérifier un point de la documentation, je signe tout de suite :-)
Il existe plusieurs solutions alternatives assurant peu ou prou la même fonction que RVM, comme les chruby et ry qu'un lecteur m'a conseillés, mais je me suis finalement tourné vers rbenv.
Pourquoi celui-ci plutôt qu'un autre, me direz-vous ? Parce qu'il fallait bien faire un choix, et parce que son auteur, Sam Stephenson, bosse chez 37signals (chez qui Rails a été créé), et qu'il est notamment l'auteur de Prototype.js, eco ou Sprockets (l'équivalent railesque d’Assetic, si vou avez bossé avec Symfony 2), et que je me dis qu'un auteur aussi prolifique et semble-t-il talentueux sait a priori ce qu'il fait.
Sam m'a l'air de quelqu'un à qui on peut faire confiance, et étant donné que je débute avec Ruby et Rails ça me fait plaisir d'être rassuré.
Bref, je désinstalle RVM et je suis sagement les consignes d'installation de rbenv (entièrement manuelle, mais au moins je sais ce qui se passe sur ma machine contrairement à RVM), et me voici quelques minutes plus tard avec un Ruby géré par rbenv.
# désintallation de RVM
rvm implode
# téléchargement de rbenv
git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
# sous Ubuntu, remplacer "~/.bash_profile" par "~/.profile"
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
# idem
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
# recharge le shell avec rbenv
exec $SHELL -l
# installe le plugin rbenv "ruby-build"
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
# installe une version récente de Ruby 1.9.3
rbenv install 1.9.3-p392
# ...
# ça télécharge et ça compile. On a le temps de se faire un café...
# ...
# après la compilation, sélectionne cette version de Ruby par défaut sur la machine
rbenv global 1.9.3-p392
Vous noterez peut-être au passage que j'ai installé une version 1.9.3 de Ruby, et non la 2.0 que j'avais installée initialement avec RVM. C'est que l'on se trouve en ce moment dans une période charnière. Ruby 2.0 est sorti il y a quelques mois, pour les 20 ans du langage. C'est à cette occasion d'ailleurs que Yukihiro Matsumoto, dit “Matz”, le créateur de Ruby, nous a révélé une nouvelle approche du l'ingénierie logicielle : le Birthday Driven Development . Je suis impatient d'essayer cette nouvelle méthode :-)
Ruby 2 est sorti, donc, mais tout l'écosystème Ruby n'est pas encore à jour avec les quelques incompatibilités que cette nouvelle mouture apporte par rapport à la précédente. J'ai notamment rencontré avec cette 2.0 un soucis lors de l'installation de Passenger (que je détaillerai dans le prochain article).
J'ai donc préféré ne pas faire le chien fou, et rester sur une version moins avant-gardiste de Ruby. J'aurai bien le temps de jouer avec les nouveautés de la 2.0 plus tard, si tout se passe bien et que j'arrive à perdurer dans la “Voie du Ruby”.
Ruby 1.9.3 est proprement installé, j'ai réinstallé Rails avec un petit “gem install rails”, il est temps de lancer la bête ! Je me rends donc dans le répertoire de mon appli Rails (générée la veille avec “rails new blog –database=mysql”), et je m’apprête à enfin démarrer le moteur.
Dans un instant de lucidité je me rappelle néanmoins d’une étape que j’ai vue décrite dans tous les tutoriels que j’ai parcourus, que j’allais oublier : l’installation des Gems nécessaire à ma première application Rails !
Les Gems sont des bibliothèques logicielles Ruby, équivalentes à peu près aux packages PHP que l’on peut trouver sur Packagist. En Ruby ces bibliothèques sont donc des Gems, et elles sont regroupées sur le bien-nommé site RubyGems.org.
Pour les installer d'après les descriptions contenues dans un fichier texte, on a accès à un outil qui lui s’appelle Bundler.
Allez, un petit tableau récapitulatif :
| PHP | Node.js | Ruby |
|---|---|---|
| package | module | gem |
| Composer | npm | Bundler |
| composer.json | package.json | Gemfile |
On peut tout-à-fait installer des Gems avec uniquement l'outil en ligne de commande gem, qui va à la demande télécharger la Gem que vous lui demandez et l'installer sur votre machine.
Mais dès lors que l'on souhaite télécharger et installer plusieurs Gems en une seule fois d'après les indications contenues dans un fichier texte, Bundler (qui est lui-même une Gem) est nécessaire.
Ce fichier texte, à la racine du projet qui exprime ses dépendances, doit se nommer “Gemfile” (équivalent donc de “composer.json” en PHP ou de “package.json” en Node.js).
Si vous avez comme moi installé Ruby avec rbenv, Bundler n'est malheureusement pas encore présent, et il va nous falloir l'installer. Heureusement c'est très facile, l'utilitaire gem va le faire pour nous :
gem install bundler
Si vous n'avez pas installé le plugin rbenv rbenv-gem-rehash, il faut ensuite :
rbenv rehashIl n'est cependant pas forcément très commode de devoir lancer cette commande après chaque installation de Gem, aussi vais-je plutôt installer rbenv-gem-rehash.
Une fois cela fait, Bundler est disponible sur notre système, sous la forme d'une commande “bundle”.
Je me rends donc dans le répertoire où est installé mon appli RoR, et je demande à Bundler d'aller me télécharger et de m'installer toutes les Gems dont va dépendre mon application Ruby on Rails, telles que décrites dans le fichier Gemfile qui avait été automatiquement créé lors de la génération avec “rails new [nom du projet]” :
cd /home/oliv/_WORK/www/blog/
bundle install
Hop, ni une ni deux, voilà Bundler qui se connecte au dépôt central de Gems, et commence à télécharger et installer les différentes Gems qui figurent dans le fichier “Gemfile”, ainsi que les Gems dont elles dépendent.
Malheureusement la magie s'interrompt brusquement, avec un vilain message d'erreur concernant la Gem mysql2. Eh oui, j'ai fait mon malin en choisissant d'utiliser MySQL au lieu du SQLite que me proposait Rails par défaut, et j'avais donc remplacé la Gem “sqlite3” par “mysql2” dans mon “Gemfile” :
#gem 'sqlite3' # au revoir SQLite...
gem 'mysql2' # ...bienvenue MySQLJusque-là rien de bien litigieux. Mais le soucis, c'est que la Gem “mysql2” qui permet à Ruby de communiquer avec un serveur MySQL nécessite lors de son installation de compiler une partie de son code écrit en C. Et pour que ce code puisse être compilé, il est nécessaire d'avoir sur sa machine le code source C qui permet cette communication avec MySQL.
Sous Ubuntu, donc, j'ai dû installer le package ad hoc :
sudo apt-get install libmysqld-dev
Une fois cela fait, je relance bundle install, et tout se déroule cette fois comme sur des roulettes.
Pfwheee ! Eh bien, je l'aurais mérité mon premier lancement de Ruby on Rails ! :-) Bon, rien de bien méchant non plus : une fois les manips connues c'est en fait assez rapide à reproduire, et j'ai pu les refaire en 5 petites minutes sur une nouvelle machine virtuelle.
Allez, je commence tranquillou, avec le serveur Web intégré à Ruby on Rails. Ce serveur, WEBrick, n’a absolument pas vocation à être utilisé en production, mais il est semble-t-il bien utile pour tester son application Rails rapidement.
Je me rends donc dans le répertoire de mon appli Rails (générée la veille avec “rails new blog –database=mysql”), et je lance la commande suivante :
rails server
Dans mon cas le premier lancement fut un échec cuisant, avec un message d’erreur de 3km. Mais je suis motivé, j'en ai vu d'autres et je ne m'arrête pas là.
Alors alors, qu'est-ce qu'il y a qui va pas cette fois-ci ? Voyons… Le message d'erreur me parle de la nécessité d'installer… un moteur JavaScript.
JavaScript, vous ici ? Diable ! J'aime beaucoup JavaScript, mais je ne pensais pas l'avoir invité sur mon appli Ruby on Rails.
En y regardant de plus près, je repère le coupable : il s'agit de CoffeeScript. Ce bien chouette langage, géré de base par RoR, a originellement été écrit par son auteur en Ruby. Mais aujourd'hui le “transcompilateur” CoffeeScript est lui-même écrit en CoffeeScript, et compilé il nécessite un moteur JavaScript pour mener sa mission à bien.
Pour lancer automatiquement les compilations de mes futurs fichiers CoffeeScript en JavaScript, Ruby va donc demander à un moteur JavaScript de faire le boulot. Pour ce faire c'est la Gem “ExecJS” qui va être appelée à la rescousse, et elle est capable de communiquer avec 5 moteurs JavaScript différents - listé ici.
En ce qui me concerne je ne me suis pas pris la tête, et j'ai tout simplement installé Node.js sur la machine :
sudo apt-get install nodejs
Une fois cela fait, tout se passe bien cette fois, ExecJS a automatiquement détecté Node.js et pourra faire affaire avec lui le temps venu. Pour peu que j'ai correctement indiqué à RoR les identifiants de connexion à MySQL (c.f. ma config, en annexe), mon application Rails prend enfin vie !
Je me rends en HTTP sur ma propre machine, sur le port 3000, et la page d'accueil par défaut de Ruby on Rails s'affiche enfin dans toute sa splendeur.
Mais le temps passe, ma foi, et je vais clôturer ici-même cet article. Je n'ai pas démérité après tout, avec ma première application Ruby on Rails opérationnelle. Dans le prochain article j'installerai notamment un serveur Web un peu plus balèze que WEBrick, Phusion Passenger.
Je commencerai également, si tout se passe bien, à plancher sur mon premier projet Ruby on Rails.
Car oui, je suis bien content et bien chanceux. A peine ai-je commencé à me mettre à Rails que j'ai pu trouver une mission me permettant de mettre les mains dans le cambouis de manière furieusement concrète. Je suis impatient de voir ce que donne en conditions réelle le développement Ruby on Rails !
A bientôt !
Mon fichier “config/database.yml” :
development:
adapter: mysql2
encoding: utf8
host: localhost
database: blog_test
username: blog_test
password: blog_test
pool: 5
timeout: 5000
Dans le cas de l'utilisation de Vagrant : pour que le Ruby on Rails installé sur ma machine virtuelle VirtualBox puisse communiquer avec le serveur MySQL installé sur ma machine réelle, j'ai dû indiquer l'adresse “10.0.2.2” au lieu de “localhost”.
Si comme moi vous utilisez Vagrant pour gérer votre environnement Ruby, vous constaterez peut-être que la machine virtuelle rame fort à chaque fois qu'elle va chercher du contenu sur Internet. C'est bien dommage, puisqu'entre les installations des packages Debian et celles des différentes Gem, j'en ai bien besoin d'Internet.
J'ai trouvé sur le Web les lignes suivantes, à ajouter au fichier “Vagrantfile”, qui peuvent aider :
config.vm.provider :virtualbox do |vb|
vb.auto_nat_dns_proxy = false
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
end
Qui plus est, dès qu'on lance WEBrick la communication se fait extrêmement lentement, avec plusieurs secondes de latence entre la demande HTTP et la réponse.
Cela est apparemment dû à un problème avec VirtualBox, et pour y remédier, j'ai trouvé la solution suivante (précisément ici), qui fonctionne rudement bien : il suffit de passer à true la valeur de la directive :DoNotReverseLookup du fichier de config de WEBrick.
(chez moi à l'emplacement /home/vagrant/.rbenv/versions/1.9.3-p392/lib/ruby/1.9.1/webrick/config.rb)
Bonjour !
Eh bien me voici à présent au pied du mur. Après avoir mûrement réfléchi et décidé de tenter de passer du développement PHP au développement Ruby On Rails, et après avoir passé une journée à installer, configurer et tester les bases du langage Ruby, me voici prêt à me retrousser les manches pour enfin découvrir Ruby On Rails.
Première étape, donc : installer Rails sur ma machine. De ce coté c'est très simple. Si vous êtes sous Mac ou Linux (ou si vous passez comme moi par une VM, via Vagrant par exemple), installer Ruby on Rails est à peu près aussi long que taper
gem install railsVous l'avez compris si vous avez suivi le précédent épisode, cela demande tout bêtement au gestionnaire de packages Ruby, Gem, de télécharger et d'installer le package Rails.
Si vous êtes sous Windows avec un environnement Ruby bien configuré, vous pouvez tenter également cette ligne de commande, ça devrait fonctionner j'imagine. Et si vous avez été plus malin que moi et que vous avez installé Ruby via Rails Installer, comme on me l'a conseillé dans les commentaires, vous avez déjà le framework en votre possession.
Vous souvenez-vous de ce qu'était le développement PHP il y a encore quelques années ? Pour chaque projet on repartait de zéro, installant si besoin est quelques bibliothèques Pear, et on copiait-collait ça et là quelques fichiers PHP qui nous servaient de bibliothèques utilitaires génériques.
C'était rigolo, c'était les joies de l'artisanat, et c'est peut-être comme ça qu'on apprend le mieux à mettre les mains dans le cambouis, lorsqu'on débute.
Mais si vous avez travaillé avec Zend Framework, Symfony, Laravel, Silex ou n'importe quel autre surcouche PHP “majeure” dernièrement, ce temps de l'artisanat doit comme moi vous sembler bien lointain. En effet, aujourd'hui on ne démarre plus guère de projet PHP from scratch, et on utilise bien souvent quelque chose dans ce goût-là pour initialiser un projet :
composer create-project symfony/framework-standard-edition path/to/install 2.2.0
Je ne suis pas spécialiste de l'histoire des nouvelles technologies, et encore moins de celle de Ruby, mais sauf erreur de ma part ces processus-là, très carrés et très sérieux - “industriels”, comme on dit aujourd'hui - nous viennent encore une fois de Ruby on Rails.
C'est à mon humble connaissance le premier framework Web à avoir eu recours à des utilitaires en ligne de commande pour l'initialisation, la maintenance et le déploiement de sites Internet.
De là, la brèche était ouverte et nombreux sont ceux à s'y être engouffrés - et Fabien Potencier parmi les premiers, avec Symfony 1.
Mais foin de blabla, il était question ici de créer ma première application Rails !
Eh bien une fois la Gem Rails installée et bien campée sur ses positions, à l’affût de mes demandes, je n'ai plus qu'à me rendre dans le répertoire dans lequel je gère gère mes projets Web et à y lancer la commande (ici mon projet va s'appeler “blog”) rails new blog.
En ce qui me concerne je n'ai aucun doute sur les qualités intrinsèques de SQLite, et surtout pas dans le cadre d'un développement en local, mais en tant que vieux croûton habitué à MySQL j'ai préféré tout de suite indiquer à Rails que j'allais me connecter à mon bon vieux serveur MySQL, géré via mon fidèle camarade HeidiSQL, plutôt que de créer une base de données SQLite. Ca donne donc quelque chose dans ce genre :
cd work/www
rails new blog --database=mysqlOn voit alors la gem Rails créer tout un tas de répertoires et de fichiers. Ayant déjà travaillé avec des frameworks comme Symfony ou Zend Framework cela ne m'a pas trop choqué, mais si vous n'êtes pas déjà passé par cette case ça pourrait - à juste titre - vous effrayer.
Mais pas de panique, cela n'est que l'expression fort noble d'un concept cher à Rails, “Convention over Configuration” : plutôt que de laisser chaque développeur personnaliser son usage du framework comme il l'entend, RoR nous prend par la main avec douceur mais fermeté, et entend nous faire utiliser ses propres conventions plutôt que de nous laisser partir en free style.
Si vous n'êtes pas habitué à ce principe, cela vous gênera peut-être, mais en ce qui me concerne j'adhère plutôt pas mal à ce principe.
Il permet de retrouver ses marques instantanément dans n'importe quel projet utilisant cette même technologie, et nous laisse tout de même toute la logique métier de notre application pour épancher notre soif insatiable de liberté.
Oui, moi aussi j'aime parcourir les plaines et sentir le vent dans mes cheveux, sans avoir l'impression d'être restreint par un dogme technologique.
Mais j'accepte cette “Convention over Configuration” initiale parce que pour avoir vu des dizaines de projets Web architecturés au petit bonheur et pas toujours d'une bonne manière, je la pense judicieuse, et que je sais que ma créativité de codeur va pouvoir s'en donner à coeur joie là où est vraiment le vif du sujet - à savoir tout mettre en oeuvre pour réaliser une belle application correspondant aux besoins du client.
A ce stade-là j'ai ma première application “blog” automatiquement générée par Rails, et puisque je suis curieux il va bien me falloir décortiquer un peu toute cette structure avant de commencer bêtement à appliquer tout le bazar MVC.
Si vous avez déjà utilisé des technos comme Symfony ou Zend Framework, la structure de fichiers de RoR doit vous rappeler quelque chose, mais elle comporte tout de même quelques spécificités propres.
Je pourrais ici reproduire une énième copie de l'explication de l'arborescence de Rails, afin de vous faire partager mes recherches sur le sujet, mais je ne m'en abstiendrai car beaucoup l'ont fait avant moi, et d'une bien meilleure façon que je ne le ferais.
Ils ne se sont pas arrêté là, d'ailleurs, les bougres. Non, car tout dans l'esprit de partage qui caractérise les développeurs Ruby et qui…
Ah oui, j'ai oublié de parler de ça. Ruby on Rails a l'air d'être assez ardu à assimiler, même quand on vient de frameworks à peu près équivalents en PHP. Heureusement, pour m'aider à franchir le cap j'ai vraiment eu l'impression pour le moment que la communauté Ruby était ouverte et plutôt prompte à aider son prochain. Ce qui est toujours agréable lorsqu'on est nouveau venu, et me rassure en cas de pépin si j'ai besoin d'aide. Cela ne veut pas dire pour autant que j'ai été reçu comme un chien galeux dans la communauté PHP ou Node.js, hein :-) Mais celle de Ruby a l'air plutôt sympatoche en tout cas.
Où en étais-je ? Ah oui, l'esprit-de-partage-de-la-communauté-Ruby, donc. Eh bien oui, il existe un grand nombre de guides et tutoriels pour s'initier à Ruby on Rails, ce qui est plutôt bath.
Pour ma part, l'un des éléments qui m'a motivé à tenter cette transition de PHP vers RoR n'est rien d'autre qu'un tutoriel vidéo interactif gratuit. Il n'est disponible qu'en anglais, mais on peut afficher des sous-titres (anglais) pour aider à la compréhension, si besoin est.
Ce tutoriel est vraiment excellemment fait : on visionne une courte vidéo, puis on nous pose une série de cinq questions, auxquelles on répond par une ou plusieurs lignes de code Ruby. A travers ces vidéos on entraperçoit vraiment le potentiel de Ruby on Rails, et c'est vraiment après avoir fait ce tutoriel “Rails for Zombies” que je me suis dit qu'il fallait que je m'y mette :-)
Une fois ce tutoriel vidéo fait, il m'a bien fallu aller chercher quelque chose de plus substantiel. Une première recherche sur le site officiel de Rails m'a donné ainsi accès aux “guides Rails”, en anglais, avec notamment cette mise en jambe qui aborde rapidement les concepts clefs du framework : Getting started with Rails
Mais puisqu'il y a moult à lire (RoR est vraiment un gros framework), et même si je lis pas trop mal l'anglais après toutes ces années à lire des documentations IT, mon cerveau a tout de même une préférence naturelle pour le texte en français lorsqu'il doit ingurgiter une aussi grande quantité d'informations.
Si votre cerveau fait ce qu'il peut donc, comme le mien, vous aurez peut-être avantage à plutôt vous diriger vers le lien suivant. C'est une traduction en français des mêmes “guides Rails”, et même s'ils ne portent pas sur la toute dernière version du framework (la traduction a dû prendre un peu de retard) j'imagine que la majorité des principes qui y sont détaillés continuent de s'appliquer : Débuter avec Rails
J'en profite pour citer au passage ce même site railsdebutant.org : “Nous parlons d'un framework conçu par un Danois dans un langage inventé par un Japonais et utilisé majoritairement aux Etats-Unis et en Angleterre !”. Je me sens vraiment citoyen du monde.
Mais tout cela n'était que peu de chose en réalité comparé à ce que j'ai découvert par la suite, après de plus amples recherches ! Un livre en ligne qui donne une vision d'ensemble de Ruby on Rails, avec des exemples pratiques, des références dans tous les sens et un contenu exhaustif, traduit en plusieurs langues dont le français, véritable mine d'or de connaissances sur RoR :
http://french.railstutorial.org/
Je ne sais pas si cet ouvrage fait autorité ou non parmi les développeurs Ruby on Rails, et je ne sais même pas qui s'est donné la peine de traduire un aussi grand volume de tutoriel, mais c'est tout bonnement épatant.
(prenez garde par contre à la licence de ce livre, qui comporte un piège caché : tout ce contenu est sous la terrible licence “BEERWARE”, qui vous demande de payer une bière à l'auteur du livre si vous le croisez un jour et que vous pensez que son travail en vaut la peine. A vous de voir si vous voulez risquer le coup. Vous êtes prévenu.)
EDIT : j'ai finalement trouvé le dépôt GitHub des traductions de ce livre. D'après ce que j'ai vu des commits tout ce contenu a été traduit en français par Philippe Perret - un grand merci à lui ! :-)
En ce qui vous concerne vous êtes peut-être quelqu'un de bien peu attaché aux cours magistraux, et préférez aller vous frotter tout de suite à du code réel. Le confort douillet de la cheminée en lisant un tutoriel, un verre de bourbon à la main, très peu pour vous. Vous êtes hot like lava, et avez envie d'aller voir tout de suite ce que donne Rails utilisé sur des sites en production avec des milliers de pages vues.
Je ne reprendrai pas là la liste des principaux “grands sites” basés sur Rails, que vous pourrez trouver un peu partout, mais si vous voulez voir le code source d'applis Rails “de la vraie vie” je ne peux que vous recommander ces quelques dépôts d'applis Rails Open Source sur GitHub, qui devraient satisfaire votre curiosité :
Je les ai pas mal parcouru, pour ma part. Et même si je n'ai pas encore tout saisi, je trouve intéressant de voir, au-delà des blogs que proposent les tutoriels, à quoi ressemble du “vrai” code Ruby on Rails.
Qui plus est, je me dis que je pourrai toujours me reporter à ces codes sources d'applis matures, développées par des pros du RoR, si je me trouve bloqué dans une impasse.
Eh bien en fait non, je ne suis pas allé plus loin pour ce deuxième jour.
J'ai pensé qu'il était nécessaire de vraiment commencer à assimiler les premières bases du framework.
Rails est vraiment un énorme ensemble logiciel : il doit être tout-à-fait possible de commencer à coder une appli RoR avec pour seul bagage la lecture du guide “Getting started”, mais pour ma part j'ai pensé que ça n'était pas forcément une bonne idée de partir à l'aventure sans équipement.
Aussi, et même si j'avais bien sûr hâte de coder mes premiers Contrôleurs, Modèles et Vues dans mon application “blog”, cette deuxième journée d'initiation Rails a été principalement consacrée à la lecture de ce gigantesque tutoriel Ruby on Rails.
J'avoue ne pas avoir reproduit toutes les manipulations indiquées par le livre, mais je l'ai lu à peu près en entier (même si c'était en diagonale, pour certaines parties) et j'ai l'impression à présent de mieux savoir où je vais.
Voilà qui clôt ma deuxième journée d'apprentissage de Rails. Je vous retrouve prochainement pour la suite de mon récit, avec notamment l'utilisation de Bundler, puis ma première application Rails, en immersion immédiate dans un projet client réel.
- oh mais quel teasing ! :-)