Frederic de Villamil


, , , , , , ,


Partagez sur Twitter Partagez sur Facebook Partagez sur Google Plus Partagez sur Linkedin Plus

Un système de plugins avancé pour vos applications Rails

Disclaimer

Ce qui va suivre est (probablement) très particulièrement sale. Il existe certainement d’autres manières beaucoup plus propre d’arriver au même résultat de manière simple, mais je ne les ai pas trouvées.

Tout ce que vous pourrez lire par la suite a été écrit sous la menace des deux inséparables compagnes Grey Goose et Red Bull et la responsabilité ne saurait m’être imputée de quelque manière que ce soit. Enfin, petit rappel : ne le faite pas chez vous, à moins de savoir ce que vous faites.

Ceci étant dit, passons aux choses sérieuses.

Introduction

Ma TODO s’enorgueillit depuis deux ans du souhait de doter Typo, le blogware basé sur le framework Ruby on Rails qui équipe ce site, d’un système de plugins modernes, dont le moindre défi n’est pas de ne nécessiter aucune intervention de l’utilisateur post installation.

Mes besoins côté utilisateur étaient simples :

  1. Le plugin devait offrir de nouvelles fonctionnalités en s’intégrant toalement dans l’application, et notamment en en reprenant le looks and feel.
  2. L’utilisateur devait pouvoir le déployer sans éditer ses templates ni manipuler le mondre code.
  3. Le plugin devait pouvoir également s’intégrer dans l’administration de l’outil en cas de besoin : paramétrage, gestion de contenus…

Et surtout, il était hors de question de demander aux utilisateurs de toucher à config/routes.rb

Si vous débutez dans le développement Rails, vous n’avez sans doutes pas encore joué avec les routes plus que nécessaire. Celles-ci permettent de définir que l’accès à telle ou telle URL dans l’application va entraîner l’appel à tel contrôleur, et à telle méthode.

C’est muni de ces problématiques que j’ai commencé à boire combattre l’insomnie et les crétins qui faisaient exploser des feux d’artifices à deux pas de la maison afin d’atteindre le Saint Graal : un formulaire de contact.

Ne rigolez pas. La création d’un plugin apparemment très simple devient tout de suite beaucoup plus complexe avec les pré requis exposés ci-dessus.

Création du plugin

Commencez par créer un plugin Rails des plus standards.

<typo:code lang=’shell’> neuro$ ./script/generate plugin typo_contactform

  create  vendor/plugins/typo_contactform/lib
  create  vendor/plugins/typo_contactform/tasks
  create  vendor/plugins/typo_contactform/test
  create  vendor/plugins/typo_contactform/README
  create  vendor/plugins/typo_contactform/MIT-LICENSE
  create  vendor/plugins/typo_contactform/Rakefile
  create  vendor/plugins/typo_contactform/init.rb
  create  vendor/plugins/typo_contactform/install.rb
  create  vendor/plugins/typo_contactform/uninstall.rb
  create  vendor/plugins/typo_contactform/lib/typo_contactform.rb
  create  vendor/plugins/typo_contactform/tasks/typo_contactform_tasks.rake
  create  vendor/plugins/typo_contactform/test/typo_contactform_test.rb
  create  vendor/plugins/typo_contactform/test/test_helper.rb

</typo:code>

Puis, dupliquez l’arborescence d’une application Rails normale dans votre plugin :

<typo:code lang=’shell’> neuro$ cd vendor/plugins/typocontactform/lib neuro$ mkdir -p app/controllers/admin \ app/models app/views/contactform app/views/admin/contact_form \ app/helpers </typo:code>

Ouvrez maintenant votre éditeur de texte favori. Si vous ne l’avez pas encore adopté, pourquoi n’essaieriez-vous pas Textmate ? Éditez votre nouveau plugin, les choses sérieuses commencent.

Forcez Rails à intégrer votre plugin dans ses dépendances

Éditez le fichier lib/typo_contactform.rb afin d’y ajouter le code suivant :

<typo:code lang=’ruby’> %w{ models controllers }.each do |dir| path = File.join(File.dirname(FILE), ‘app’, dir) $LOADPATH << path ActiveSupport::Dependencies.loadpaths << path ActiveSupport::Dependencies.loadoncepaths.delete(path) end

ActionController::Base.send :helper, ContactformHelper </typo:code>

Ce bloc ajoute vos contrôleurs, modèles et helpers dans les dépendances Rails. Vos contrôleurs pourront notamment hériter d’autre chose que de la classe ActionController::Base, par exemple, d’ApplicationController.

Profitez-en également pour déclarer auprès de Rails vos helpers pour ce qu’ils sont, vous en aurez certainement besoin plus tard.

Création des tables

Toujours dans lib/typo_contactform.rb ajoutez le code suivant :

<typo:code lang=’ruby’> unless ContactForm.tableexists? ActiveRecord::Schema.createtable(ContactForm.table_name) do |t|

t.column :name,           :string
t.column :email,          :string
t.column :address,        :string
t.column :message,        :text
t.column :created_at,     :datetime
t.column :updated_at,     :datetime

end end </typo:code>

Au chargement de votre application, Rails créera la table contact_forms si cette dernière n’existe pas encore.

Ajout des routes à la volée

C’est là que les choses passent de pas super propres à franchement SALE. Si vous avez peur de ne pas le supporter, servez-vous un grand verre de vodka bien glacée et buvez-le cul sec. Ça ira certainement mieux après.

Éditez init.rb et ajoutez le code suivant au tout début du fichier :

<typo:code lang=’ruby’> ActionController::Routing::Routes.draw do |map| map.connect ‘/contactform/:action’, :controller => ‘contactform’ map.connect ‘/admin/contactform/:action’, :controller => ‘admin/contactform’ end

ActionController::Routing::Routes.draw do |map| map.connect ‘/contactform/:action’, :controller => ‘contactform’ end
</typo:code>

ActionController::Routing::Routes.draw est appelé par Rails après le chargement des plugins. Il commence par appeler la méthode clear! qui vide la table de routage de votre application de son contenu, en commençant par les règles que votre plugin vient de définir. Pour cette raison, vous devez d’abord la surcharger.

Extension du chemin des vues

Le code contenu dans ce paragraphe concerne plus particulièrement Typo, qui possède un système de template particulier. Vous devrez sans doutes l’adapter un peu à votre application. Éditez pour cela le fichier app/controllers/contactform_controller.rb, et faites le commencer comme suit :

<typo:code lang=’ruby’> class ContactformController < ContentController unloadable layout :themelayout beforefilter :template_root

def template_root

self.view_paths = ::ActionController::Base.view_paths.dup.unshift("#{RAILS_ROOT}/vendor/plugins/typo_contact_form/lib/app/views")

end end </typo:code>

Si jamais vous n’utilisiez pas Typo, le bloc de code ci-dessous ressemblerait très probablement à :

<typo:code lang=’ruby’> class ContactformController < ApplicationController unloadable layout :themelayout beforefilter :template_root

def theme_layout

"#{RAILS_ROOT}/app/views/layouts/default.html.erb"

end

def template_root

self.view_paths = ::ActionController::Base.view_paths.dup.unshift("#{RAILS_ROOT}/vendor/plugins/typo_contact_form/lib/app/views")

end end </typo:code>

Vous indiquez d’abord au plugin où aller chercher le layout de l’application. Puis, vous étendez le chemin d’accès aux vues afin de permettre à Rails de trouver celles de votre plugin.

L’appel à unloadable résoud les plantages à base de A copy of ApplicationController has been removed from the module tree but is still active! qui surviennent quand vous allez chercher à charger la page de votre plugin une seconde fois.

À ce stade, si vous avez mal à la tête, servez-vous une autre vodka, ça passera tout seul.

Et l’admin dans tout ça ?

Je vous avais promis que votre plugin serait administrable. Chose promise, chose due – et non rose promise chomedu comme le disait un tract de l’UNI à la fin des années 90. Prenez un verre, remplissez-le à moitié de vodka, et à moitié de sirop de cramberrie, détendez-vous, ça arrive.

Éditez à nouveau le fichier link/typocontactform.rb, et ajoutez-y le code suivant :

<typo:code lang=’ruby’> unless ContactForm.tableexists? admin = Profile.findby_label(‘admin’) admin.modules << :contactform admin.save end

AccessControl.map :require => [ :admin, :publisher, :contributor ] do |map| map.project_module :contactform, nil do |project|

project.menu    "Contact",  { :controller => "admin/contactform", :action => "index"  }

end </typo:code>

Vous venez d’un seul coup d’ajouter l’entrée Contact Form dans votre administration – si vous utilisez le Typo Login System – et d’ajouter les ACL qui vont bien aux administrateurs de votre application.

Créez un fichier app/controllers/admin/contactform_controller.rb.

<typo:code lang=’ruby’> module Admin; end class Admin::ContactformController < Admin::BaseController unloadable layout :setlayout beforefilter :template_root

def template_root

self.view_paths = ::ActionController::Base.view_paths.dup.unshift("#{RAILS_ROOT}/vendor/plugins/typo_contact_form/lib/app/views")

end

def set_layout

"#{RAILS_ROOT}/app/views/layouts/administration.html.erb"

end end </typo:code>

Ça ressemble pas mal à ce que nous avons vu précédemment non ?

Conclusion

Et voilà, c’est terminé, vous connaissez le plus compliqué. Pour le reste, c’est du développement Rails des plus standards. J’ai posé le code du Typo Contact Form sur Gighub. Tout n’est pas totalement terminé, mais le proof of concept est là. En espérant que ça vous donne envie d’en faire d’autres.