Hace un par de meses (2 años para ser exactos ) escribi un pequeño tutorial sobre como podemos monitorear los cambios realizados sobre nuestros modelos en Ruby on Rails, esto principalmente cuando actualizamos un registro, y queremos conocer que atributo, o atributos, fuerón modificados y con que valor. El tutorial es completamente funcional para estas nuevas versiones de Rails, por ello no te preocupes. Mi intención con esta segunda parte es profundizar, un poco más, en el cómo podemos conocer si un atributo fue modificados después de guardar cambios. Es algo muy interesante. Veamos.
Cómo sabemos en Rails existen diferentes formas de actualizar un registro. Te comparto un post para más información.
Podemos conocer si un atributo fue modificado a través de changed? o was.
user = User.last
user.username
=> "eduardo_gpg"
user.username = 'Cody'
user.username_was
=> "eduardo_gpg"
user.username_changed?
=> true
Hasta aquí nada nuevo, sin embargo ¿Qué pasa si persistimos los cambios?
user = User.last
user.username
user.username = 'Cody'
user.save
=> true
user.username_changed?
=> false
En este caso al intentar conocer su un atributo fue modificado obtendremos como resultado false .
Esto se debe ya que el objeto, al ser persisto, regresa a un estado sin cambios. 😬 Esto sin duda es muy útil en una gran cantidad de casos, pero ¿Y si necesitamos realizar un acción después de persistir los cambios validando si un campo en concreto fue modificado? Ok, este quizás ya es un escenario muy puntual, pero dejame decirte que puedes toparse con él, o inclusive es probable que ya te encuentre con este problema.
Imaginemos el siguiente escenario: Debemos enviar un correo electrónico a nuestro usuario después de haberse validado su cuenta. La validación la realizaremos mediante el atributo verified, un atributo booleano. En otras palabras, enviaremos un correo electrónico a nuestro usuario siempre y cuando su atributo verified pase de falso a verdadero, esto después de guardar cambios.
Si queremos evitar el uso de máquinas de estados podemos tener dos posibles soluciones. La primera es agregando un par de callbacks y un atributo extra al modelo.
attr_accessor :memento_verified
before_update :set_value_for_memento_verified
after_update :send_mail, if: :is_valid_to_send_mail?
def set_value_for_memento_verified
self.memento_verified = self.verified_was if self.verified_changed?
end
def send_mail
# Enviamos el correo
end
# El método retona true
# En caso el atributo verified haya sido modificado y su valor actual sea true
def is_valid_to_send_mail?
self.memento_verified != self.verified && self.verified
end
En este caso aplicamos todos los conocimientos adquiridos previamente. Almacenamos en la variable memento_verified el valor anterior de verified, claro siempre y cuando esta variable haya sido modificada. Realizamos esta acción antes de actualizar el registro.
Después de la actualización validamos si memento_verified es diferente a verified, y si self.verified es true, en caso de cumplirse ambas condiciones podemos concluir que hubo un cambio de valor, de falso a verdadero.
Esto funciona, y muy probablemente sea la primera opción de muchos, sin embargo para nuestra fortuna Ruby on Rails tiene contemplado este escenario (Rails es amor). Nosotros podemos conocer si un atributo fue modificado o no, después de guardar cambios, utilizando el método saved_change_to_attribute?
Veamos el mismo ejemplo.
after_update :send_mail, if: :is_valid_to_send_mail?
def send_mail
# Enviamos el correo
end
# El método retorna true
# en caso el atributo verified haya sido modificado y su valor actual sea true
def is_valid_to_send_mail?
saved_change_to_verified? && self.verified
end
Obtenemos el mismo resultado, con menos líneas de código y desde mi punto de vista, mucho más legible.
El método saved_change_to_attribute? podremos utilizarlo para todos los atributos del modelo, basta con reemplazar attribute? con el atributo el cual queremos conocer si fue modificado o no. Por supuesto no olvidemos colocar el signo de interrogación (?).
El método saved_change_to_attribute? estará disponible en versiones superiores de Rails 5.