Il est particulièrement simple, en Ruby, de parser un flux RSS.
Tout d’abord, dans le contrôleur, nous chargeons le contenu et le parsons :
require 'rss'
require 'open-uri'
@content = RSS::Parser.parse(open('http://feeds2.feedburner.com/DmathieuFr'), false)

La variable @content contiendra alors votre flux RSS parsé et chacun de ses éléments accessible.

Par la suite dans votre vue, vous pouvez récupérer les éléments de votre flux :
<ul>
  <% @content.items.each do |i| %>
    <li><%= link_to i.title, i.link %><br /><%= i.description %></li>
  <% end %>
</ul>

Nous bouclons sur chaque élément et affichons un lien pointant vers la balise link et ayant le titre; suivi du contenu.
Cela ne fonctionne évidemment que pour les flus RSS. Pour le XML en général, il faut utiliser REXML.

Pour finir (et parce que c’est dans le titre), il y a une petite exception pour le flux RSS de Flickr.
En effet, avec celui-ci, le parseur ne trouvera pas l’élément dateTaken mais date.Taken.
Il faut donc remplacer l’un par l’autre.

Voici donc le code de mon contrôleur :
@content = RSS::Parser.parse(open('http://api.flickr.com/services/feeds/photos_public.gne?id=23186094@N07&lang=fr-fr&format=rss_200').read.gsub!('date.Taken', 'dateTaken'), false)

Et dans ma vue, pour afficher les dernières photos postées sur mon pool :
<ul>
  <% @content.items.each do |i| %>
    <li><%= i.description %></li>
  <% end %>
</ul>

Et vous pouvez même voir la chose en action ! Sur mon portfolio :)

Quelque soit le langage, un élément est toujours délicat à traiter : créer des url cool, avec des vrais mots.
Mais en ne plaçant pas de caractères qui vont rendre l’url toute pas jolie avec des %20 et autres.

Pour cela, il faut supprimer tous les accents (et les remplacer par leur lettre sans accent); supprimer les espaces (et les remplacer par des -) et supprimer les signes de ponctuation (et ne pas les remplacer).

Avec Django, pas besoin de s’embêter avec ça :)
Il y a les slugs.
Par principe, un slug contiendra toujours une valeur qu’il est possible de mettre en url et sera toujours unique.
A vous par la suite de l’utiliser pour créer l’url de vos objets.

Mais il faut encore le définir correctement :)
Pour cela, dans votre administration, nous allons placer un prepopulated_field.
Placez dans vos fieldsets le titre de l’uplet et son slug.

Puis placez le code suivant en dessous de la définition des fieldsets :

prepopulated_fields = {'slug': ('title',)}

Ou slug est le nom du champ « slug » (je vous conseille de l’appeller slug).
Et ou title est le nom de votre champ de titre.

Chargez votre administration, remplissez votre titre et admirez votre champ de slug se remplir tout seul :)
Vous constaterez également qu’il ne se remplit que lors de la première édition, pour éviter un changement d’url inopportun lorsque vous changez le titre de la page.
Rien ne vous empêche par ailleurs de placer le slug de votre choix.

Vous pouvez par ailleurs personnaliser la chose dans votre application.
Par exemple dans un template, pour afficher la valeur d’un string slugifié :

{{ "mon Test"|slugify }}

Affichera : mon-test

Et dans vos fichiers .py directement également

from django.template import defaultfilters
slug = defaultfilters.slugify("Mon Test")

la variable slug contiendra alors mon-test.

Et vous pouvez faire de belles URLs. N’oubliez pas d’utiliser les vues génériques pour trouver le contenu qui correspond à un slug et le charger :)

Derrière ce mot compliqué « circulaire » se cache l’un des effets pervers de Python.
En effet, pour obtenir accès à des méthodes situées dans des fichiers différents, Python requiert que l’on importe celles-ci.

from mon_fichier import ma_methode
Dans l’idée, c’est cool. Ca permet de ne charger que la méthode désirée en mémoire et évite la surcharge.

Dans la pratique, supposons le cas suivant :
- Un fichier comments.py, qui contient la méthode getPost.
Cette méthode permet d’obtenir le billet dans lequel a été fait le commentaire.
- Un fichier posts.py, qui contient la méthode getComments.
Cette méthode permet d’obtenir tous les commentaires d’un billet.

Dans comments.py, vous faites :
from posts import *
Avec un * parce que vous désirez pouvoir obtenir non seulement le getComments, mais également toutes les méthodes de posts, qui peuvent être aussi diverses que votre imagination le permet.

Dans posts.py, vous faites :
from comments import *
Le *, pour la même raison que précédemment.
La, c’est balot. Mais le second import ne fonctionnera pas.
Avec une erreur du genre :

NameError: name ‘getPost’ is not defined

Python n’arrive pas à charger la classe posts car elle a déjà été chargée auparavant.

Heureusement, nous avons une solution ! :)
Ne faites pas de from class import *. Mais plutôt :
import posts
import comments

La différence est au premier abord anodine. Nous n’aurons plus accès directement à notre méthode getPost, mais à comments.getPost.
Du coup nous allons, en spécifiant l’espace de noms, pouvoir inclure nos classes en les croisant, l’une dans l’autre.

Je ne suis pas sur que cela soit très clair et je ne vois pas trop comment expliquer cela différemment. Donc un petit résumé :
Si vous cherchez à importer une méthode de la classe qui a importé la méthode dans laquelle vous êtes (la, déjà, si vous êtes perdus, on est mal barrés), ne faites pas de from class import method
Mais plutôt un import class.
Puis appellez votre méthode avec un class.method.

Merci à Philippe et à Guillaume Ayoub (pour qui je n’ai pas de lien. Si il passe par ici, qu’il n’hésite pas à faire signe).

Une faille de sécurité a été détectée récemment dans l’identification par http digest de Rails.
Dans la méthode authenticate_with_http_digest plus précisément.

Cette méthode permet d’identifier un utilisateur. J’en ai déjà parlé ici.
Si vous regardez cet article, vous constaterez le code suivant :
authenticate_or_request_with_http_digest("Identification requise") do |name|
  users[name]
end

(enfin pas exactement car j’ai appliqué la correction « dans l’application »).

Si l’utilisateur correspond, on retourne le mot de passe. Sinon on retourne nil.
Et c’est bien la le problème. La méthode attends le mot de passe ou false. Sinon elle tente de comparer le mot de passe avec nil.
Il suffirait donc que ce mot de passe soit vide et l’utilisateur est identifié.

Cette faille a déjà été corrigée. Mais ne sera disponible que dans la prochaine release.
En attendant, la correction officielle est de faire la chose suivante :
authenticate_or_request_with_http_digest("Identification requise") do |name|
  users[name] || false
end

Ainsi, soit on retourne le mot de passe; soit l’utilisateur n’est pas trouvé et on retourne false : L’identification est refusée.

Par ailleurs j’en profite pour vous conseiller mon extension simple auth, qui permet d’implémenter un système d’identification, volontairement mono utilisateur par HTTP.
Il est notamment utilisé sur mon portfolio.

Pour finir le problème de cette faille est la manière dont elle a été gerée d’un point de vue communication par l’équipe rails.
Nate, qui a découvert la faille se serait apparemment fait rembarrer plusieurs fois avant de, finalement, choisir de publier la faille pour qu’elle soit enfin corrigée.
Du coup ça troll dans les commentaires de l’annonce officielle.

Un petit article rapide. Lorsque vous testez vos applications, l’envoi d’email est toujours embettant. Vous n’avez pas envie de l’email parte vraiment et souhaitez vérifier qu’il pars correctement.

Du coup il faut utiliser un Mock, qui va simuler l’envoi d’email mais n’enverra, en fait, rien.
Et qui vous permettra tout de même de surveiller l’email envoyé et de vérifier qu’il est correct.

Commençons, dans notre vue, par envoyer notre email :
from django.core.mail import send_mail
def envoi(self):
  send_mail(
    'A propos des tests',
    'C\'est fun quand même',
    'from@example.com',
    'to@example.com'
)

Et testons notre méthode :
from django.core import mail
def test_send(self):
  response = self.client.post('/envoi')
  self.failUnlessEqual(response.status_code, 302)
  self.assertEquals(len(mail.outbox), 1)
  self.assertEquals(mail.outbox[0].subject, 'A propos des tests')

Ainsi lorsque nous lançons notre test, en supposant que l’url /envoi soit configurée pour diriger vers notre action envoi, celle-ci sera exécutée, l’email sera envoyé de manière fictive.
Et vous aurez accès à un tableau dans mail.outbox qui contiendra tous les emails envoyés avec leur contenu, leur sujet et les personnes à qui ils ont été envoyés.

A vous, par la suite, d’écrire les tests appropriés au bon fonctionnement de votre application.

Comme tout framework qui se respecte, Symfony possède une interface permettant d’écrire et exécuter des tests unitaires et fonctionnels.
Voyons un petit peu celle-ci.

Dans votre projet, vous avez le dossier test qui contient trois dossiers.

Bootstrap

Ce dossier contient deux fichiers. functional.php et unit.php.
Ils sont à inclure au début de chacun de vos tests et permettent d’instancier l’environnement.

Unit

Passons donc maintenant aux choses sérieuses avec les tests unitaires :)
Voici un exemple de tests :
require_once dirname(__FILE__).'/../bootstrap/unit.php';

$t = new lime_test(2, new lime_output_color());

$t->comment('is the valid() method valid?');
$t->is(Post::my_test(true), true, 'valid() return the argument we give him');
$t->is(Post::my_test(false), false, 'valid() return the argument we give him');

A côté de ce test, nous avons un modèle Post qui contient une méthode my_test, celle-ci étant on ne peut plus basique puisqu’elle retourne la valeur que l’on lui donne.

Pour commencer, vous constatez que en première ligne de nos tests, nous incluons le bootstrap des tests unitaires.

Ensuite nous créons notre nouveau test, en y signalant que nous aurons deux assertions.
$t = new lime_test(2, new lime_output_color());
La méthode comment() permet de placer un commentaire dans l’interface de test, facilitant ainsi le débugguage ensuite.

Puis vient le moment des tests à proprement parler.
$t->is(Post::my_test(true), true, 'valid() return the argument we give him');
Le test échouera si la valeur retournée par Post::my_test n’est pas égale à true.

De nombreuses méthodes sont disponibles en plus de is().

  • comment($msg) – Affiche un commentaire mais n’exécute aucun test.
  • ok($test, $msg) – Affiche le commentaire et vérifie que $test est vrai.
  • is($value1, $value2, $msg) – Affiche le commentaire et vérifie que $value1 est égal à $value2.
  • isnt($value1, $value2, $msg) – Affiche le commentaire et vérifie que $value1 n’est pas égal à $value2.
  • like($string, $regexp, $msg) – Affiche le commentaire et vérifie que $string respecte l’expression régulière $regexp<./li>
  • unlike($string, $regexp, $msg) – Affiche le commentaire et vérifie que $string ne respecte pas l’expression régulière $regexp.
  • cmp_ok($value1, $operator, $value2, $msg) – Compare les deux arguments avec l’opérateur.
  • isa_ok($value, $type, $msg) – Vérifie le type de l’argument donné.
  • isa_ok($object, $class, $msg) – Vérifie que l’objet est bien de la classe mentionnée.
  • can_ok($object, $method, $msg) – Vérifie la disponibilité de la méthode pour un objet ou une classe.
  • is_deeply($array, $array2, $msg) – Vérifie que deux tableaux ont les mêmes valeurs.
  • include_ok($file, $msg) – Vérifie qu’un fichier existe et qu’il est correctement inclu.
  • fail() – Echoue toujours. Utile pour tester des exceptions.
  • pass() – Passe toujours. Utile pour tester des exceptions.
  • skip($msg, $nb_tests) – Compte comme $nb_tests tests. Utile pour les tests conditionnels
  • todo() – Compte comme un test. Utile pour les tests restant à écrire.

Functional

Et les tests fonctionnels. Voici un exemple de test :
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new sfTestFunctional(new sfBrowser());

$browser->get('/')
  ->info('1 - The culture is appropriately fr')
  ->with('user')
  ->isCulture('fr')

  ->info('2 - The response is 200 and the content has a title')
  ->with('response')
  ->begin()
  ->isStatusCode(200)
  ->checkElement('body', '/<h1>/i')
  ->end();

Comme précédemment, nous commencons par implémenter le bootstrap.
Puis nous instancions un objet sfTestFunctional, qui fera office de navigateur et s’occupera de faire les appels http et de faire nos validations.

Ensuite nous faisons une requête get sur l’url /.
Et nous entrons dans la vue culture, pour vérifier que la langue définie pas défaut est bien le français. Voir internationalisation

Ensuite nous passons dans la vue response, nous vérifions que le code HTTP est bien 200 et que la page contient une balise h1.
Puis nous fermons cette requête.

Il serait bien évidemment possible de faire d’autres requêtes sans réinstancier le navigateur. Il suffit de refaire un $browser->get('/chemin');

Par ailleurs, comme précédemment, de multiples méthodes sont présentes :

  • get($url, $parameters) – Fait une requête get
  • post($url, $parameters) – Fait une requête post
  • call($url, $method, $paramaters) – Permet de faire un appel en précisant sa méthode (utilisé pour les requêtes PUT et DELETE).
  • back() – Retourne à la page précédente dans l’historique
  • forward() – Va à la page suivante dans l’historique
  • reload() – Recharge la page courante
  • click($name, $arguments, $options) – Clique sur un lien ou un bouton
  • select($name) – Sélectionne un bouton radio ou une case à cocher
  • deselect($name) – Désélectionne un bouton radio ou une case à cocher
  • restart() – Redémarre le navigateur (et la session)

Ainsi que d’autres méthodes pour configurer le comportement du navigateur

  • setHttpHeader($header, $value) – Définit une entête HTTP
  • setAuth($username, $password) – Définit l’identification http (basique)
  • setCookie($name, $value, $expire, $path, $domain, $secure, $httpOnly) – Définit un cookie
  • removeCookie($name) – Supprime un cookie
  • clearCookie() – Supprime tous les cookies
  • followRedirect() – Suis la redirection (et retourne une exception si le lien n’en est pas une)

Une fois que la requête est correctement paramétrée, il vous faut définir la vue que vous désirez avoir et exécuter celle-ci.
Il existe cinq vues. Mais nous n’en verrons que deux dans cet article.

request

Cette vue apporte des méthodes permettant de tester la requête.

  • isParameter($key, $value)> – Teste qu’un paramètre est présent et que sa valeur est correcte
  • isFormat($format) – Vérifie le format de la requête
  • isMethod($method) – Vérifie la méthode de la requête
  • hasCookie($key, $exists) – Vérifie que le cookie existe
  • isCookie($key, $value) – Vérifie la valeur du cookie

response

Cette méthode permet de vérifier le contenu renvoyé par la page.

  • checkElement($selector, $value, $options) – Vérifie qu’un sélecteur CSS respecte les critères donnés
  • isHeader($key, $value) – Vérifie la valeur d’une entête HTTP
  • isStatusCode($code) – Vérifie le code HTTP renvoyé
  • isRedirected() – Vérifie qu’il s’agit d’une redirection

Autres vues

Nous avons donc vu les deux vues les plus couramment utilisées. Cependant il en existe trois autres, que nous ne détaillerons pas ici :

Pour finir, comme vous pouvez le constater, tester ses applications est assez aisé avec Symfony. Alors vous n’avez plus aucune raison de ne pas le faire :)

Comme j’en ai déjà parlé récemment, Rails permets, depuis sa version 2.2, de gérer les sites multilingues.
Du coup comme j’avais envie de m’amuser, j’ai fait une mini application console qui permet de prendre un fichier yaml de langue, le traduit dans la langue de votre choix (en utilisant Google Translate) et écrit les nouvelles données dans un second fichier. Cela s’appelle Translator.

Pour l’utiliser, c’est très simple. En ayant ruby et rubygems d’installé, il vous manque encore deux gem :
gem install yaml googletranslate
Le premier permet de parser des documents YAML. Le second est une librairie permettant de faire des appels à l’API Google Translate.

Puis ouvrez une fenêtre console, rendez-vous dans le répertoire de translator et tapez :
ruby translator.rb /chemin/vers/le/fichier/originel.yml /chemin/vers/le/nouveau/fichier.yml langue
Exemple :
ruby translator.rb en.yml fr.yml fr
Traduira le document en.yml de la langue qui lui est définie vers le français en mettant le contenu dans fr.yml.

Depuis sa version 2.2, Ruby on Rails permet de gérer les sites multi langue et leurs divers contenus.
Nous allons voir, dans cet article, comment utiliser le module I18n dans votre application.

Tout d’abord, voyons les diverses méthodes de ce module dont vous aurez l’utilisation dans un cas basique.

  • available_locales. Retourne un tableau contenant toutes les langues disponibles pour l’application (qui ont au moins une chaine de traduction présente).
  • default_locale. Retourne ou définit la langue par défaut (uniquement pour le chargement courant. Ne change aucune configuration et ne mets rien en session).
  • locale. Retourne ou définit la langue en cours pour la page.
  • localize. Localise certains objets tels que les dates (en Français, retournera jj/mm/yy; en Anglais, mm/jj/yy).
  • translate. Retourne la chaine dans la langue appropriée. Si celle-ci n’existe pas, retourne la langue et le nom de l’élément transmis.

Traduire vos pages

Vous avez vu les méthodes translate et localize plus haut qui permettent de récupérer les valeurs appropriées à afficher sur votre page.
Ces deux méthodes ont des abbréviations, t et l.
Pour traduire une chaine, vous ferez donc :
<%= t :hello %>
Ou bien pour localize :
<%= l Time.now %>

Définir la langue actuelle

La méthode available_locales retourne un tableau de toutes les langues disponibles.
Il vous suffit donc de définir la langue que vous désirez, de la manière que vous désirez : en la récupérant dans le nom de domaine; en paramètre; en session; dans les préférences du navigateur. A vous de choisir la méthode que vous désirez.

Puis définissez votre locale :
I18n.locale = my_locale if available_locales.include? my_locale
Ainsi, si la langue existe, celle-ci sera définie. Sinon vous conservez la langue par défaut.

Traduire les messages flash

Les méthodes t et l fonctionne dans les vues. Mais également dans les contrôleurs. En conséquent, pour traduire vos messages flash, il vous suffit de faire :
flash[:notice] = t(:my_message)

Séparer ses fichiers de langue

Avoir toutes ses variables de langue dans un même fichiers peut rapidement rendre la chose illisible et inmaintenable.
Vous pouvez donc organiser vos fichiers en sous-répertoires :
- en.yml
- fr.yml
|- user
|– en.yml
|– fr.yml
|- faq
|– en.yml
|– fr.yml
|— category
|—- en.yml
|—- fr.yml

La seule condition pour que cela fonctionne est d’indiquer à Rails de parser également les sous-répertoires en mettant, dans config/environment.rb :
config.i18n.load_path += Dir[File.join(RAILS_ROOT, 'config', 'locales', '**', '*.{rb,yml}')]

Traduction des modèles

Vous pouvez traduire les noms des champs de vos bases de données.
en:
 activerecord:
  models:
   user: Bro
  attributes:
   user:
    login: "Who are you"

La méthode User.human_name retournera Bro. Et User.human_attribute_name(:login) retournera Who are you.

Je m’arrête ici. Mais je vous invite à lire la documentation officielle, qui reprends tous ces éléments plus en profondeur et explique également divers autres détails non mentionnés ici. La traduction des erreurs de validation des modèles par exemple.

Du coup après ça vous n’aurez plus aucune raison de ne pas concevoir vos applications rails en plusieurs langues.

Je viens de publier un article sur le blog d’O2Sources, sur le thème suivant : « Ecrire du code testable« .
En effet, vous pouvez avoir toute la bonne volonté du monde à vouloir écrire des tests dans votre code, si celui-ci n’est pas écrit de manière à vous permettre de créer des tests de manière aisée, vous n’arriverez à rien.

Cet article donne donc une liste de 10 conseils pour écrire du code testable.

  1. Ne pas mélanger le constructeur avec le code logique
  2. Demandez les choses. N’allez pas les chercher vous même
  3. Ne faites pas tout le travail dans le constructeur
  4. Evitez les singletons et variables globales
  5. Evitez les méthodes statiques
  6. Favoriser les compositions aux héritages
  7. Favoriser le polymorphisme aux conditions
  8. Mélanger des objets de valeur et des objets de service
  9. Ne mélangez pas tout
  10. Pensez deux fois, n’agissez qu’une

Je vous invite donc à lire cet article « Celui qui voulait rendre son code testable« .

Ruby est très développé et très bien document lorsqu’il s’agit de développer des applications web.
Mais parce qu’il n’y a pas que le web dans la vie (ah bon ???), il peut être utile, même dans le cas du développement web, de mettre en place des applications console.

Par console, j’entends sans aucune interface graphique. Celles-ci sont encore un autre sujet.

Du coup en faisant quelques recherches, j’ai découvert SimpleConsole, qui est justement un framework de développement d’applications console simples.
Et en plus il gère un (semblant) de MVC.

Votre application va donc se diviser en deux parties.
Supposons un fichier « myapp.rb », avec le code suivant :
#!/usr/bin/env ruby -w
require 'rubygems'
require 'simpleconsole'
require 'views/myapp'

class Controller < SimpleConsole::Controller
  params :string => {:n => :name, :w => :word}

  def default
    @name = params[:name]
    @word = params[:word]
  end
end

SimpleConsole::Application.run(ARGV, Controller, View)

Puis un fichier views/myapp.rb
class View < SimpleConsole::View
  def default
    puts "Your name is " + @name + "."
    puts "You wanted me to say the word " + @word + "."
  end
end

Si vous exécutez
ruby myapp.rb -name Damien -w Yay

La console vous retournera alors :

Your name is Damien.
You wanted me to say the word Yay.

Le code est tout d'abord interprété par le contrôleur pour ensuite être transmis à la vue, qui renvoie le contenu que vous désirez.
C'est du pseudo MVC et la chose serait largement améliorable.

Mais c'est un bon début pour une application console qui se doit d'être simple.
Et puis vu que l'application ne semble plus maintenue, il est possible que je la forke et que je fasse quelques commits dessus ;)

 
Fork me on GitHub