ActiveRecord update


#1

Tenemos un modelo para la tabla clientes y dos operaciones sobre él, una
actualiza la información personal del cliente: nombre, email,
dirección,etc. La segunda actualiza información relativa a su cuenta: balance, fecha
de última transacción, etc.

El problema potencial que encuentro es que dos usuarios del sistema
esténoperando sobre el mismo cliente. Al hacer ‘c = Cliente.find(id)’ se lee
la
tupla completa de la DB. Digamos que para nuestra situación hipotética
ambos se leen “simultaneamente” y obtienen los mismos datos.

Luego el primer usuario actualiza algunos datos personales del cliente y
luego guarda: c.save

Posteriormente, el segundo usuario actualiza otros datos relativos a la
cuenta y luego guarda: c.save

El problema es que c.save produce un 'update clientes set … where id

…’ PARA TODAS las columnas de Clientes.
Entonces el primer c.save es sobreescrito por el segundo c.save.

Esto se solucionaría si ActiveRecord únicamente hiciera update sobre las
columnas que fueron actualizadas y no sobre toda la tupla. Lo
implemetaríapor medio de una bandera para cada columna de la tabla, donde se indica
si
el valor de dicha columna cambio.

Yo lo “solucioné” ejecutando un update directamente

class Cliente < ActiveRecord::Base

def agregar_balance(monto)
Cliente.connection.update(“update clientes set balace
=balance+#{monto.to_i} where id = #{self.id}”)
end

end

Siembargo preferíria no tener que recurrir a esto parche sino que
ActiveRecord me proveyera de la funcionalidad que necesito.

Saludos foro.


#2

Luego el primer usuario actualiza algunos datos personales del cliente y luego guarda: c.save
Posteriormente, el segundo usuario actualiza otros datos relativos a la cuenta y luego guarda: c.save

bueno… si lo que quieres es que el que lee primero bloquee, puedes usar
el parámetro :lock del find de AR

El problema es que c.save produce un ‘update clientes set … where id =…’ PARA TODAS las columnas de Clientes.

sí… save hace eso. Si quieres actualizar sólo algunas columnas, puedes
usar update_attributes

Esto se solucionaría si ActiveRecord únicamente hiciera update sobre las columnas que fueron actualizadas y no sobre toda la tupla.
No se solucionaría si los dos usuarios actualizan las mismas columnas.
La forma de solucionarlo en ese caso podría pasar por el bloqueo
mediante :lock, como te comentaba más arriba.

saludos,

javier ramirez


Estamos de estreno… si necesitas llevar el control de tus gastos
visita http://www.gastosgem.com !!Es gratis!!


#3

Gracias por contestas, pero en los fuentes de AR me encontré

   def update_attributes(attributes)
     self.attributes = attributes
     save
   end

Entonces update_attributes tambien llama a save y por tanto actualiza
todas las columnas

El find :lock no me funciona pero debe ser porque uso Rails 1.1.6,
siembargo supongo que si le agrego un :lock a cada find esto
provocaríabloqueos y por tanto tiempos de espera hasta que el primer usuario
guarde
y si éste nunca guarda entonces la espera sería eterna :frowning:


#4

Entonces update_attributes tambien llama a save y por tanto actualiza
todas las columnas

pues tienes razón… de hecho una vez me encontré que update_attributes
me hacía un update de todos los campos en un sql_server y le echaba la
culpa al adaptador de sqlserver pensando que el problema estaba ahí y no
en el propio update_attributes… no sé porqué tenía la idea que
actualizaba sólo los atributos que le pasabas.

siembargo supongo que si le agrego un :lock a cada find esto provocaría bloqueos y por tanto tiempos de espera
si… con los bloqueos a nivel de base de datos vas a tener ese
problema… siempre puedes tirar del bloqueo optimista que te da rails a
través de la columna lock_version. En este caso realmente no bloquea la
fila, pero si dos personas intentan actualizar el mismo registro, al
segundo que guarde le va a dar una excepción. A partir de ahí, ya tienes
que ver qué haces tú con la
excepción.
saludos,

j


Estamos de estreno… si necesitas llevar el control de tus gastos
visita http://www.gastosgem.com !!Es gratis!!


#5

On Feb 13, 2007, at 9:02 PM, Esteban wrote:

attributes id, name, and paytype—only these columns will be updated
when the object is saved. (Note that you have to include the id column
if you intend to save a row fetched using find_by_sql( )).

orders = Order.find_by_sql(“select id, name, pay_type from orders
where
id=123” )

first = orders[0]
first.name = “Wilma”
first.save

Pero sigues asumiendo que las modificaciones concurrentes van a usar
columnas distintas. Es que la aplicacion tiene algo especial que hace
que eso se cumpla?

La aproximacion normal a esto sin complicarse la vida es aceptar la
concurrencia: Si para el segundo usuario esos eran los campos
correctos, esta bien que sobreescriba los del anterior. Ya fueran
accesos concurrentes o no lo fueran. El ultimo gana y en general
tiene sentido esta interpretacion.

Si no, hay que tirar de lock.

– fxn


#6

ActiveRecord guarda todo lo que inicialmente leyó, asi que la solución es
cargar únicamente las columnas que necesitamos actualizar. Esto se puede
lograr con find_by_sql y luego update.

De hecho el AGDWR lo recomienda

However, in this next example the Active Record object contains just the
attributes id, name, and paytype—only these columns will be updated
when the object is saved. (Note that you have to include the id column
if you intend to save a row fetched using find_by_sql( )).

orders = Order.find_by_sql(“select id, name, pay_type from orders where
id=123” )

first = orders[0]
first.name = “Wilma”
first.save


#7

La aproximacion normal a esto sin complicarse la vida es aceptar la
concurrencia: Si para el segundo usuario esos eran los campos
correctos, esta bien que sobreescriba los del anterior. Ya fueran
accesos concurrentes o no lo fueran. El ultimo gana y en general
tiene sentido esta interpretacion.

Si no, hay que tirar de lock.

En efecto el :lock=>true del find me viene al pelo