mercredi 28 juillet 2010

5 astuces pour #rails


1) La mise au point.
Le debug du code en Rails est très facile.
Il faut tout d'abord avoir installé le gemme ruby-debug.
Puis on peut ajouter "debugger" aux endroits stratégiques . "debugger" qui va provoquer un point d'arrêt dans le code.
Le lancement du serveur se fera avec l'option script/server --debugger

Avec cette option le serveur va exécuter le code jusqu'au point d'arrêt et vous rend la main. Vous pouvez alors examiner les variables, faire du ligne à ligne.

Sans utiliser le debug , il est parfois utile d'accéder aux paramètres d'une transaction, il suffit d'insérer dans le code du contrôleur l'instruction "raise" pour provoquer un plantage et un affichage de toutes les variables de la requete.

Enfin , dans les vues la fonction "debug(nom_de_variable") affiche le contenu des objets, des lignes d'un tableau etc.

2) Les observateurs.

Le design pattern 'observer' permet d'introduire une dépendance souple entre deux classes.
Ce motif sert de lien entre deux classes en observant les évènements des instances d'une classe.
Le cas d'utilisation simple est le déclenchement de la journalisation à la suite d'un évènement sur un objet. Par l'utilisation des observers, on évite de polluer le code d'une classe avec des opérations qui ne sont pas directement dans le périmètre d'un objet.

Je m'en sert pour tenir à jour des compteurs de lignes.
Exemple: j'ai deux classes , une pour 'projet' et une autre pour 'tache_annuelle'. Je souhaite avoir une variable d'instance qui pour chaque projet retrace le nombre de ligne de la table des taches. Cette opération peut etre réalisée en SQL mais l'affichage de 60 projets avec pour chacun une requête SELECT COUNT plomberait les performances.

J'ai utilisé un observer qui scrute deux évènements : la création et la suppression des lignes de la table tache.

(Zeya:/usr/local/projetsawd# emacs app/models/auditeur_observer.rb)



class AuditeurObserver < ActiveRecord::Observer
observe Lignepaatec

def after_create(ligne)
@projet= Projet.find(ligne.projet_id)
@projet.paa+=1
@projet.save
end
def after_destroy(ligne)
@projet= Projet.find(ligne.projet_id)
@projet.paa-=1
@projet.save
end


end

Le fichier auditeur_observer.rb doit se trouver dans le répertoire des modèles et chargé au démarrage du serveur par l'ajout de cette ligne dans le fichier config/environment.rb

config.active_record.observers = :auditeur_observer

J'ai appris depuis qu'il est possible de générer automatique les fichiers pour les observers par la commande: script/generate observer


Zeya:/usr/local/projetsawd# script/generate observer essai_eric
exists app/models/
exists test/unit/
create app/models/essai_eric_observer.rb
create test/unit/essai_eric_observer_test.rb



3) La meta-programmation au service du DRY.

Le Don't Reapeat Yourself doit commencer à s'appliquer à soi. Ainsi , je suis parfois amené à saisir plusieurs fois la même chose ou avec quelques nuances.
J'ajoute aux formulaires de modification (action => edit) un bouton nommé 'clone' . Quand je veux obtenir une entrée qui diffère légèrement d'une autre, j'édite l'entrée servant de modèle et après quelques ajustements, je soumet le formulaire avec le bouton 'clone'. J'obtiens ainsi une nouvelle entrée avec peu de saisie.
La procédure update est chargée de propager les modifications ou de cloner, elle récupère les paramètres du formulaire et instancie un nouvel objet avec.

def update
@paatec = Paatec.find(params[:id])

if params[:commit] == 'Clone' then
@paatec_clone= Paatec.new
cle= params[:paatec].keys
cle.each do |k|

@paatec_clone.send "#{k}=" , params[:paatec][k.to_sym]
end
@paatec_clone.save
respond_to do |format|

format.html { redirect_to(@paatec_clone) }
format.xml { render :xml => @paatec_clone, :status => :created, :location => @paatec_clone }
end
else
....


La ligne magique est celle ci :
@paatec_clone.send "#{k}=" , params[:paatec][k.to_sym]

Elle illustre la puissance de la meta-programmation :
Elle appelle successivement tous les 'setter' par la méthode send. La méthode send attend en paramètre un nom de méthode: exemple objet1.send("libelle") est équivalant à objet1.libelle qui est un 'getter'. Activer un 'setter' de cette manière est plus difficile car il faut ajouter le '=' à la fin du nom de la méthode.

ps : ici la notion de 'clone' n'a pas de rapport avec la la méthode clone ou dup de ruby.





4) L'export CSV.
J'ai aussi des exports CSV à faire. Il y a plusieurs techniques , j'ai choisi celle-ci:
A ajouter dans  un contrôleur
...
require 'csv'
....

def export_csv

cond= params[:cond]
tab =cond.split ('%')


@projets= Projet.find_select(tab[0],tab[2],tab[1])
# @projets =Projet.find(:all)
report = StringIO.new
CSV::Writer.generate(report, ';') do |csv|
csv << ['Projet','Libelle','domaine','diDEV','diPROD','MOA','Filiere','Techno']
@projets.each do |projet|
csv << [projet.nom,projet.libelle,@template.sauf_null(projet,'domaine','libelle'),@template.sauf_null(projet,'didev','libelle'),@template.sauf_null(projet,'diprod','libelle'),
@template.sauf_null(projet,'moa','libelle'),@template.sauf_null(projet,'filiere','libelle'),@template.sauf_null(projet,'techno','libelle')
]
end
end
report.rewind
send_data(report.read,
:type => 'text/csv; charset=iso-8859-1; header=present',
:filename => 'report.csv',
:encoding => 'utf8'
)
end


end



Dans le code on peut constater l'utilisation de la variable @template. Cette astuce permet de réutiliser les méthodes écrites pour les vues (helpers). La variable globale @template, contient tous les helpers.

5) Le bridage de l'application.

Protéger son application par un user/password est parfois un peu lourd pour une application basique. Je désire seulement pouvoir contrôler les périodes de mise à jour. Pour cela j'utilise un filtre callback 'before_save' et before_destroy dans le modèle :

############ bloquer tout ################
before_destroy :periode
before_save :periode
#########################
def periode
errors.add_to_base("la mise a jour est interdite")
return false
end


Pour afficher l'origine de l'erreur dans les messages flash , quelques lignes dans la méthode destroy du contrôleur suffisent :


if !@lignepaatec.destroy then
flash[:notice] ="Mise a jour interdite"
end



En bonus une 6 eme astuce :

La commande: rake routes affiche toutes les url reconnues pas l'application.


portable:/usr/local/projetsawd# rake routes
(in /usr/local/projetsawd)
statuts GET /statuts(.:format) {:controller=>"statuts", :action=>"index"}
POST /statuts(.:format) {:controller=>"statuts", :action=>"create"}
new_statut GET /statuts/new(.:format) {:controller=>"statuts", :action=>"new"}
edit_statut GET /statuts/:id/edit(.:format) {:controller=>"statuts", :action=>"edit"}
statut GET /statuts/:id(.:format) {:controller=>"statuts", :action=>"show"}
PUT /statuts/:id(.:format) {:controller=>"statuts", :action=>"update"}
DELETE /statuts/:id(.:format) {:controller=>"statuts", :action=>"destroy"}




Good hacking.

Aucun commentaire: