On se tutoie ?

Aller au contenu | Aller au menu | Aller à la recherche

lundi, septembre 6 2010

undefined method `debug' for nil:NilClass avec Sinatra et ActiveRecord

Si tu rencontres une erreur de ce type avec Sinatra et ActiveRecord il y a de fortes chances que ce soit le logger qui soit en cause.

Pour y palier tu peux définir un logger dans ton rackup:

log = File.new("sinatra.log", "w")
ActiveRecord::Base.logger = Logger.new(log)

mercredi, décembre 31 2008

Publication de la gem wrtranslate

wrtranslate est une gem permettant de traduire des mots ou expression en utilisant wordreference.com. La gem s'appelle wrtranslate à cause d'un conflit de nom (translate existe déjà sous forme de gem) mais le projet et l'éxécutable s'appellent translate.

Le projet et la gem sont hébergés chez rubyforge, ainsi que la rdoc. Le code source est lui chez github.

Pour l'installer, il existe plusieurs méthodes. Soit via rubygems (recommandé):

gem install wrtranslate

soit en récupérant le code source sur github:

$ git clone git://github.com/fuse/translate.git
# ruby install.rb

Voici en vrac les améliorations de cette version:

  • Plus modulaire
  • Peut être utilisé à l'intérieur d'un autre programme
  • Plus de langues supportées
  • Peut être installé directement grâce à rubygems
  • Peut utiliser un fichier de configuration placé dans ~/translate.yml
  • Possibilité de spécifier la largeur de shell utilisée
  • Meilleur utilisation de l'espace (2 colonnes)
  • Ajouts de tests
  • Commande translate directement utilisable après installation

Et quelques corrections:

  • Quitte proprement quand ^C est utilisé
  • Le séparateur est positionné au bon endroit

Il y a encore beaucoup de chemin à faire pour que toutes les traductions fonctionnent du fait que word reference n'utilise pas de conventions précises pour présenter ses résultats.

L'affichage est différent selon les mots mais aussi selon les langues ; la priorité va donc à l'amélioration du parser.

Il y a plusieurs moyens de contribuer pour ceux que ça intéresse:

  • Envoyer des patchs
  • Écrire des fichiers de tests
  • M'envoyer des mots qui sont traduits sur le site et qui renvoie un «No results found» en utilisant translate.

samedi, décembre 13 2008

Différence entre les accolades {} et le do end en ruby

J'ai souvent lu à droite et à gauche que le choix entre les accolades et le do end était purement estéthique en ruby et que globalement un code d'une ligne se mettait entre accolades, quand un code de plusieurs se mettait entre do end.

Et bien, c'est faux, il y a une différence.
Prenons l'exemple d'un accumulateur qui réalise la somme des chiffres de 1 à 10 :

s = (1..10).inject do |sum, n|
	sum + n
end
puts s
=> 55

peut s'écrire de la même façon que :

s = (1..10).inject { |sum, n|
	sum + n
}
puts s
=> 55

Ce qui change c'est le moment d'évaluation du block ; il est possible d'écrire :

puts (1..10).inject { |sum, n|
	sum + n
}
=> 55

mais pas :

puts (1..10).inject do |sum, n|
	sum + n
end
=> `inject': no block given (LocalJumpError)

mardi, novembre 11 2008

Installer ruby-qt sur Mac OS X

Voici un petit howto pour compiler Ruby-qt sur Mac OS X Leopard.

Installer Qt

Commence par télécharger le dmg de Qt et installe le.

Installer Ruby-Qt

Télécharge ensuite la dernière version de qt4-qtruby sur rubyforge (1.4.10 à ce jour) et extrait le tarball. Une fois réalisé on se place dans le répertoire nouvellement créé et on lance un petit coup de cmake :

$ cd qt4-qtruby-1.4.10
$ cmake .

S'il te manque des outils comme cmake, des headers ou des dépendances, tu peux tout résoudre en installant les packages qui vont bien via fink ou port.

Logiquement on devrait maintenant simplement lancer un make.
Le souci c'est qu'on aura cette erreur à la compilation :

Undefined symbols:
  "QWebHistoryItem::~QWebHistoryItem()", referenced from:
      QList<QWebHistoryItem>::node_destruct(QList<QWebHistoryItem>::Node*, QList<QWebHistoryItem>::Node*)in handlers.o

Le problème vient du QWebHistoryItem mais je ne sais pas ce qui cloche exactement.
On va donc mettre en commentaire deux lignes qui y font référence dans le fichier ruby/qtruby/src/handlers.cpp :

Ligne 2488 : // DEF_VALUELIST_MARSHALLER( QWebHistoryItemList, QList<QWebHistoryItem>, QWebHistoryItem ) Ligne 2661 : // { "QList<QWebHistoryItem>", marshall_QWebHistoryItemList },

et lancer respectivement un :

$ make
$ make check
# make install

Logiquement tout devrait bien se passer, on peut le vérifier avec irb :

$ irb
>> require 'Qt'
=> true
>> Qt.version
=> "4.4.3"

Et c'est parti pour réaliser des applications cross-platform avec notre langage préféré \o/

wizard-qt-ruby

vendredi, octobre 24 2008

Ruby et la notion de finalizer

En ruby il n'existe pas à proprement parler de destructeur.
Toutefois il est possible de mettre en place des finalizer qui agissent comme des callbacks.

Ex :

#!/usr/bin/env ruby
 
class Foo
  def initialize
    puts "Object #{object_id} borned."
    ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
  end
 
  def self.finalize(id)
    puts "Object #{id} died."
  end
end
 
puts "start"
f = Foo.new
f = nil
puts "end"

La notion de finalizer peut être particulièrement pratique (dans le cas d'un debug par exemple) mais elle est à manier avec précaution.

Pourquoi finalize est une méthode de classe ?
Parce que la méthode est appelée une fois l'élément déréférencé ; la méthode doit donc être une méthode de classe.

A l'éxécution on se rend compte que le finalizer est appelé après le end, même en mettant l'objet à nil.
Cela est dû au garbage collector de ruby, qui est de type mark-and-sweep. Ce type de garbage ne libère pas instantanément les objets déréférencés.
Ceux ci sont accumulés, et une fois en grand nombre le programme se suspend pour libérer ces objets.

lundi, septembre 22 2008

Utiliser uniq avec une collection d'objets ruby

Récemment j'ai eu besoin d'utiliser uniq sur une collection d'objet ruby. En l'occurrence il s'agissait de modèles ActiveRecord.

L'idée était donc de filtrer ces objets en se basant sur l'un des attributs pour éviter les doublons.
En premier lieu je pensais que uniq pourrait prendre un bloc en paramètre indiquant comment comparer les éléments deux à deux, du genre : books.uniq { |b1, b2| b1.name == b2.name }

Pour le moment uniq ne sait pas faire ça (ou j'ai raté quelquechose).

Egalité et identité des objets ruby

uniq utilise eql? pour comparer les éléments deux à deux.
Pour qu'uniq fonctionne sur une collection d'objets, il nous faut donc surcharger eql? et hash qui vont de pair.

Pourquoi pas == ?
Contrairement à ==, .eql? compare aussi les types. Par exemple 42.0 == 42 mais ! 42.eql?(42.0)

A quoi sert hash ?

hash permet de générer un identifiant pour un objet non pas pour rendre l'objet unique mais pour permettre les comparaisons.
eql? et hash sont intimement liés.

Et equal?
equal permet de comparer l'identité des objets. Deux objets peuvent être strictement identiques en terme de valeur sans pour autant être les mêmes.

Ex :

a = "42"
b = "42"

a == b, a.eql?(b), a.hash == b.hash mais ! a.equal?(b) car a.object_id != b.object_id

La pratique : implémenter uniq sur une collection d'objets ruby

#!/usr/bin/env ruby -w
 
class Book
  attr_accessor :name
 
  def initialize(attributes)
    attributes.each do |k, v|
      send("#{k}=", v)
    end
  end
 
  def eql?(o)
    hash.eql?(o.hash)
  end
 
  def hash
    name.downcase.hash
  end  
end
 
books = [
  Book.new({ :name => "Programming Ruby" }),
  Book.new({ :name => "Harry Potter" }),
  Book.new({ :name => "Ruby on rails" }),
  Book.new({ :name => "programming ruby" })
]

Avec ce code :

books.length == 4
books.uniq.length == 3

mercredi, septembre 17 2008

Accèder à une base MS SQL en ruby sous linux, avec et sans ActiveRecord

Pré-requis

Cette procédure a été testé sous une debian lenny avec un kernel 2.6.18 (et sous OSX Leopard 10.5.4).
L'objectif est donc d'accéder à une base de données Microsoft SQL Server depuis un script ruby sous GNU / Linux.

Installation des packages

L'idée est de communiquer avec la base MS SQL par le biais des librairies FreeTDS et de l'API ODBC.

On commence par installer les packages nécessaires :

aptitude install unixodbc
aptitude install unixodbc-dev
aptitude install sqsh

Il est possible d'installer freetds par aptitude (install freetds-dev qui installe common) mais dans mon cas il manquait le fichier libtdsodbc.so.
Je l'ai donc installé depuis les sources :

wget ftp://ftp.ibiblio.org/pub/Linux/ALPHA/freetds/stable/freetds-stable.tgz
tar xzvf freetds-stable.tgz
cd freetds-0.82
./configure && make && sudo make install

Il faut maintenant définir quelques variables d'environnement définisant les emplacements des fichiers de configuration.
Soit dans le /etc/profile pour que tous les utilisateurs y ait accès, soit dans le .bashrc uniquement pour l'utilisateur courant :

export ODBCINI=/etc/odbc.ini
export ODBCSYSINI=/etc
export FREETDSCONF=/etc/freetds/freetds.conf

Rechargement de la configuration :

$ source /etc/profile

Configuration de la datasource

Dans le /etc/freetds/freetds.conf :

[MY_DSN]
	host = IP du serveur
	port = 1433
	tds version = 7.0

MyDSN représente le nom de la datasource, on peut y mettre le nom souhaité. On teste la connexion au datasource :

$ sqsh -S MY_DSN -U user -P password

Si tout se passe tu atteris sur un prompt de type :

1>

Il nous faut maintenant indiquer à ODBC d'utiliser FreeTDS :

$ vim /etc/odbc.ini
 
[MY_DSN]
Driver          = FreeTDS
Description     = ODBC connection via FreeTDS
Trace           = No
Servername      = MY_DSN
Database        = YOUR_ACTUAL_DB_NAME
$ vim /etc/odbcinst.ini
 
[FreeTDS]
Description     = TDS driver (Sybase/MS SQL)
Driver          = /usr/local/lib/libtdsodbc.so
FileUsage       = 1

On peut maintenant tester l'accès au datasource par le biais de isql, qui permet l'envoi de requêtes SQL sur le serveur :

$ isql MY_DSN user password
SQL> select top 10 * from users (pas de limit)

Note : La close top agit de la même façon que la close limit pour une base de données MySQL.

Accéder aux données MS SQL depuis ruby, sans ActiveRecord

Nous allons utiliser l'interface d'accès aux données DBI en utilisant le driver ODBC.

Il existe maintenant une gem pour ruby dbi :

# gem install dbi

Faisons un premier essai depuis un shell ruby :

$ irb

require 'rubygems'
require 'dbi'

DBI.connect('dbi:odbc:MY_DSN', login, password)
=> Unable to load driver 'odbc'

DBI ne parvient pas à charger le driver odbc. Actuellement odbc est installé sur notre machine mais il n'y a pas d'extension pour dialoguer avec.

On installe donc la librairie ODBC pour ruby :

$ wget http://www.ch-werner.de/rubyodbc/ruby-odbc-0.9995.tar.gz
$ tar zvxf ruby-odbc-0.9995.tar.gz
$ cd ruby-odbc-0.9995/
$ ruby extconf.rb
$ make
# make install

On installe le driver ODBC pour ruby dbi qui utilise odbc.so installé ci dessus :

# gem install dbd-odbc

Désormais le code testé ci dessus fonctionne.
Note : Pas besoin de require 'odbc', dbi inclus automagiquement les drivers présents.

Il est maintenant possible de faire des requêtes manuelles sans passer par AR.
Des exemples sont disponibles sur la doc officielle de ruby-dbi.

Accéder aux données MS SQL depuis ruby, avec ActiveRecord

Il faut utiliser l'adapter odbc, qui n'est plus inclus de base avec Rails :

# gem install activerecord-odbc-adapter

Une fois effectué, la syntaxe pour établir une connection est la suivante :

ActiveRecord::Base.establish_connection({ :adapter => 'odbc', :dsn => 'MY_DSN', :username => 'user', :password => 'password'})

Note : Ce tutoriel fonctionne sous OSX. Sous Leopard, les fichiers de conf odbc (odbc.ini, odbcinst.ini) doivent se situer dans/Library/ODBC.
Les packages à installer peuvent s'installer avec les port ou être directement compilés.

jeudi, mars 13 2008

Plugin highlight et pv pour weechat

Le besoin

Comme shingara, j'utilise weechat pour IRC et il nous manquait un plugin pour être notifié sur notre jabber à la réception d'un PV ou lors d'un highlight sur un channel.
Le but est de recevoir un message sur sa messagerie instantanée avec le nom de l'expéditeur et le contenu du message lorsque quelqu'un nous interpelle sur IRC.
Je me suis donc motivé pour écrire ce plugin in en me basant sur ses travaux en amont.

Qu'est ce qui change ?

Je suis donc parti de la révision 204 de son script (euh en fait y a d'autres choses dans le dépôt hein, vas faire un tour !) , basée sur celui de davux (c'est beau le libre hein ?), qui apparemment avait tendance à faire freezer weechat.

Au lieu d'utiliser xmpp4r j'utilise xmpp4r-simple qui est une lib plus légère et largement suffisante pour nos petits besoins.
Je pense avoir cerné ce qui potentiellement faisait planter weechat.
En effet lorsqu'on tente d'envoyer un message via XMPP, et que l'on ferme la connexion instantanément après, le message est non seulement perdu, et n'arrive donc jamais, mais a (semble t-il, je n'ai pas de certitudes) tendance à geler weechat.

Le script attend donc maintenant 3 secondes avant de fermer la connexion. D'une manière générale j'ai essayé d'améliorer la lisibilité et les perfs du script.
Au lieu d'instancier systématiquement un objet pour délivrer un message je passe par un singleton qui délivrera tous les messages pendant la durée de vie de weechat.

En terme de configuration il ne faut plus que le strict minimum : un couple jid / password qui désigne le compter jabber à utiliser pour envoyer les messages et un destinataire.
Il est possible de spécifier le port de connexion s'il est particulier, autrement le script utilise le port 5222.

Attention : Je n'ai pas testé mais il me semble logique qu'il ne faut pas utiliser le jid comme recipient, autrement le script risque de s'envoyer des messages à lui même.
En effet lorsque l'on utilise jabber c'est le dernier endroit depuis lequel on se connecte qui prend la main.

Au cas où ton compte destinataire ne serait pas connecté, les messages seront mis en file et tu les recevras à ta prochaine connexion.

Comment ça marche ?

L'usage est vraiment simple : il suffit de copier ce plugin dans ton .weechat/ruby/autoload et le plugin sera chargé au lancement de weechat. Ensuite tu édites ton fichier .weechat/plugins.rc et tu colles les informations te correspondant :

ruby.highlight_jabber_notify.jid = "jid@provider.tld"
ruby.highlight_jabber_notify.password = "toto42" # c'est pas ça ?
ruby.highlight_jabber_notify.port = "5222" # optionnel
ruby.highlight_jabber_notify.recipient = "recipient@provider.tld"

C'est tout. Le script est volontairement minimaliste mais on peut imaginer plus de configuration et de code pour n'être notifié que sur certains channels etc ...
J'ai aussi proposé ce script dans la section officielle du projet weechat (il est en attente d'approbation a été approuvé \o/).