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 ?

Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746

5 Answers5

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 .

jbescoyez
  • 1,393
  • 9
  • 17
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

fl00r
  • 82,987
  • 33
  • 217
  • 237
  • 1
    `my_message.update_all(:created_at => ...)` issues a syntax error, but the second option works fine! – Misha Moroshko Aug 30 '11 at 14:14
  • 1
    my_message.update_all will trigger `undefined method update_all`. Message.update_all will do the trick – Arivarasan L Feb 26 '14 at 06:55
  • You might be able to use increment_counter [if it's a counter you want to increment] which also skips callbacks, I believe. – rogerdpack Aug 19 '14 at 13:58
  • to work with instance you can use [update_column](http://apidock.com/rails/ActiveRecord/Persistence/update_column) or [update_columns](http://apidock.com/rails/ActiveRecord/Persistence/update_columns) for avoid callback calls – goodniceweb Nov 24 '15 at 10:52
6

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

lucapette
  • 20,564
  • 6
  • 65
  • 59
  • Looks like _almost_ what I need. The new value of `created_at` in my case is **not** the current time. – Misha Moroshko Aug 30 '11 at 13:26
  • @Misha you're obviously right. So you can use http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_attribute :D – lucapette Aug 30 '11 at 13:35
  • @lucapette: How? The docs says that `update_attribute` invokes the callbacks. – Misha Moroshko Aug 30 '11 at 14:07
  • @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 http://edgeguides.rubyonrails.org/active_record_validations_callbacks.html#skipping-callbacks maybe there is something i'm overlooking – lucapette Aug 30 '11 at 14:21
2

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

Archonic
  • 5,207
  • 5
  • 39
  • 55
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 => ...)
nathanvda
  • 49,707
  • 13
  • 117
  • 139