Forum: Rails France validates_uniqueness_of et la duplicatio n de données

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
A2f664b4d5cbe5db3390e4be338c5ea8?d=identicon&s=25 ALAHYANE Rachid (Guest)
on 2009-05-06 20:02
(Received via mailing list)
Bonjour,

Je suis entrain de développer une application dans laquelle j'ai
besoin, à un moment donné, d'insérer plusieurs entrées dans une table:

--------------------------------------------------
property.rb
--------------------------------------------------
class Property < ActiveRecord::Base
   belongs_to :version
   validates_presence_of :name, :value
   validates_uniqueness_of :name, :scope => :version_id
end
--------------------------------------------------
la table associée à ce model contient les colonnes : id, name, value
et version_id

--------------------------------------------------
version.rb
--------------------------------------------------
class Version < ActiveRecord::Base
   has_many     :properties  ,:dependent => :destroy #e.g ON CASCADE
delete
   accepts_nested_attributes_for  :properties,:allow_destroy => true
end
--------------------------------------------------
la table associée contient les colonnes : id et numero

Le problème que j'ai est le suivant : lorsque j'insère via un
formulaire associé à la version 1 par exemple

prop1 de valeur p1
et
prop1 de valeur p2

les deux entrées sont insérées dans la table properties, ici, je viole
la contrainte validates_uniqueness_of, mais je ne reçois aucune
erreur, et les entrées sont insérées :( Est-ce validates_uniqueness_of
qui ne fonctionne pas bien ? ou bien j'ai autres chose à rajouter ?

Si ma question n'est pas claire je peux la détailler encore.

Merci d'avance

Meilleures salutations / Best Regards

Rachid ALAHYANE
047a4fc673336a70a6b58338bc6d677d?d=identicon&s=25 Michel Belleville (Guest)
on 2009-05-06 20:30
(Received via mailing list)
Ce n'est pas super clair mais si je pige bien, tu fais deux nouvelles
instances de Property avec le même name et pas la même value, tu les lie
à
la même instance de Version, tu les sauves et ça passe malgré la
contrainte
d'unicité. Je veux bien que tu montres le code où tu fais cette manip en
console avec les retours...

Michel Belleville


2009/5/6 ALAHYANE Rachid <afkkir@gmail.com>
2fd0206c71a1b22a9cc6293f38537461?d=identicon&s=25 Cyril Mougel (shingara)
on 2009-05-07 00:28
(Received via mailing list)
ALAHYANE Rachid a écrit :
>    validates_presence_of :name, :value
>    has_many     :properties  ,:dependent => :destroy #e.g ON CASCADE
> et
> prop1 de valeur p2
>
> les deux entrées sont insérées dans la table properties, ici, je viole
> la contrainte validates_uniqueness_of, mais je ne reçois aucune
> erreur, et les entrées sont insérées :( Est-ce validates_uniqueness_of
> qui ne fonctionne pas bien ? ou bien j'ai autres chose à rajouter ?
>
> Si ma question n'est pas claire je peux la détailler encore.
>
Tu peux le détailler avec un test unitaire? Ca sera largement plus
simple a lire.

--
Cyril Mougel
http://blog.shingara.fr
Be1e3ee645d23c95ba650c21bc885927?d=identicon&s=25 Fabien Jakimowicz (Guest)
on 2009-05-07 03:09
(Received via mailing list)
2009/5/7 Cyril Mougel <cyril.mougel@gmail.com>

> > class Property < ActiveRecord::Base
> > --------------------------------------------------
> >
> >
> Tu peux le détailler avec un test unitaire? Ca sera largement plus
> simple a lire.
>
>
C'est un débat courant avec ruby on rails : la validation d'uniqueness
ne
devrait pas être situé au niveau modèle, mais au niveau base de données
sous
forme de contrainte. C'est la seule façon de valider correctement ton
unicité.
Tu devrais déclarer un index unique sur la colone (ou son ensemble) que
tu
souhaites puis catcher l'exception levée en cas de violation de la
contrainte (ActiveRecord::StatementInvalid). Enfin, vérifier qu'il
s'agit
bien de la contrainte d'unicité qui est relevée puis traiter le cas et
raiser ActiveRecord::Rollback pour remettre la transaction sql dans un
état
correct.

Il existe un plugin permettant de gérer ces contraintes (et d'autres)
pour
postgresql, c'est en cours de discussion sur rails-core pour une
éventuelle
modification du core afin de gérer plus proprement ce genre de cas. Mais
on
est encore loin d'avoir une chose uniforme pour cela :
http://github.com/pedz/activerecord_constraints/tree/master

Je crois que certains parlent même de développer une sorte de
gestionnaire
de validations qui permettrait à un endroit de déclarer ses règles de
validation, d'avoir les contraintes sql liées et de remonter les erreurs
de
validations ActiveRecord.

--
http://fabien.jakimowicz.com
A2f664b4d5cbe5db3390e4be338c5ea8?d=identicon&s=25 ALAHYANE Rachid (Guest)
on 2009-05-07 23:00
(Received via mailing list)
Merci pour vos réponse, le plugin que Fabien propose est pas mal, il
faudra que je le teste.

En fait, lorsque je fais des tests via la console Ruby tout marche
comme prévu, c'est à dire que si je fais :

 >> p2 = Property.new
=> #<Property id: nil, type: "Property", version_id: nil, name: nil,
value: nil, created_at: nil, updated_at: nil>

 >> p3 = Property.new
=> #<Property id: nil, type: "Property", version_id: nil, name: nil,
value: nil, created_at: nil, updated_at: nil>

 >> v = Version.find :first
=> #<Version id: 1, num: 1, description: "des v1", created_at:
"2009-05-06 16:10:50", updated_at: "2009-05-06 16:10:50">

 >> p2.version = v
=> #<Version id: 1, num: 1, description: "des v1",  created_at:
"2009-05-06 16:10:50", updated_at: "2009-05-06 16:10:50">

 >> p3.version = v
=> #<Version id: 1, num: 1, description: "des v1",  created_at:
"2009-05-06 16:10:50", updated_at: "2009-05-06 16:10:50">

 >> p3.name = "req22"
=> "req22"

 >> p2.name = "req22"
=> "req22"

 >> p2.value = "r22"
=> "r22"

 >> p3.value = "r33"
=> "r33"

 >> p3.save and p2.save
=> false

v.properties
=> [#<Property id: 21, type: "Property", version_id: 1, name: "req22",
value: "r33", created_at: "2009-05-07 20:36:53", updated_at:
"2009-05-07 20:36:53">]

Ici, on voit très bien que p2 n'est pas ajouté à la base de données.

Le problème avec mon application, c'est que la saisie se fait via un
formulaire généré à la volé (du javascript)



Je passe par de tas de partiels pour afficher ce formulaire...

Je me suis dis que ce peut être ActiveRecord insère les deux lignes en
une seule requête, mais lorsque j'ai consulté les logs j'ai trouvé que
ce n'est pas le cas. C'est atomique, voici un bout de log :

SQL (0.4ms)   INSERT INTO "properties" ("name", "updated_at",
"version_id", "value", "created_at") VALUES(E'req2', '2009-05-06
18:23:49.090473', 3, E'hgjhg', '2009-05-06 18:23:49.090473') RETURNING
"id"

   SQL (0.4ms)   INSERT INTO "properties" ("name", "updated_at",
"version_id", "value", "created_at") VALUES(E'req2', '2009-05-06
18:23:49.096753', 3, E'lkj', '2009-05-06 18:23:49.096753') RETURNING
"id"

   SQL (0.4ms)   INSERT INTO "properties" ("name", "updated_at",
"version_id", "value", "created_at") VALUES(E'req2', '2009-05-06
18:23:49.098239', 3, E'kjlkj', '2009-05-06 18:23:49.098239') RETURNING
"id"

   SQL (0.4ms)   INSERT INTO "properties" ("name", "updated_at",
"version_id", "value", "created_at") VALUES(E'req2', '2009-05-06
18:23:49.099697', 3, E'sdfasdf', '2009-05-06 18:23:49.099697')
RETURNING "id"


cela ne vient pas du fait que j'utilise du javascript ?



On 7 mai 09, at 03:08, Fabien Jakimowicz wrote:

> > property.rb
> > --------------------------------------------------
> > Le problème que j'ai est le suivant : lorsque j'insère via un
> validates_uniqueness_of
> niveau base de données sous forme de contrainte. C'est la seule
> d'autres) pour postgresql, c'est en cours de discussion sur rails-
> http://fabien.jakimowicz.com
>
> --~--~---------~--~----~------------~-------~--~----~
> Vous avez reçu ce message, car vous êtes abonné au groupe
> "Railsfrance" de Google Groups.
> Pour transmettre des messages à ce groupe, envoyez un e-mail à
> l'adresse railsfrance@googlegroups.com
> Pour résilier votre abonnement envoyez un e-mail à l'adresse 
railsfrance-unsubscribe@googlegroups.com
> -~----------~----~----~----~------~----~------~--~---
>

Meilleures salutations / Best Regards

Rachid ALAHYANE
79b719d7f060027f31511f43f030e5c7?d=identicon&s=25 Tony Chauveau (kusanar)
on 2009-05-07 23:30
Dans ton log, où sont situés les

SELECT * FROM "properties"              |
 WHERE version_id = 3

Car d'après l'API, le principal risque de validates_uniqueness_of se
situe ici.

Si tous les select sont fait avant les insert...ben automatiquement,
tous retourneront que la propriété dans cette version n'existe pas
encore autorisant du même coup tous les insert

2 solutions :
- la contrainte au niveau de la table comme dit plus haut
- effectuer tes insertion complètement avant de permettre la suivante
(en effet, pas forcement simple en javascript, il te faudrait bloquer la
création de nouvelles propriétés tant que tu n'as pas eu le retour avec
confirmation de la sauvegarde de la propriété précédente.)
Bef7ff8a0537495a1876ffebdc9f8e51?d=identicon&s=25 Lionel Bouton (Guest)
on 2009-05-08 01:08
(Received via mailing list)
ALAHYANE Rachid a écrit, le 05/07/2009 11:00 PM :
> Le problème avec mon application, c'est que la saisie se fait via un
> formulaire généré à la volé (du javascript)
>
>

Le problème avec une application web c'est qu'il peut y avoir plusieurs
requêtes simultanées. Ton cas n'est donc pas exceptionnel. Le
validates_uniqueness_of ne protège pas contre deux requêtes simultanée
ayant pour effet de créer des entrées conflictuelles puisqu'il se
contente de vérifier si une entrée conflictuelle existe *avant* de
l'ajouter.

Comme l'expliquait Fabien, la seule solution est d'ajouter la contrainte
d'unicité en base.
Une autre consisterait à créer une transaction et verrouiller la table
entière avant d'essayer de sauver l'objet, mais d'une part les
performances baisseraient beaucoup et d'autre part il serait très facile
de se retrouver en situation de deadlock.


>
> "version_id", "value", "created_at") VALUES(E'req2', '2009-05-06
> 18:23:49.099697', 3, E'sdfasdf', '2009-05-06 18:23:49.099697')
> RETURNING "id"
>

Note que ces requêtes sont séparées par quelques millisecondes au plus :
le SELECT fait par le processus qui génère le dernier INSERT pour
vérifier qu'il n'y a pas de conflit a déjà eu lieu lorsque le premier
INSERT s'exécute (ce que tu peux aisément vérifier dans tes logs) et ne
voit donc pas de problème.

>
> cela ne vient pas du fait que j'utilise du javascript ?


Non, ça vient du fait qu'il n'y a pas moyen de vérifier qu'un autre
processus ne fait pas quelquechose derrière ton dos dans la base de
données si tu ne demandes pas à la base de données de le vérifier pour
toi (en ajoutant les contraintes qui te sont nécessaires dans cette
dernière). Les requêtes faites en Javascript illustrent juste le
problème (qui se produirait de toute façon lorsque plusieurs
utilisateurs accèderaient à ton appli pour créer des entrées
conflictuelles).

Lionel
A2f664b4d5cbe5db3390e4be338c5ea8?d=identicon&s=25 ALAHYANE Rachid (Guest)
on 2009-05-08 03:45
(Received via mailing list)
Merci pour vos explications claires et précises.
Alors je testerai le plugin et puis je ferai un retour ici.

On 8 mai 09, at 01:07, Lionel Bouton wrote:

> validates_uniqueness_of ne protège pas contre deux requêtes simultanée
> facile
>>
>>
>>
>> cela ne vient pas du fait que j'utilise du javascript ?
>
> Lionel
>
> --~--~---------~--~----~------------~-------~--~----~
> Vous avez reçu ce message, car vous êtes abonné au groupe
> "Railsfrance" de Google Groups.
> Pour transmettre des messages à ce groupe, envoyez un e-mail à
> l'adresse railsfrance@googlegroups.com
> Pour résilier votre abonnement envoyez un e-mail à l'adresse 
railsfrance-unsubscribe@googlegroups.com
> -~----------~----~----~----~------~----~------~--~---
>

Meilleures salutations / Best Regards

Rachid ALAHYANE
This topic is locked and can not be replied to.