TDD Default and Optimistic Lock on Rails 1

Publié par Yannick Francois Mer 25 fév 2009 13:57:00 GMT

RubyOnRails utilise par défaut ActiveRecord comme ORM.

J’ai eu envie de faire quelques tests sur le fonctionnement des accès concurrents. Voici les resultats sur le comportement par défaut et lorsque l’on met en place une gestion optimiste de verrou (ou une gestion de verrou optimiste ;-)).

J’ai effectué mes tests avec un Ruby 1.8.6.p114, un MySQL-server-5.0.51ap1 et un ActiveRecord 2.2.2 le tout sur mon bon openbsd 4.4

On va commencer par mettre en place une table Users avec juste champ Name pour faire nos tests et ajouter un enregistrement avec un ID à 1, ça facilitera le test.

Comportement par défaut

Le test.

require 'test/unit'
require 'rubygems'
require 'active_record'

class DBAccess < Test::Unit::TestCase
  def setup
    # On va prendre la connection à la base de données ici. Ca me gène dans le cas d'un test unitaire, mais passons
    ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'optimistic', :username => 'yaf', :password => 'monpassword', :socket => '/var/run/mysql/mysql.sock')
  end

  def teardown
    # on remet l'enregistrement comme il était avant de venir
    user = User.find 1
    user.name = "Joe"
    joe.save
  end

  def test_default_db_priority
    joe = User.find 1
    bill = User.find 1
    bill.name = "billy"
    assert bill.save
    assert_equal "billy", User.find(1).name
  
    joe.name = "joe"
    joe.save
    assert_equal "joe", User.find(1).name
  end
end

class User < ActiveRecord::Base; end;

J’ai glissé le model User dans le même fichier, c’est plus simple.

Executons le test:

$ ruby test.rb
Loaded suite test
Started
.
Finished in 0.173901 seconds.

1 tests, 3 assertions, 0 failures, 0 errors

Conclusion
Par défaut, c’est le dernier qui enregistre une modification qui a raison. Cela peut poser des problèmes dans certains cas, c’est pourquoi on va maintenant aller voir comment mettre en place une gestion de verrou optimiste.

Verrou optimiste (Optimistic Lock)

ActiveRecord permet la mise en place d’un verrou optimiste. Pour cela il nous faut ajouter un champ spécifique de type entier qui servira à la gestion des accès.
Cette colonne doit s’appeler lock_version selon les conventions ActiveRecord, mais on peut utiliser un autre nom.

Ajoutons donc cette colonne et écrivons notre test:

Le test

require 'test/unit'
require 'rubygems'
require 'active_record'

class DBAccess < Test::Unit::TestCase
  def setup
    ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'optimistic', :username => 'yaf', :password => 'monpassword', :socket => '/var/run/mysql/mysql.sock')
  end

  def teardown
    user = User.find 1
    user.name = "Joe"
    joe.save
  end

  def test_optimistic_lock
    joe = User.find 1
    bill = User.find 1
    bill.name = "billy"

    assert_equal joe.lock_version, bill.lock_version
    # et oui, maintenant nos objet on un attribut lock_version

    assert bil.save
    assert_equal "billy", User.find(1).name
  
    joe.name = "joe"
    assert_raise(ActiveRecord::StaleObjectError) { joe.save }
    assert_equal "billy", User.find(1).name
  end
end

class User < ActiveRecord::Base; end;

Executons le test:

$ ruby test.rb
Loaded suite test
Started
.
Finished in 0.437534 seconds.

1 tests, 5 assertions, 0 failures, 0 errors

Conclusion

Maintenant, au moment d’enregistrer des modifications, ActiveRecord est capable de nous prevenir par le biais d’un ActiveRecord::StaleObjectError. On peut donc imaginer encapsuler le save dans un begin/rescue pour gérer la situation.

la methode reload permet de recharger l’objet à partir de la base de donnée, ça peut être pratique dans ce cas

Un certain jqr propose un stale_object_destroyer qui apparemment permet de facilité la gestion des tentative de mise à jour plus facilement

Il serait interessant de faire tourner ces tests sur les divers base de données suporté par ActiveRecord. Notamment avec sqlite3. J’essayerais de faire le test sur une base oracle. N’hésitez pas à me tenir au courant si vous executez ces tests sur d’autres bases ;-)

On verra les verrou en mode pessimiste dans un autre billet

Note to self

Publié par Yannick Francois Ven 02 mars 2007 22:51:00 GMT

Je n’oublierais plus d’installer mysql-dev avant d’essayer d’installer le binding ruby-mysql

Il faut que je me marque ça sur un marbre, accroché en pendentif !

Ca doit faire au moins 3 fois que je me fais avoir. Bon ce soir, j’ai juste mis 10 minutes avant de m’en rendre compte. Donc histoire de ne plus oublier, je note ici la procédure. Elle se déroule sur macosX mais elle s’adapte à tout à peut de chose prêt.

Pour l’installation de mysql:

pouype@pomme:pouype$ fink install mysql

pour la futur installation de ruby-mysql

pouype@pomme:pouype$ fink install mysql-dev

Et après on peut sereinement executé:

pouype@pomme:pouype$ gem install mysql 


RubyGem c’est bien, c’est pratique. Il faudrait que j’en parle…

Dernière minutes

Bon en fait j’avais pas vu, mais ça n’as pas marché, un problème pour trouver les librairies mysql on dirait. Bref, Gem c’est bien, mais on bonne installation de librairies à l’ancienne, avec un setup.rb, install.rb ça marche mieux ;-)

Mysql user privileges

Publié par Yannick Francois Sam 13 jan 2007 14:42:00 GMT

Désolé pour l’apparition du précédent billet, c’étais une erreur de manipulation :-p


Petite note pour l’ajout d’utilisateur avec Mysql.

Se connecter en root à Mysql

mysql -u root -p

Le -p c’est pour signaler qu’on vas saisir le mot de passe. J’espère que vous en avez mis un bon, root est un utilisateur ayant tout les droits sur toute vos bases Mysql…

Plusieur possibilités ensuite. La gestion des accès et des utilisateurs (ainsi que bien d’autres chose) sont faite par le biais d’une base système dans Mysql (dans oracle on a un petit peu ça aussi, je me demande si tout les moteur de base de donnée ne le font pas…). Donc soit on fait de la requête SQL basique sur les tables qui vont bien (dans notre cas mysql.user où mysql est le nom de la base accéssible par root uniquement et user la table de gestion des utilisateur mysql). Soit on utilise quelque raccourci (on vas faire les deux :p).

Pour ajouter un utilisateur “toto” pouvant se connecter à partir de la machine hebergant la base:

USE mysql;
CREATE USER toto@localhost;

N’oublié pas le ; pour finir vos commande :). USE permet de spécifier sur quelle base on souhaite travailler. Le localhost précise que cet utilisateur ne pourra se connecté qu’à partir de “localhost”.
Voilà un petit contrôle maintenant:

SELECT * FROM user;

Doit nous retourner la liste des utilisateurs, “toto” doit apparaitre avec toute les valeurs par défaut qui vont bien, mais sans mot de passe… La boulette ! Corrigeons ça:

UPDATE user SET password=PASSWORD("motdepasse")
WHERE user="toto";

La fonction PASSWORD permet de crypter le mot de passe dans la base de donnée.

Un autre petit select devrais nous confirmer que le mot de passe à bien été ajouté (bon normalement il est illisible :p).


Les accès maintenant. Ben oui, notre utilisateur toto n’accède à aucune base par défaut. Ajoutons lui l’accès (toujours avec l’utilisateur root):

GRANT ALL ON test.* TO 'toto'@'localhost';

Voilà, c’est basique, pas forcement très fin, mais ça donne un accès complet à l’utilisateur toto sur la base “test”.

Il faut ensuite valider toute ces modifications (surtout les privileges, la création d’utilisateur n’en à pas bessoin.

FLUSH PRIVILEGES;

Et voilà, on peut sortir et se connecté avec toto :).
Il existe beaucoup d’option pour réglé les droits d’accès très finement. Si vous souhaité justement mettre en place une meilleur gestion d’utilisateur, je vous envoie vers la doc officielle qui est très bien faites : http://dev.mysql.com/doc/

Depuis longtemps je souhaite utiliser PostgreSQL en lieu et place de Mysql, mais voilà, le manque de temps tout ça… Bon okay, j’ai aucune excuse, promis je le ferais dès que j’ai le temps. En attendant, l’avantage affiché clairement de mysql c’est la simplicité, et il faut avouer que ça marche bien.

edit:
Petite ajout de dernière minutes. On peut (et je dirais même que ça semble mieux) ajouter les privilèges comme ceci:

grant all privileges on database_name.* to toto@host identified by "monpasswordenclair";