Al's blog

Alexis Toulotte

Il est parfois utile d’afficher les dates et les heures selon la TimeZone de l’utilisateur dans nos applications Web.

Il y a la manière que l’on retrouve le plus (pénible à mon gout) qui consiste à la faire choisir à l’utlisateur via un énorme <select>, ceci qui ressemble à peu près à ça :

TimeZone select

De plus, il faut ensuite stocker cette TimeZone en base de données ou dans un cookie. Bref, pénible pour le développeur et pour l’utilisateur.

C’est pourquoi, le mieux serait d’obtenir la TimeZone du navigateur directement. Malheureusement, les navigateurs n’envoient pas de header HTTP avec la TimeZone configurée sur la machine (un peu comme pour les locales). La seule manière donc, est d’utiliser Javascript.

Dans le petit script ci-dessous, on va stocker dans un cookie l’UTC offset.

var TimeZone = (function () {
 
    function getOffset()
    {
        return -(new Date().getTimezoneOffset()) * 60;
    }
 
    function init()
    {
        var offset = getOffset();
        if (offset != Cookie.get('time_zone_offset'))
            Cookie.set('time_zone_offset', offset);
    }
 
    return {
        'init': init
    }
 
})();
 
document.observe('dom:loaded', TimeZone.init);

Note : les méthodes Cookie.get et Cookie.set sont issues d’un autre bout de Javascript. Demandez-moi si vous en avez besoin.

Maintenant coté serveur (Rails), on va changer la TimeZone en fonction de la valeur de ce cookie :

class ApplicationController < ActionController::Base
 
  before_filter :set_time_zone_from_cookies
 
  private
 
  def set_time_zone_from_cookies
    if cookies['time_zone_offset'].present?
      Time.zone = cookies['time_zone_offset'].to_i
    else
      Time.zone = Rails.configuration.time_zone
    end
  end
 
end

On se retrouve maintenant avec les dates et les heures qui s’affichent correctement partout dans le monde.

Suite à mon précédent billet concernant la création d’un jeu de données propre à une migration en utilisant les fixtures ainsi qu’aux nouveautés concernant les migrations dans rails 2.1, quelques ajustements ont du être effectués.

Les fichiers de migrations sont désormais préfixées par un timestamp (exemple : db/migrate/20080618211857_create_admin_user.rb) et non plus par un simple identifiant incrémental. Vos fichiers de fixtures doivent désormais être préfixés par ce même timestamp (exemple : db/fixtures/20080618211857_users.yml).

Voici donc le script lib/create_fixtures.rb mis à jour :

module MigrateFixtures
 
  FIXTURES_LOCATION = File.dirname(__FILE__) + '/../db/fixtures'
  MIGRATIONS_LOCATION = File.dirname(__FILE__) + '/../db/migrate'
 
  def self.extended(object)
    class << object
      alias_method :migrate_without_fixtures, :migrate unless method_defined?(:migrate_without_fixtures)
      alias_method :migrate, :migrate_with_fixtures
    end
  end
 
  def migrate_with_fixtures(direction)
    migrate_without_fixtures(direction)
    return if :down == direction
    migration = current_migration
    cnx = ActiveRecord::Base.connection
    files = fixture_files.find_all do |file|
      file =~ /\/#{migration}_/
    end
    files.each do |file|
      table_name = file.gsub(/.*\/\d+_(.*)\.yml/, '\1')
      say_with_time("Adding #{table_name} fixtures") do
        Fixtures.new(cnx, table_name, nil, file.gsub(/(.*)\.yml/, '\1')).insert_fixtures
      end
    end
  end
 
  private
 
    def current_migration
      version = ActiveRecord::Migrator.current_version
      file = migration_files.find do |f|
        version = '%03d' % version if version.to_s.size &lt; 3
        f =~ /\/#{version}_/
      end
      index = migration_files.index file
      file = index ? migration_files[index + 1] : migration_files.first
      file.gsub(/.*\/(\d+)_.*/, '\1')
    end
 
    def fixture_files
      @fixture_files ||= Dir["#{FIXTURES_LOCATION}/[0-9]*_*.yml"].sort
    end
 
    def migration_files
      @migration_files ||= Dir["#{MIGRATIONS_LOCATION}/[0-9]*_*.rb"].sort
    end
 
end
 
ActiveRecord::Migration.extend(MigrateFixtures)

Note : Cette version est compatible avec les anciennes version de rails.

27 avr 08

iSport

Voici une petite appli Web adaptée pour iPhone / iPod Touch : iSport.

Elle permet d’avoir les informations sportives ainsi que les résultats des matchs en direct (pour l’instant, que Fooball, Tennis, Basket US et Rugby).

La chose utilise Ruby, Rails, Hpricot et comme d’habitude : Prototype.

Quelques screenshots pour la peine :

iSport (1)

iSport (2)

Des retours et / ou commentaires sont les bienvenus !

15 avr 08

mount /dev/null

Ce matin un collègue a trouvé un gentil mot de notre administrateur système :

Format

29 fév 08

Diff SVN

En regardant le script de davux, permettant la colorisation d’un diff, je me suis dit qu’il devait y avoir une possibilité d’intégrer ça de manière simple à subversion.

Collez ce petit script dans un fichier (nous allons prendre ici /usr/local/bin/colorized_diff) et rendez le exécutable :

#! /bin/sh
 
plus='^[[0;32m'
minus='^[[0;31m'
diff "$@" | sed 's/^+.*$/'"$plus"'&^[[0m/; s/^-.*$/'"$minus"'&^[[0m/'

Ensuite, modifiez le fichier ~/.subversion/config pour y rajouter ou modifier la ligne suivante dans la section [helpers]:

diff-cmd = /usr/local/bin/colorized_diff

Vous pouvez omettre le chemin (/usr/local/bin) si il est dans votre PATH. Maintenant, un svn diff dans un shell vous affichera les lignes supprimées en rouge et les lignes ajoutées en vert. C’est pas plus mignon ?

Note : ^[ est le caractère d’échappement, vous pouvez le taper dans vim en faisant Ctrl-V puis Echap

10 juil 07

Fixtures on Rails

Il est assez fréquent d’avoir du contenu à pré-remplir dans une base de données lors du déploiement d’une application.

La plupart du temps on se fait un script SQL qui fait les insertions. En Rails, c’est mieux, on mets les insertions dans les scripts de migration et ça roule.

Petit hic, mettons que l’on a beaucoup de données et contenant des champs text, le script de migration va vite devenir illisible. Pour ça il y a les Fixtures. Seulement, ce n’est pas géré par les migrations (de base). Les fixtures sont représentés grâce à des fichiers YAML décrivant vos modèles.

Voilà un petit script à rajouter dans le répertoire /lib/ (nommez-le create_fixtures.rb) :

require 'active_record/fixtures'
 
module CreateFixtures
 
  FIXTURES_LOCATION = 'db/fixtures'
 
  def self.extended(object)
    class << object
      alias_method :migrate_without_fixtures, :migrate unless method_defined?(:migrate_without_fixtures)
      alias_method :migrate, :migrate_with_fixtures
    end
  end
 
  def migrate_with_fixtures(direction)
    migrate_without_fixtures(direction)
    return if :down == direction
    version = ActiveRecord::Migrator.current_version + 1
    cnx = ActiveRecord::Base.connection
    files = Dir["#{FIXTURES_LOCATION}/[0-9]*_*.yml"].each do |file|
      next unless file.gsub(/.*/([0-9]+)_.*.yml/, '1').to_i == version
      table_name = file.gsub(/.*/[0-9]+_(.*).yml/, '1')
      Fixtures.new(cnx, table_name, nil, file.gsub(/(.*).yml/, '1')).insert_fixtures
    end
  end
 
end
 
ActiveRecord::Migration.extend(CreateFixtures)

Ensuite, rajouter cette ligne dans le fichier config/environment.rb :

require File.dirname(__FILE__) + '/../lib/create_fixtures'

Voilà la chose est en place. Maintenant voyons l’utilisation.

Vous avez juste à créer un répertoire db/fixtures dans votre application et y mettre vos fichiersYAML. Nommez vos fichiers de la sorte :

<numéro_de_migration><nom_de_la_table>.yml

Rien de mieux qu’un petit exemple :

$ cat db/fixtures/002_users.yml
login: admin
hashed_password: d033e22ae348aeb5660fc2140aec35850c4da997
status: administrator

Ici, l’utilisateur administrateur sera directement ajouté à la fin de la deuxième migration. Vous pouvez bien-entendu mettre plusieurs fichiers de fixtures pour une même migration et créer des utilisateurs par exemple après la deuxième ou même la 42e migration.