95

I have a User model that has a :credits attribute. I want a simple button that will add 5 to the user's credits, through a route called "add" so that /users/3/add would add 5 to the credits of user id = 3.

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

That is the relevant part of my controller. The problem is, I dont want to call @user.save because I have a before_save callback that re-encrypts the user's password based on the current UTC time. I just want to simply add 5 to the attribute and avoid the callback, I never thought such a simple thing could be so hard.

EDIT:

I changed the callback to :before_create, here is my new controller code (relevant part):

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

and here is my code in the model:

 def add_credits(num)
    self.credits = num
 end

EDIT 2:

Ok it was a validation problem that made the changes in "EDIT" not work, but I'd still love an answer to the original question of updating without callbacks!

Sam Stern
  • 24,624
  • 13
  • 93
  • 124
  • I provided a link with a list of the methods that don't trigger callbacks, and both Finbarr and I suggested using a conditional callback--what additional solutions are you looking for? – Dave Newton Dec 28 '11 at 04:21

10 Answers10

158

Rails 3.1 introduced update_column, which is the same as update_attribute, but without triggering validations or callbacks:

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

cvshepherd
  • 3,947
  • 3
  • 20
  • 14
34

As a general answer, in Rails 4 this is a simple way to update attributes without triggering callbacks:

@user.update_column :credits, 5

If you need to update multiple attributes without triggering callbacks:

@user.update_columns credits: 5, bankrupt: false  

There are other options here in the Rails Guides if you prefer, but I found this way to be the easiest.

Matt
  • 5,800
  • 1
  • 44
  • 40
  • 4
    user.update_column credits:5 does not work. It should be user.update_column 'credits', 5 – Richard H Fung Mar 28 '17 at 01:56
  • I'm using Rails 4.2 and this definitely works for me. This line generates the following query: `UPDATE "users" SET "credits" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["credits", 5], ["updated_at", "2017-04-01 21:34:52.746626"], ["id", 1]]` – Matt Apr 01 '17 at 21:37
  • 1
    When using `update_column`, you can't pass it an Object like `credits: 5`. You have to pass two parameters, the first being the column name and the second being the value to update it to. So you can do `update_column :credits, 5`, which would generally be preferred over using a String for the column name. – Joshua Pinter Nov 25 '20 at 16:43
8

To update multiple attributes without callbacks you can use update_all in your model as so:

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

If you really want you can even try even a update_columns method and mixin this to your active record base class.

To update one attribute you can use update_column. In addition there is some specific methods that can found in the rails guides http://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks

Sam
  • 1,205
  • 1
  • 21
  • 39
Matt Smith
  • 3,479
  • 2
  • 28
  • 29
7

For mongoid, I ended up using http://mongoid.org/en/mongoid/docs/persistence.html Specifically, you can use:

person.set(name:"Robert Pulson")

and no callback will be issued. So cool.

hb922
  • 989
  • 1
  • 11
  • 23
4

I think you should use the method update_counters in this case. Use it like this in your controller action:

def add
  User.update_counters params[:id], :credits => 5
  redirect_to root_path
end
DanneManne
  • 21,107
  • 5
  • 57
  • 58
3

You should be able to use update_all to avoid triggering callbacks.

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

I'd prefer to put this logic in the model, but this should work to solve your original problem as spec'd in the controller.

miked
  • 4,489
  • 1
  • 17
  • 18
2

A few options for how to do this in rails4 http://edgeguides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks

katzmopolitan
  • 1,371
  • 13
  • 23
2

You have a number of options, including changing which callback you use, e.g., after_create.

You can update columns without triggering callbacks, see Skipping Callbacks in the AR guide. For example, update_column doesn't trigger callbacks. The previous link lists non-triggering functions.

You could also use any of the Conditional Callback forms (or even an observer) for when the password is changed. See ActiveModel::Dirty, e.g., @user.password_changed?.

mahemoff
  • 44,526
  • 36
  • 160
  • 222
Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • Ok I changed it to "after_create" but it still won't work! See my edit for my new code, I can't get credits to change. – Sam Stern Dec 28 '11 at 01:56
  • @hatboysam Well, it does something different--before you were adding, now you're setting. Use `save!` to see if there's an exception (and/or check the logs, and/or remove the backtrace silencers). – Dave Newton Dec 28 '11 at 02:02
1

Maybe your other before_save hook should check if the user's password has actually changed before encrypting it again.

Finbarr
  • 31,350
  • 13
  • 63
  • 94
0

You can update a column doing this

User.where( name: 'lily' ).update_all(age: '10')