22

My question is similar to this one How to skip ActiveRecord callbacks? but instead of AR I'm using Mongoid, It seems like that isn't implemented yet in the current version of Mongoid, so I'd like to know what should be an elegant solution to implement it. (if necessary).

Community
  • 1
  • 1
jpemberthy
  • 7,473
  • 8
  • 44
  • 52

5 Answers5

31

Yes you can!

Mongoid is built on ActiveModel and ActiveModel has a skip_callback function. You can use skip_callback like this:

# skip the callback
MyModelClass.skip_callback(:save, :before, :ensure_foo_is_not_bar)

# rescue any errors to ensure callback is restored afterwords
begin
  my_model_instance.update_attributes :foo => 'bar'
rescue
  puts "Error from 'my_model_instance.update_attributes': #{$!}"
end

# restore the callback for future calls
MyModelClass.set_callback(:save, :before, :ensure_foo_is_not_bar)

I'm using this without a hitch in a big app. For more info, see this blog post by Jeff Kreeftmeijer:

http://jeffkreeftmeijer.com/2010/disabling-activemodel-callbacks/

bowsersenior
  • 12,524
  • 2
  • 46
  • 52
  • 1
    It would be great if there was a way to turn off all callbacks like you would turn off validations: `u.save!(validate: false)` would be `u.save!(callbacks: false)` – Brian Armstrong Oct 30 '13 at 19:28
  • Thanks a lot, this work for skip callbacks in my test suite. – miguel savignano Sep 20 '16 at 10:58
  • 2
    This can lead to a really big issue. If the code between the `skip_callback` and `set_callback` generates some error, the callback can be skipped forever and never setted again. The server would need to be restarted to make this work again. And the worst: this callback will be skipped for the entire class! That means all of the instances of that class will have this callback skipped! Be careful if you want to use this. I had big issues on my app while using this approach to `skip` and `set` callbacks. – Victor Apr 15 '20 at 09:02
  • 1
    Thanks, Victor, for pointing out this issue. I've updated the code in my answer to use a `resuce` block to prevent errors from disabling the callback. – bowsersenior Apr 15 '20 at 13:40
23

It might be easier to use the Mongoid atomic operations (set, unset, etc):

https://docs.mongodb.com/mongoid/current/tutorials/mongoid-persistence/#atomic

These do not fire callbacks.

Edit: Mongoid 3 says they do not fire callbacks. I am seeing them fire callbacks in Mongoid 2 though. So YMMV

hiroshi
  • 6,871
  • 3
  • 46
  • 59
Brian Armstrong
  • 19,707
  • 17
  • 115
  • 144
  • This works in Mongoid 4 as well. Was having a helluva time trying to figure out how to inc a counter in the parent document with cascading callbacks set to true. – Dex Dec 23 '13 at 09:48
19

I ended up using Brian Armstrong's suggestion and simply calling

person.set(name:"Robert Pulson")

in my after save callback.

hb922
  • 989
  • 1
  • 11
  • 23
3

Often this comes up when you want to do an update without firing all callbacks on a large volume of items. This can be done by descending to the driver:

Replacing 'foo' by 'bar' in the 'x' field.

User.all.each do |u|
  User.collection.where({ _id: u.id }).update({ 
    "$set" => { :x => u.x.gsub('foo', 'bar') 
  })
end
dB.
  • 4,700
  • 2
  • 46
  • 51
1

I search on the code. And there are no way to avoid callback in Mongoid. In both version 1.9 and 2.0.

You need made a patch or a feature request about that.

shingara
  • 46,608
  • 11
  • 99
  • 105
  • Thanks, Yes I was also reviewing the 2.0.0.beta and there is no skipping callbacks support ... let's see how AR does it and then implement it on my version of mongoid. – jpemberthy Jun 15 '10 at 16:43