How to "update_attributes" without executing "before_save"?
Asked Answered
L

5

25

I have a before_save in my Message model defined like this:

   class Message < ActiveRecord::Base
     before_save lambda { foo(publisher); bar }
   end

When I do:

   my_message.update_attributes(:created_at => ...)

foo and bar are executed.

Sometimes, I would like to update message's fields without executing foo and bar.

How could I update, for example, the created_at field (in the database) without executing foo and bar ?

Lucifer answered 30/8, 2011 at 13:15 Comment(0)
N
35

In rails 3.1 you will use update_column.

Otherwise:

In general way, the most elegant way to bypass callbacks is the following:

class Message < ActiveRecord::Base
  cattr_accessor :skip_callbacks
  before_save lambda { foo(publisher); bar }, :unless => :skip_callbacks # let's say you do not want this callback to be triggered when you perform batch operations
end

Then, you can do:

Message.skip_callbacks = true # for multiple records
my_message.update_attributes(:created_at => ...)
Message.skip_callbacks = false # reset

Or, just for one record:

my_message.update_attributes(:created_at => ..., :skip_callbacks => true)

If you need it specifically for a Time attribute, then touch will do the trick as mentioned by @lucapette .

Nomen answered 30/8, 2011 at 13:33 Comment(5)
Looks like a good general solution! One question: What exactly Message.batch = true do ?Lucifer
It is just a flag. You can replace it by whatever you want.Nomen
This won't work on serialized columns (update_column, that is), as it skips serialization / deserialization as well for some reason.Thence
Worth noting that using update_column will also mean validations are not run.Lamdin
Since you are setting an attribute on the class, this doesn't seem very thread-safe. Thoughts?Legist
E
17

update_all won't trigger callbacks

my_message.update_all(:created_at => ...)
# OR
Message.update_all({:created_at => ...}, {:id => my_message.id})

http://apidock.com/rails/ActiveRecord/Base/update_all/class

Ecthyma answered 30/8, 2011 at 13:18 Comment(4)
my_message.update_all(:created_at => ...) issues a syntax error, but the second option works fine!Lucifer
my_message.update_all will trigger undefined method update_all. Message.update_all will do the trickEnplane
You might be able to use increment_counter [if it's a counter you want to increment] which also skips callbacks, I believe.Brave
to work with instance you can use update_column or update_columns for avoid callback callsJardiniere
S
5

Use the touch method. It's elegant and does exactly what you want

Sushi answered 30/8, 2011 at 13:19 Comment(4)
Looks like almost what I need. The new value of created_at in my case is not the current time.Lucifer
@Misha you're obviously right. So you can use api.rubyonrails.org/classes/ActiveRecord/… :DSushi
@lucapette: How? The docs says that update_attribute invokes the callbacks.Lucifer
@Misha you can't. Thinking about your problem I was confusing callbacks with validation... So I think you should use update_all. By the way see edgeguides.rubyonrails.org/… maybe there is something i'm overlookingSushi
A
2

update_column or update_columns is the closest method to update_attributes and it avoids callbacks without having to manually circumvent anything.

Aeneid answered 13/10, 2017 at 23:33 Comment(1)
api.rubyonrails.org/classes/ActiveRecord/…Duenna
E
1

You could also make your before_save action conditional.

So add some field/instance variable, and set it only if you want to skip it, and check that in your method.

E.g.

before_save :do_foo_and_bar_if_allowed

attr_accessor :skip_before_save

def do_foo_and_bar_if_allowed
  unless @skip_before_save.present?
    foo(publisher)
    bar
  end
end

and then somewhere write

my_message.skip_before_save = true
my_message.update_attributes(:created_at => ...)
Exculpate answered 30/8, 2011 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.