Rails: Actualizar atributo de modelo sin invocar callbacks


Tengo un modelo de usuario que tiene un atributo :credits. Quiero un botón simple que agregue 5 a los créditos del usuario, a través de una ruta llamada "add" para que /users/3/add agregue 5 a los créditos de user id = 3.

def add
    @user = User.find(params[:id])
    @user.credits += 5
    redirect_to root_path
end

Esa es la parte relevante de mi controlador. El problema es que no quiero llamar a @usuario.guardar porque tengo una devolución de llamada before_save que vuelve a cifrar la contraseña del usuario en función de la hora UTC actual. Solo quiero simplemente añadir 5 al atributo y evitar el callback, nunca pensé que una cosa tan simple podría ser tan difícil.

EDITAR:

Cambié la devolución de llamada a: before_create, aquí está mi nuevo código de controlador (parte relevante):

  def add
    @user = User.find(params[:id])
    @user.add_credits(5)
    @user.save
    flash[:success] = "Credits added!"
    redirect_to root_path
  end

Y aquí está mi código en el modelo:

 def add_credits(num)
    self.credits = num
 end

EDITAR 2:

Ok fue un problema de validación que hizo que los cambios en "EDITAR" no funcionaran, pero todavía me encantaría una respuesta a la pregunta original de actualizar sin devoluciones de llamada!

Author: Sam Stern, 2011-12-28

9 answers

Rails 3.1 introducido update_column, que es lo mismo que update_attribute, pero sin activar validaciones o devoluciones de llamada:

Http://apidock.com/rails/ActiveRecord/Persistence/update_column

 117
Author: cvshepherd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-28 22:54:36

Para actualizar múltiples atributos sin devoluciones de llamada, puede usar update_all en su modelo de la siguiente manera:

self.class.update_all({name: value, name: value}, self.class.primary_key => id)

Si realmente lo desea, puede probar incluso un método update_columns y mezclar esto con su clase base active record.

Para actualizar un atributo puede usar update_column. Además hay algunos métodos específicos que se pueden encontrar en las guías rails http://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks

 7
Author: Matt Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-22 09:06:25

Creo que deberías usar el método update_counters en este caso. Úsalo así en la acción del controlador:

def add
  User.update_counters params[:id], :credits => 5
  redirect_to root_path
end
 3
Author: DanneManne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-28 04:50:19

Como respuesta general, en Rails 4 esta es una forma sencilla de actualizar atributos sin activar callbacks:

@user.update_column 'credits', 5

Si necesita actualizar varios atributos sin disparadores callbacks:

@user.update_columns credits: 5, bankrupt: false  

Hay otras opciones aquí en las Guías Rails si lo prefieres, pero encontré que esta manera es la más fácil.

 3
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-06 20:03:11

Debería ser capaz de usar update_all para evitar desencadenar devoluciones de llamada.

def add
 @user = User.find(params[:id])
 User.where(:id=>@user.id).update_all(:credits => @user.credits+5)
 redirect_to root_path
end

Preferiría poner esta lógica en el modelo, pero esto debería funcionar para resolver su problema original como se especifica en el controlador.

 2
Author: miked,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-28 04:22:42
 2
Author: katzmopolitan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-09 20:25:18

Para mongoid, terminé usando http://mongoid.org/en/mongoid/docs/persistence.html Específicamente, puedes usar:

person.set(name:"Robert Pulson")

Y no se emitirá devolución de llamada. Qué guay.

 2
Author: hb922,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-19 18:35:58

Tal vez su otro gancho before_save debería verificar si la contraseña del usuario realmente ha cambiado antes de cifrarla nuevamente.

 1
Author: Finbarr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-28 01:41:38

Tiene varias opciones, incluyendo cambiar qué devolución de llamada usa, por ejemplo, after_create.

Puede actualizar columnas sin activar callbacks, consulte Omitir Callbacks en la guía AR. Por ejemplo, update_column no activa devoluciones de llamada. El enlace anterior enumera las funciones que no activan.

También puede usar cualquiera de los formularios de Callback Condicional (o incluso un observador) para cuando se cambie la contraseña. Ver ActiveModel::Dirty, por ejemplo, @user.password_changed?.

 1
Author: Dave Newton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-22 11:45:50