Forum: Rails-ES Problema con validates_uniqueness_of

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.
Jose vicente R. (Guest)
on 2009-04-07 12:57
Muy buenas, estamos diseñando una seccion de comentarios para los
videos, algo al estilo del Railcast sobre Ajax que permita introducir un
comentario de forma rapida y sencilla.Nuestros videos heredan de
contenidos y habiamos pensado en usar dentro del modelo comentarios esta
linea:

validates_uniqueness_of :texto_completo, :scope => [:usuario_id,
:inicial_id], :on => :create


Con ella queriamos evitar que un mismo usuario pudiese introducir el
mismo mensaje varias veces (el típico inquieto que le da 2 veces al
raton antes de que termine de cargar), pero al hacer las pruebas nos
hemos dado cuenta que si pinchamos varias veces el boton si se vuelve a
introducir el mensaje.

Finalmente hemos optado por construir una funcion duplicado que
comprueba que en la tabala no existe ya el comentario.


Me gustaria saber si alguien sabe porque no nos funciona el
validates_uniqueness_of, ya que nos parecia una opcion muy atractiva y
facil de leer, como comento hemos conseguido solventarlo, pero en le
futuro nos gustaria poder utilizar las herramientas que rails nos
ofrece.
javier ramirez (Guest)
on 2009-04-07 13:05
(Received via mailing list)
hola,

> Me gustaria saber si alguien sabe porque no nos funciona el
> validates_uniqueness_of, ya que nos parecia una opcion muy atractiva y
>

validate uniqueness of es una validación muy débil de rails que no
escala bien así por defecto. Básicamente hace una query a ver si el
valor ya existe, y si no existe entonces te da true.

esto para un único servidor corriendo va bien, pero cuando tienes más,
si dos peticiones llegan "a la vez" puede que la segunda lea antes de
que la primera haya escrito, y ahí tienes un conflicto.

yo para todos los validate uniqueness of recomiendo crearse un índice
unique en la base de datos, para evitar que si la validación te falla (o
si accedes a la db directamente) nunca vayas a tener duplicados.
Recuerda, la base de datos es tu amiga, aunque a veces se oigan cantos
de sirena que dicen lo contrario ;)

además del índice único, otra buena opción es utilizar bloqueo en esa
tabla. Puedes utilizar el bloqueo optimista que da rails de forma
transparente y simplemente usar la columna row_version (o algo así, en
la doc te lo dice seguro) y capturar la excepción en caso de que se de
una modificación desde la última lectura.

saludos,


--
javier ramírez

..i do ruby on rails development in madrid, spain, at
http://www.aspgems.com
..you can find out more about me on http://formatinternet.wordpress.com
and http://workingwithrails.com/person/5987-javier-ramirez
Andrés G. (Guest)
on 2009-04-07 14:03
(Received via mailing list)
Lo que dice Javier es lo que yo creo que deberías hacer. Aunque si no he
entendido mal este caso se da cuando un usuario hace un submit de un
comentario via AJAX. Bien si este es el caso:
Asumo que el usuario tiene javascript (por lo que podrá disfrutar del
magnifico formulario AJAX)

En ese caso yo miraría también hacer algo en javascript que controle el
estado en el que se encuentra la operación AJAX (introducir el comment
en la
BBDD) y hasta que no venga una respuesta de terminado deshabilitar el
boton
de "enviar" Así los nerviosos no pueden darle dos veces.
Como ya digo esto es sólo una solución parcial (y no probada) para el
caso
de que el usuario este usando un browser con javascript (casi todos los
grandes)

El 7 de abril de 2009 11:04, javier ramirez 
<removed_email_address@domain.invalid>
escribió:
Daniel R. Troitiño (Guest)
on 2009-04-07 14:48
(Received via mailing list)
2009/4/7 Andrés gutiérrez <removed_email_address@domain.invalid>:
> Como ya digo esto es sólo una solución parcial (y no probada) para el caso
>>
>> si accedes a la db directamente) nunca vayas a tener duplicados.
>>
>> removed_email_address@domain.invalid
>> http://lists.simplelogica.net/mailman/listinfo/ror-es
>
>
> _______________________________________________
> Ror-es mailing list
> removed_email_address@domain.invalid
> http://lists.simplelogica.net/mailman/listinfo/ror-es
>
>

Y como propuesta final: sin validates_uniqueness_of (que no funciona
muy bien, como ya han dicho) y sin indices en texto_completo (que
funcionan perfectamente, pero son un poco burrada en un campo de texto
como un comentario). Y recuerda: la base de datos es el único punto de
tu arquitectura donde puedes comprobar la unicidad de tus datos.

¿Y como lo evitas? Sencillo: al crear rellenas un campo extra que sea
un hash (MD5, SHA1,... elige tu droga) de algo como
"#{usuario_id}|#{inicial_id}|#{texto_completo}" (yo incluiría también
el id del video en el que se comenta, porque dos comentarios iguales
en dos videos diferentes sí deberían estar permitidos). Dos
comentarios iguales tendrán el mismo hash, dos diferentes es muy
complicado que lo tengan. Ese campo pueden ser 128 bits (caben en un
BIGINT de MySQL) y ese índice único sí que será manejable. Luego solo
queda capturar la excepción que se genere al salvar y listo.

Suerte.
Fernando G. (Guest)
on 2009-04-07 15:02
(Received via mailing list)
El día 7 de abril de 2009 11:04, javier ramirez 
<removed_email_address@domain.invalid>
escribió:> yo para todos los validate uniqueness of recomiendo crearse un índice
> unique en la base de datos, para evitar que si la validación te falla (o
> si accedes a la db directamente) nunca vayas a tener duplicados.
> Recuerda, la base de datos es tu amiga, aunque a veces se oigan cantos
> de sirena que dicen lo contrario ;)

Al 100% sobre apoyarse en la BD..

Sobre lo de crear un índice unique tambien mola sólo que un índice de
un campo TEXT igual no mola mucho.. aunque se me ocurre que se
podríahacer un hash del campo TEXT y almacenar el hash en un campo
VARCHAR(32) y hacer en unique sobre este.

Aunque hacer un unique sobre el contenido de un comentario igual no es
del todo correcto pues puede haber 2 comentarios iguales de manera
totalmente lícita.. incluso del mismo usuario.. incluso del mismo
usuario mismo elemento padre..

Yo creo que la comprobación en el caso que nos atañe debería ir por
otro lado.. nosé.. igual no dejarle comentar con menos de 10 segundo
de retraso ...

f.
javier ramirez (Guest)
on 2009-04-07 15:26
(Received via mailing list)
> Y como propuesta final: sin validates_uniqueness_of (que no funciona
> muy bien, como ya han dicho) y sin indices en texto_completo (que
> funcionan perfectamente, pero son un poco burrada en un campo de texto
> como un comentario).

cierto..  sinceramente ni me paré a mirar lo que se estaba enviando como
campo único. He visto el problema del validate_uniqueness_of y la
concurrencia y he dado la explicación estándar

un índice único para el contenido de un comentario es, efectivamente,
una burrada :)

Releyendo el problema original, e insistiendo en que cuando tengas que
mirar un uniqueness of lo mejor es validar directamente en la base de
datos (índice + transaction), para este caso igual la solución no es
jugar con el uniqueness. Más que nada porque dos comentarios diferentes
no tienen porqué ser únicos. Piensa en el caso típico de "+1" o "prime".

Una solución simple es en el momento de empezar el submit hacer un
disable del botón de envío y en el callback de complete dejarlo
habilitado de nuevo (y de paso si ha habido éxito limpias el campo para
que no pueda enviarlo otra vez).

saludos,

--
javier ramírez

..i do ruby on rails development in madrid, spain, at
http://www.aspgems.com
..you can find out more about me on http://formatinternet.wordpress.com
and http://workingwithrails.com/person/5987-javier-ramirez
Andrés G. (Guest)
on 2009-04-07 16:11
(Received via mailing list)
Una solución simple es en el momento de empezar el submit hacer un
disable del botón de envío y en el callback de complete dejarlo
habilitado de nuevo (y de paso si ha habido éxito limpias el campo para
que no pueda enviarlo otra vez).

Vale, esto es lo que he dicho yo arriba, pero  bien explicado :-)
Pero de todas formas sólo es solución para los usuarios que tengan el
javascript habilitado ¿no?

El 7 de abril de 2009 13:26, javier ramirez 
<removed_email_address@domain.invalid>
escribió:
Xavier N. (Guest)
on 2009-04-07 18:16
(Received via mailing list)
En cuanto al doble submit Rails provee una opcion :disable_with que le
va al pelo:

http://api.rubyonrails.org/classes/ActionView/Help...
Jose vicente R. (Guest)
on 2009-04-07 18:25
Bueno.....pues muchisimas gracias por todas las opiniones!!!!

Ya tengo mil y un recursos alternativos...eso si, una pena que el
uniqueness flojee tanto.

Saludos ;)
Gunnar W. (Guest)
on 2009-04-08 21:33
(Received via mailing list)
Jose vicente Ribera pellicer dijo [Tue, Apr 07, 2009 at 10:57:23AM
+0200]:
> mismo mensaje varias veces (el típico inquieto que le da 2 veces al
> raton antes de que termine de cargar), pero al hacer las pruebas nos
> hemos dado cuenta que si pinchamos varias veces el boton si se vuelve a
> introducir el mensaje.

Ummm... Varias personas ya te apuntaron a que validates_uniqueness_of
es un validador bastante débil, y principalmente orientado a buscar
enteros (y además, en realidad depende de que tengas un índice sobre
el campo en cuestión - sobre un campo de texto se vuelve
necesariamente muy lento).

Te recomiendo:

1- Hacer lo que otros te han recomendado, poner la validación en la
   BD a través de un índice UNIQUE

2- No olvides que una excepción levantada por la BD (con el índice
   mencionado anteriormente) se ve muy fea cuando el usuario se la
   encuentra. ¡Envuelve todos los eventos que graben al comentario en
   un begin/rescue!

3- Ataca al problema en cuestión, no al síntoma. Si te molestan los
   usuarios que le pican tres veces seguidas al "Save", ¿por qué no
   agregar una validación más simple y no dependiente del contenido,
   sino del tiempo? Por ejemplo, prohibir que un usuario grabe más de
   un comentario en un lapso menor a diez segundos. Las comparaciones
   de fechas (claro, no olvides ponerle índices) son baratas y
   confiables. E incluso, más que una validación, podrías hacerlo vía
   un callback... Creo que resulta incluso más simple y legible.

   class WebUser < ActiveRecord::Base
     has_many :comments, :order => 'created_at DESC'
   end

   class Comment < ActiveRecord::Base
     belongs_to :web_user
     MinCommentWaitTime = 5.seconds
     before_validation_on_create do |comment|
       return false if comment.web_user.comments.last.created_at >=
         Time.now() - MinCommentWaitTime
     end
   end

Saludos,

--
Gunnar W. - removed_email_address@domain.invalid - (+52-55)5623-0154 / 1451-2244
PGP key 1024D/8BB527AF 2001-10-23
Fingerprint: 0C79 D2D1 2C4E 9CE4 5973  F800 D80E F35A 8BB5 27AF
Gunnar W. (Guest)
on 2009-04-08 21:34
(Received via mailing list)
Jose vicente Ribera pellicer dijo [Tue, Apr 07, 2009 at 04:25:44PM
+0200]:
> Bueno.....pues muchisimas gracias por todas las opiniones!!!!
>
> Ya tengo mil y un recursos alternativos...eso si, una pena que el
> uniqueness flojee tanto.

No flojea - Pero requiere que lo sepas usar.

--
Gunnar W. - removed_email_address@domain.invalid - (+52-55)5623-0154 / 1451-2244
PGP key 1024D/8BB527AF 2001-10-23
Fingerprint: 0C79 D2D1 2C4E 9CE4 5973  F800 D80E F35A 8BB5 27AF
Daniel R. Troitiño (Guest)
on 2009-04-08 23:57
(Received via mailing list)
2009/4/8 Gunnar W. <removed_email_address@domain.invalid>:
>     has_many :comments, :order => 'created_at DESC'
>
Esta solución presenta el mismo problema que la original: imagina que
tienes dos mongrels, dos passenger o dos lo-que-sea, el primero recoge
el primer click y el segundo el click repetido. Ambos llegan casi al
mismo tiempo al before_validation, pero ninguno de ellos hace el save
a la base de datos antes de que el otro pase el validation. Los dos
hacen el save sin problemas.

A mucha gente del mundo Rails no les gusta la teoría de bases de
datos, pero tengo que repetir que en este tema (validaciones de
unicidad) la BBDD es el **único** punto donde se pueden realizar con
fiabilidad (bueno, al menos la fiabilidad que te da el motor de BBDD).
Francesc E. (Guest)
on 2009-04-09 00:17
(Received via mailing list)
On Apr 8, 2009, at 9:57 PM, Daniel R. Troitiño wrote:

> fiabilidad (bueno, al menos la fiabilidad que te da el motor de BBDD).
El año pasado hubo un thread parecido donde se comentaba el problema de
las validaciones en Rails.

     http://lists.simplelogica.net/pipermail/ror-es/200...

Yo normalmente meto el constrain en la base de datos y así evito
sustos, eso
no quita que en la aplicación tambien tengas un mecanismo para evitar
los
problemas en caso de que portes la aplicación a sistemas en lo que no
puedas meter constrains.

En su momento programé un plugin [1] para añadir los constraints de
psql desde
las migraciones.

[1] http://github.com/fesplugas/simplified_constraints...
Gunnar W. (Guest)
on 2009-04-09 06:12
(Received via mailing list)
Daniel R. Troitiño dijo [Wed, Apr 08, 2009 at 09:57:04PM +0200]:
> fiabilidad (bueno, al menos la fiabilidad que te da el motor de BBDD).
Pues... ¿Qué te digo? Hay cosas que no me gustan de la cultura Rails
(como asumir que la BD es tonta y toda la inteligencia debe estar en
los modelos), y es precisamente la integración con ese cachito a lo
que me refiero con que tienes que hacer cosas aparentemente feas como
cachar excepciones de la BD a diferentes niveles. No es, como algunos
lo dirían, "The Rails Way"... Cuando eres Railsero de lleno usas bases
de datos MySQL (¡Puaj!), y no asumes que tu BD es capaz de nada más.

Hay un puntito que sí mejora con la implementación que sugerí: La
ventana de tiempo en que puede presentarse este conflicto es mucho
menor (porque la transacción completa de inserción tomará menos
tiempo, dado que tiene que hacer una búsqueda sobre un campo timestamp
indexado y no sobre uno de texto completo). Sin embargo, como dices,
el problema sigue ahí. Y sólo puedes zafarte de él con constraints y/o
triggers en la BD.

Saludos,

--
Gunnar W. - removed_email_address@domain.invalid - (+52-55)5623-0154 / 1451-2244
PGP key 1024D/8BB527AF 2001-10-23
Fingerprint: 0C79 D2D1 2C4E 9CE4 5973  F800 D80E F35A 8BB5 27AF
This topic is locked and can not be replied to.