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!

1
  • 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? Dec 28, 2011 at 4:21

10 Answers 10

159

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

2
  • I would love to use this but I can't upgrade to Rails 3.1 cleanly, or at least I don't know how.
    – Sam Stern
    Dec 29, 2011 at 20:51
  • 1
    Which Rails version are you running at the moment?
    – cvshepherd
    Dec 29, 2011 at 22:35
35

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.

3
  • 4
    user.update_column credits:5 does not work. It should be user.update_column 'credits', 5 Mar 28, 2017 at 1: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 1, 2017 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. Nov 25, 2020 at 16:43
8

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.

2
  • its person.set(:name, "Robert Pulson") Dec 10, 2016 at 14:20
  • 3
    @JudeCalimbas no it's not, ArgumentError: wrong number of arguments (given 2, expected 1)
    – rept
    Aug 31, 2017 at 23:40
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

0
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
0
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.

2

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

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?.

2
  • 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, 2011 at 1: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). Dec 28, 2011 at 2:02
1

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

0

You can update a column doing this

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.