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 :
- 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.
- L’utilisateur devait pouvoir le déployer sans éditer ses templates ni manipuler le mondre code.
- 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.
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.rbPuis, dupliquez l’arborescence d’une application Rails normale dans votre plugin :
neuro$ cd vendor/plugins/typo_contactform/lib
neuro$ mkdir -p app/controllers/admin \
app/models app/views/contact_form app/views/admin/contact_form \
app/helpersOuvrez 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 :
%w{ models controllers }.each do |dir|
path = File.join(File.dirname(__FILE__), 'app', dir)
$LOAD_PATH << path
ActiveSupport::Dependencies.load_paths << path
ActiveSupport::Dependencies.load_once_paths.delete(path)
end
ActionController::Base.send :helper, ContactformHelperCe 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 :
unless ContactForm.table_exists?
ActiveRecord::Schema.create_table(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
endAu 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 :
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 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 :
class ContactformController < ContentController
unloadable
layout :theme_layout
before_filter :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
endSi jamais vous n’utilisiez pas Typo, le bloc de code ci-dessous ressemblerait très probablement à :
class ContactformController < ApplicationController
unloadable
layout :theme_layout
before_filter :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
endVous 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/typo_contact_form.rb, et ajoutez-y le code suivant :
unless ContactForm.table_exists?
admin = Profile.find_by_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" }
endVous 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.
module Admin; end
class Admin::ContactformController < Admin::BaseController
unloadable
layout :set_layout
before_filter :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Ç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.
Publié le 14 juillet 2009 à 18h34 Publié sous Développement
Si cet article vous a plu, n'hésitez pas à me suivre sur Twitter.
2 commentaires sur Un système de plugins avancé pour vos applications Rails »
-
Par Lanza le 15 juillet 2009 à 12h59 :
-
Par Loïc Chollier le 15 juillet 2009 à 17h37 :
“Bidouiller $LOAD_PATH” ( http://t37.net/20-mauvaises-pratiques-de-developpement-quand-on-developpe-avec-ruby-on-rails.html )
Blague à part, billet très intéressant, ça devrait permettre à typo d’avoir des plugins un peu plus consistant que ceux qui squattaient la sidebar ! merci :)
Trackbacks sur Un système de plugins avancé pour vos applications Rails
Les trackbacks sont fermés pour cause de spam.
L'ergonomie web, l'utilisabilité et la qualité des logiciels sont trois grandes passions mises au services de ma profession.
Merci. Ça arrive juste à point, ce petit didacticiel :)