74

I have a particularly complex model with validations and callbacks defined. The business needs now calls for a particular scenario where adding a new record requires skipping the validations and callbacks. What's the best way to do this?

Johnny Klassy
  • 1,650
  • 5
  • 16
  • 22

9 Answers9

118

This works in Rails 3:

Model.skip_callback(:create)
model.save(:validate => false)
Model.set_callback(:create)

(API docs and related question)

Community
  • 1
  • 1
Dinatih
  • 2,456
  • 1
  • 21
  • 21
  • 5
    Don't you again need to use ```set_callback(:create)``` ? I was under the impression that ```skip_callback``` disables it until re-enabled. – Caley Woods Sep 27 '11 at 17:08
  • 1
    I think yes, I never use this method, more info http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-skip_callback – Dinatih Sep 27 '11 at 17:13
  • 20
    This can't be thread safe? it would be nice to be able to set it on an instance level.. – Christopher Lindblom Dec 25 '12 at 22:15
  • 1
    Just in case anyone gets the same problem I did: In rails 3, this did not disable the observer callbacks. In order to do that, I did what http://stackoverflow.com/questions/707615/simple-way-of-turning-off-observers-during-rake-task says – Alex Siri Oct 08 '13 at 16:56
  • Recently we have the problem in our project. We have solved this with rather simple approach. It works for us well. The solution is for any version of Rails. Check it out in my blog post: http://railsguides.net/2014/03/25/skip-callbacks-in-tests – ka8725 Apr 11 '14 at 09:04
29

Use ActiveRecord::Persistence#update_column, like this:

Model.update_column(field, value)
bowsersenior
  • 12,524
  • 2
  • 46
  • 52
  • According to the documentation you linked: "Callbacks are invoked." – eggie5 Jul 28 '13 at 20:38
  • 5
    I think you looked at the documentation for `update_attribute`, not for `update_column`. If you look at the documentation for `update_column`, it is equivalent to `update_columns` for a single column. The docs for `update_columns` clearly state that validations and callbacks are skipped. – bowsersenior Aug 04 '13 at 17:25
  • Save my life! I tried skip_callback and other methods, no luck. This works! – Chihung Yu Aug 05 '14 at 04:34
12

If the goal is to simply insert or update a record without callbacks or validations, and you would like to do it without resorting to additional gems, adding conditional checks, using RAW SQL, or futzing with your exiting code in any way, it may be possible to use a "shadow object" which points to your existing db table. Like so:

class ImportedUser < ActiveRecord::Base
  # To import users with no validations or callbacks
  self.table_name = 'users'
end

This works with every version of Rails, is threadsafe, and completely eliminates all validations and callbacks with no modifications to your existing code. Just remember to use your new class to insert the object, like:

ImportedUser.new( person_attributes )
Brad Werth
  • 17,411
  • 10
  • 63
  • 88
2

My take was like this (note: this disables callbacks on create, for update, delete and others you need to add them to array).

    begin
      [:create, :save].each{|a| self.class.skip_callback(a) } # We disable callbacks on save and create

      # create new record here without callbacks, tou can also disable validations with 
      # .save(:validate => false)
    ensure
      [:create, :save].each{|a| self.class.set_callback(a) }  # and we ensure that callbacks are restored
    end
Marcin Raczkowski
  • 1,500
  • 1
  • 18
  • 26
  • Upvoted, but then it didn't work for my 3.2.6 app. I was calling Model.skip_callback(:create, :after) – TuteC Aug 09 '12 at 22:18
  • This is solution for 3.0+ It will not work on 2.x - but I've remember when I was looking for solution to this I've stumbled upon solution for 2.x which did not work for 3.x – Marcin Raczkowski Aug 13 '12 at 09:38
2

I would recommend NOT using the skip_callback approach since it is not thread safe. The sneaky save gem however is since it just runs straight sql. Note this will not trigger validations so you will have to call them yourself (ex: my_model.valid?).

Here are some samples from their docs:

# Update. Returns true on success, false otherwise.
existing_record.sneaky_save

# Insert. Returns true on success, false otherwise.
Model.new.sneaky_save

# Raise exception on failure.
record.sneaky_save!
Eric
  • 3,632
  • 2
  • 33
  • 28
1

I wrote a simple gem for skipping validations adhoc, but it could probably be updated to include skipping call backs as well.

https://github.com/npearson72/validation_skipper

You could take the can_skip_validation_for in the gem and add functionality for also skipping callbacks. Maybe call the method can_skip_validation_and_callbacks_for

Everything else would work the same. If you want help with doing that, let me know.

Nathan
  • 7,627
  • 11
  • 46
  • 80
1

What about adding a method to your model that let's you skip the callbacks?

class Foo < ActiveRecord::Base
  after_save :do_stuff

  def super_secret_create(attrs)
    self.skip_callback(:create)
    self.update_attributes(attrs)
    self.save(:validate => false)
    self.set_callback(:create)
  end
end

If you end up using something like this, I would recommend using self in the method instead of the model name to avoid connascence of name.

I also ran across a gist from Sven Fuchs that looks nice, it's here

Caley Woods
  • 4,707
  • 4
  • 29
  • 38
  • 1
    I like this approach but `skip_callback` is showing up as a class method while `update_attributes` is an instance method, is that what you intended? Hmm I don't get it, the API doc says `skip_callback` is instance method but it isn't so in Rails console, odd. – Johnny Klassy Sep 27 '11 at 18:08
0

This hack worked for me at last (redefined _notify_comment_observer_for_after_create method for the object):

if no_after_create_callback
  def object._notify_comment_observer_for_after_create; nil; end
end
TuteC
  • 4,342
  • 30
  • 40
0

None of these will work if your validations are written into the database itself.

+------------------------------------+--------------------------------------------------+------+-----+--------------------+----------------+
| Field                              | Type                                             | Null | Key | Default            | Extra          |
+------------------------------------+--------------------------------------------------+------+-----+--------------------+----------------+
| status                             | enum('Big','Small','Ugly','Stupid','Apologetic') | NO   |     | Stupid             |                |
Joshua Cook
  • 12,495
  • 2
  • 35
  • 31