TDD Default and Optimistic Lock on Rails 1
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 errorsConclusion
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 errorsConclusion
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
Utilisez le lien ci-dessous pour envoyer un trackback depuis votre site:
http://elsif.fr/trackbacks?article_id=1986
Pour l’avoir mis en place pour l’application Web d’un client, ça marche sous Oracle.
Pour l’avoir expliqué et fait faire en formation, ça marche sous SQLite3.
Mais peu importe la base de données, tout ça est géré par ActiveRecord…