Lorsque vous définissez, dans une application rails, des url spécifiques avec map.resources, celles-ci finissent de la forme suivante :

/post/1
Ou 1 est l’identifiant de l’uplet dans la base et blog est le nom de la ressource. C’est cool parce que ça permet d’avoir des url assez courtes simplement.
Mais d’un point de vue utilisateur, y’a mieux. Avoir une url du genre de
/post/titre-du-billet
Serait un peu mieux non ? C’est particulièrement simple :)

Map.resources utilise la méthode to_param des modèles. Méthode qui, par défaut, retourne la valeur de l’identifiant.
Si vous la modifiez en placant :

def to_param
    title
end

Ce n’est plus l’identifiant de votre uplet qui sera retourné mais la valeur du champ titre.
A vous donc ensuite d’avoir un titre correct.

Et lorsque vous appellerez la méthode vous permettant d’avoir l’url vers votre billet :

post_url @post

Ce n’est plus /post/1 qui sera retourné mais /post/titre-de-votre-billet.

Bien évidemment la valeur de title doit être correctement formatée pour rendre une url (suppression des accents et espaces etc). Rails ne fait pas cela automatiquement.
Mais ce genre de méthode ne fait pas partie du sujet de cet article. Cela sera pour un autre :)

Comme déjà dit dans mon précédent billet, j’ai migré sous Ubuntu en début de semaine.
Tout en continuant à tester Townce, j’ai voulu y passer un coup de rcov.

Et la, Ô infamie.

/usr/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `[]‘: no implicit conversion from nil to integer (TypeError)
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `wrap’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `wrap’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:90:in `write_text’
from /usr/lib/ruby/1.8/rexml/formatters/default.rb:50:in `write’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:75:in `write_element’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `each’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `write_element’
from /usr/lib/ruby/1.8/rexml/formatters/default.rb:31:in `write’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:75:in `write_element’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `each’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `write_element’
from /usr/lib/ruby/1.8/rexml/formatters/default.rb:31:in `write’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:117:in `write_document’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:111:in `each’
from /usr/lib/ruby/1.8/rexml/formatters/pretty.rb:111:in `write_document’
from /usr/lib/ruby/1.8/rexml/formatters/default.rb:28:in `write’
from /usr/lib/ruby/1.8/rexml/document.rb:197:in `write’
from (eval):93:in `pretty’
from /usr/lib/ruby/1.8/rcov/report.rb:1003:in `create_file’
from /usr/lib/ruby/1.8/rcov/report.rb:708:in `execute’
from /usr/lib/ruby/1.8/rcov/report.rb:125:in `each’
from /usr/lib/ruby/1.8/rcov/report.rb:125:in `each_file_pair_sorted’
from /usr/lib/ruby/1.8/rcov/report.rb:707:in `execute’
from /usr/lib/ruby/1.8/rcov.rb:640:in `dump_coverage_info’
from /usr/lib/ruby/1.8/rcov.rb:640:in `each’
from /usr/lib/ruby/1.8/rcov.rb:640:in `dump_coverage_info’
from /usr/bin/rcov:421
from /var/lib/gems/1.8/gems/rspec-1.2.7/lib/spec/runner.rb:45

Rcov il a tout cassé ;)

En cherchant un peu, il s’avère qu’il s’agit d’un bug dans la librairie ReXML. Il a été corrigé en septembre dernier.
Mais la version apt de Ruby est plus ancienne que cela. Du coup le bug n’a pas été répercuté.

Qu’à cela ne tienne, allons le corriger nous même !
Ouvrez le fichier qui bugue.

sudo nano /usr/lib/ruby/1.8/rexml/formatters/pretty.rb

Nous sommes obligés de le faire en sudo. Vous n’avez, en toute logique, pas le droit de modifier ce fichier.

Descendez tout en bas du fichier. Vous verrez la méthode suivante :
def wrap(string, width)
  # Recursively wrap string at width.
  return string if string.length <= width
  place = string.rindex(' ', width) # Position in string with last ' ' before cutoff
  return string[0,place] + "\n" + wrap(string[place+1..-1], width)
end

L'erreur que nous avons plus haut vient du fait que la variable "place" est égale à nil. Il est donc impossible de l'utiliser comme clé dans le tableau "string".
Il suffit d'ajouter un return avant celui qu'il y a déjà, indiquant de retourner "string" si "place" est égal à nil.
Je n'invente rien. Il s'agit du commit fait dans Ruby en septembre dernier.

Modifiez donc votre méthode en y ajoutant la ligne suivante :
return string if place.nil?

Votre méthode doit maintenant ressembler à cela :
def wrap(string, width)
  # Recursively wrap string at width.
  return string if string.length <= width
  place = string.rindex(' ', width) # Position in string with last ' ' before cutoff
  return string if place.nil?
  return string[0,place] + "\n" + wrap(string[place+1..-1], width)
end

De mon côté, suite à cela ça fonctionne. Si vous avez toujours une erreur, je vous invite à la signaler ici. Mais c'est probablement un autre problème ;)

When you’re looking to create charts in Ruby, you might spend quite some time searching a cool tool.
I’ve been, for example, falling on gruff this morning.

But the graphs created with that kind of tools are always dependant of some libraries, and many times, they don’t look very nice.
So I’ve been working on something new, using something not new :mrgreen:
You’ve probably already heard of Google Charts, which allows you to create some cute charts.

Now you can use it with Ruby and Rails with my new plugin, Ruby on GChart.
You provide your datas, the type of graph you want and it gives you the URL to see the graph :)
You can even save this graph to a file and use it locally.

Here’s an example of what you can do :

GoogleChart::Chart.new({
    :type => :line,
    :height => 200,
    :width => 500,
    :encoding => :simple,
    :datas => [70, 25, 50, 35, 150],
    :labels => ['First Label', 'Second Label']
}).to_url

It supports simple and text encoding (with the parameters :simple and :text). No extended encoding for now.

However it supports muliple graphs in multi levels array.
Example :

:datas => [[5, 10, 15], [10, 15, 5]]

You can put as mush graphs as you wish of course.

So what do you think of it ?

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 :)

I’m a bit obsessed by my applications statistics.
So on many of them, I build tools allowing me to generate graphics with several evolution indicators of theirs datas over time.
On RefStats for example, this graphic shows the evolution of the number of positions; the number of users; of websites and the average execution time for the crawler.

Moreover, you probably already know the rails commande rake stats which gives some statistics datas on your application code.
Number of code lines, number of test lines, …
The class that builds it is located in the railties.
Unfortunately this class isn’t abstract at all. It shows directly the datas to the console. Not very cool to enter them to your database.

So I’ve taken this class to update it and allow you to get those datas in an abstract way and use them in an application :)
So let’s me introduct you to Rails Code Statistics (yeah my projects names are always very original)

The principle is pretty simple.
You instantiate the class :

stats = CodeSearch.new

Then you can the datas as an hash :

p stats.to_h

As a json element :

p stats.to_json

Only the total statistics

p stats.total

Only the detailed statistics

p stats.statistics

And then do whatever you want with them :)
For me for example, I have a rake task which is executed automatically once a day and puts the number of code and test lines to my database.
Then I only have to manipulate this database to build a graph.

Note : the version above will only work if you’ve made your tests with RSpec.
If you use Shoulda, I let you do it yourself :mrgreen:
And if you use Test::Unit, I invite you to try RSpec. You will not want to do anything else after :mrgreen:

Je suis un peu genre obsédé par les statistiques de mes applications.
Du coup beaucoup d’entre elles ont des outils me permettant de générer des graphiques avec divers indicateurs d’évolution des données de celles-ci en fonction du temps.
Sur RefStats par exemple, ce graphique m’affiche l’évolution du nombre de positions; du nombre d’utilisateurs; de sites et le temps moyen d’exécution du robot pour chaque mot clé.

Par ailleurs vous connaissez probablement déjà la commande Rails rake stats qui fournit des données statistiques sur le code de votre application. Nombre de lignes de code; nombre de lignes de test; ratio, …
La classe calculant ceci est située dans les railties.
Malheureusement cette classe n’est pas vraiment abstraite. Elle affiche directement des données en console, c’est pas super cool pour les entrer en base de données.

Du coup j’ai repris cette classe pour la modifier et permettre de récupérer ces données de manière abstraite et ainsi pouvoir les utiliser dans mon application :)
Je vous présente donc Rails Code Statistics (oui j’ai toujours des super idées de noms de projets).

Le principe est archi simple.
Vous instanciez la classe :
stats = CodeSearch.new

Puis vous pouvez récupérer les données comme un hash :
p(stats.to_h)
Comme un élément json
p(stats.to_json

Uniquement les statistiques totales
p(stats.total)
Uniquement les statistiques détaillées
p(stats.statistics)

Et après en faire ce que vous voulez :)
Dans mon cas par exemple, j’ai une tache rake qui est exécutée automatiquement une fois par jour et qui me place le nombre de lignes de code et de tests.
Ainsi je n’ai plus qu’à traiter ces données pour faire un graphique d’évolution de mon application.

Note : la version ci-dessus ne fonctionnera que si vous avez vos tests faits avec RSpec.
Si vous utilisez Shoulda, je vous laisse vous débrouiller :mrgreen:
Et si vous utilisez Test::Unit, je vous invite à tester RSpec. Vous voudrez plus faire que de ça après :mrgreen:

Vous le savez probablement déjà, il est possible, avec Active Record, de faire vos conditions proprement de deux manières différentes :
Model.find(:all, :conditions => ['champ = ?', valeur])
Model.find(:all, :conditions => {:champ => valeur})

Le second est plus propre. Cependant à première vue, il n’est pas possible d’y faire des conditions autres que le égal (=).
Du coup dès que votre condition devient un chouilla complexe, il deviendrait nécessaire d’utiliser les crochets. C’est dommage.

Heureusement, il des conditions autres que le = sont gerées avec les hashes.
Petit exemple de requête
Model.find(:all, :conditions=>{
:title => "Title",          # title='Title'
:published_at => (Date.now-30 .. Date.now),  # published_at BETWEEN xxx AND xxx
:rating => [ 4, 5, 6 ],          # rating IN ( 4, 5, 6 )
:content => /(a|b|c)/          # REGEXP '(a|b|c)'
)

Cela permet déjà de faire quelques requêtes supplémentaires. Cependant dès que l’on va vouloir faire des choses comme des LIKE, des !=, des >= ou des <=, nous allons, de toute façon, devoir passer par des tableaux.

Vu que je me veux de toucher un peu à tous et que je balance sur un peu tous les frameworks lorsque quelque chose ne me plait pas, autant également le faire sur Rails …
Tout en essayant, pour une fois, de faire quelque chose de vraiment structuré et pas juste trois paragraphes ou je raconte ce qui me passe par la tête sur le sujet.

Qu’est-ce qu’une fixture ?

Lorsque vous créer des tests unitaires et fonctionnels, il est primordial d’avoir des données en base, pour effectuer vos tests.
Il est assez lourd de créer manuellement ses entrées pour chaque test.
Du coup rails propose des fixtures. Dans un fichier yaml, vous placez toutes vos données.
Et elles sont entrées automatiquement en base (sans validation).

C’est sympa ça permet de gagner pas mal de lignes de code dans vos tests.

Mais pourquoi c’est pas bien ?

Chaque fichier de fixture correspond à une base de données.
Si vous créer un fichier users.yml, qui contient des utilisateurs, celui-ci ne pourra remplir des données que dans la base users du fait du nom du fichier.

Du coup lorsque votre application commence à grossir, cela devient assez rapidement le bordel. Vous avez beaucoup de données. Celles-ci deviennent illisibles.
Vous ne savez jamais si telle ou telle donnée est réellement utilisée ou non.
Si en prime vous avez rédigé certains tests de manière absolue et non pas relative, il suffit que vous ajoutiez un utilisateur pour que 15 tests échouent alors que votre code est toujours valide.

A moins de s’imposer une rigueur particulièrement extrême, on s’oppose donc à des grosses prises de tête et une hausse de la consommation d’aspirine lorsque l’application va grossir.

Alors quelle solution ?

La solution est simple : n’utilisez pas les fixtures :)
Cela ne signifie pas ne rien utiliser du tout bien évidemment ! Parce que … Haaaave you met Factory Girl ?
L’idée de ce plugin est assez simple. Vous définissez plusieurs factories, qui vont être l’équivalent d’une instance d’un objet.

Mais par la suite, c’est vous qui définissez quel factory vous souhaitez utiliser et quand. Vous pouvez donc créer de nouvelles factories sans avoir de problèmes de tests qui échouent pour des raisons idiotes.
Et puis ce qui est encore mieux, c’est que Factory Girl ne fonctionne pas que avec ActiveRecord. Les factories que vous créer sont des instances d’un objet, sans distinction.
Vous pouvez donc créer une factory pour un model qui ne dépends pas de ActiveRecord. Sympa pour les mocks :)

Et vous, vous utilisez quoi pour initialiser des données de test ?

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.

Il y a quelques jours de cela, Google a annoncé l’ouverture de son API pour Analytics.
Et du coup j’y ai jeté un coup d’oeil. Et plus particulièrement avec rails en utilisant le gem gdata.

Tout d’abord sachez que la chose a été faite un peu rapidement. Je ne l’ai pas pluginisée. J’ai uniquement fait un modèle permettant de faire les appels nécessaires; la chose est très peu testée et est surtout à utiliser pour découvrir cette API.

Voici donc mon modèle.
Vous devez l’initialiser avec votre login et mot de passe :
Analytic.new 'you@example.com', 'your_password'

Puis deux méthodes y sont disponibles :
find_sites, qui retourne la liste de tous les profils associés à votre compte.
find_datas, qui retourne les données demandées pour tous les sites et pour la tranche de date définie.
Par défaut, vous aurez le nombre de visiteurs et le nombre de pages vues pour la veille.

Du coup on peut également l’afficher :
Notre contrôleur, pour récupérer les données.
La vue pour les afficher.
Et le code avec colorisation syntaxique.

Encore une fois, ceci est un test fait assez rapidement. Mais qui fonctionne.
Si vous cherchez à utiliser la chose dans un contexte moins scolaire, je vous invite à jeter un oeil sur gattica, un gem créé spécifiquement pour récupérer les données de cette API.

L’utilisation en est assez simple. Et vous pouvez même l’utiliser en dehors de rails (mais uniquement en ruby, cela va de soi).
Commencez par l’installer :
gem install cannikin-gattica -s http://gems.github.com

Puis incluez le dans votre application
require 'gattica'
Vous devez avoir fait un require de rubygems avant (rails le fait automatiquement).

Pour récupérer la liste de tous vos profils :
gs = Gattica.new 'you@example.com', 'password'
accounts = gs.accounts

Et pour récupérer les visiteurs et pages vuex de la veille :
results = gs.get(
:start_date => 1.day.ago.to_date.to_s(:db),
:end_date => 1.day.ago.to_date.to_s(:db),
:metrics => ['pageviews', 'visitors']
)

Sympa non ? :)

PS : Ted a 31 ans aujourd’hui.
PPS : et moi 23.

 
Fork me on GitHub