How can I avoid running ActiveRecord callbacks?
Asked Answered
H

29

169

I have some models that have after_save callbacks. Usually that's fine, but in some situations, like when creating development data, I want to save the models without having the callbacks run. Is there a simple way to do that? Something akin to...

Person#save( :run_callbacks => false )

or

Person#save_without_callbacks

I looked in the Rails docs and didn't find anything. However in my experience the Rails docs don't always tell the whole story.

UPDATE

I found a blog post that explains how you can remove callbacks from a model like this:

Foo.after_save.clear

I couldn't find where that method is documented but it seems to work.

Haldis answered 10/3, 2009 at 23:52 Comment(5)
If you're doing something destructive or expensive (like sending emails) in a callback I recommend moving this out and triggering it separately from the controller or elsewhere. This way you won't "accidentally" trigger it in development, etc.Diplegia
solution you accepted is not working for me. I am using rails 3. i am getting an error like this:--undefined method `update_without_callbacks' for #<User:0x10ae9b848>Gynaecocracy
yaa that blog post worked....Gynaecocracy
Related question : #19449519Irreverence
Wouldn't Foo.after_save.clear remove callbacks for the entire model? And then how do you propose to restore them?Neldanelia
P
72

This solution is Rails 2 only.

I just investigated this and I think I have a solution. There are two ActiveRecord private methods that you can use:

update_without_callbacks
create_without_callbacks

You're going to have to use send to call these methods. examples:

p = Person.new(:name => 'foo')
p.send(:create_without_callbacks)

p = Person.find(1)
p.send(:update_without_callbacks)

This is definitely something that you'll only really want to use in the console or while doing some random tests. Hope this helps!

Passionless answered 11/3, 2009 at 4:29 Comment(4)
its not working for me. I am using rails 3. i am getting an error like this:--undefined method `update_without_callbacks' for #<User:0x10ae9b848>Gynaecocracy
Your suggestion is not working but the blog post mentioned in the update part is working..Gynaecocracy
This will skip validations, too.Herzl
I have another solution for any version of Rails. It works for us well. Check it out in my blog post: railsguides.net/2014/03/25/skip-callbacks-in-testsDeduce
A
249

Use update_column (Rails >= v3.1) or update_columns (Rails >= 4.0) to skip callbacks and validations. Also with these methods, updated_at is not updated.

#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

#2: Skipping callbacks that also works while creating an object

class Person < ActiveRecord::Base
  attr_accessor :skip_some_callbacks

  before_validation :do_something
  after_validation :do_something_else

  skip_callback :validation, :before, :do_something, if: :skip_some_callbacks
  skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks
end

person = Person.new(person_params)
person.skip_some_callbacks = true
person.save

UPDATE (2020)

Apparently Rails has always supported :if and :unless options, so above code can be simplified as:

class Person < ActiveRecord::Base
  attr_accessor :skip_some_callbacks

  before_validation :do_something, unless: :skip_some_callbacks
  after_validation :do_something_else, unless: :skip_some_callbacks
end

person = Person.new(person_params)
person.skip_some_callbacks = true
person.save
Accountant answered 12/9, 2011 at 10:8 Comment(4)
looks like it works with 2.x as well, and there are a host of other methods that operate similarly: guides.rubyonrails.org/…Hydrate
This doesn't address :create_without_callbacks :( How can I run something similar to that? (Worked in Rails2, removed in Rails3).Drachma
Assuming @person is a variable in a controller somewhere, this solution means that people reading your model class won't be able to understand the callbacks. They will see after_create :something_cool and will think "great, something cool happens after create!". To actually understand your model class, they will have to grep through all your controllers, looking for all the little places where you have decided to inject logic. I don't like it >o< ;;Miele
replace skip_callback ..., if: :skip_some_callbacks with after_create ..., unless: :skip_some_callbacks to run this properly with after_create.Bradbradan
P
72

This solution is Rails 2 only.

I just investigated this and I think I have a solution. There are two ActiveRecord private methods that you can use:

update_without_callbacks
create_without_callbacks

You're going to have to use send to call these methods. examples:

p = Person.new(:name => 'foo')
p.send(:create_without_callbacks)

p = Person.find(1)
p.send(:update_without_callbacks)

This is definitely something that you'll only really want to use in the console or while doing some random tests. Hope this helps!

Passionless answered 11/3, 2009 at 4:29 Comment(4)
its not working for me. I am using rails 3. i am getting an error like this:--undefined method `update_without_callbacks' for #<User:0x10ae9b848>Gynaecocracy
Your suggestion is not working but the blog post mentioned in the update part is working..Gynaecocracy
This will skip validations, too.Herzl
I have another solution for any version of Rails. It works for us well. Check it out in my blog post: railsguides.net/2014/03/25/skip-callbacks-in-testsDeduce
C
30

Updated:

@Vikrant Chaudhary's solution seems better:

#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)

My original answer :

see this link: How to skip ActiveRecord callbacks?

in Rails3,

assume we have a class definition:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Approach1:

User.send(:create_without_callbacks)
User.send(:update_without_callbacks)

Approach2: When you want to skip them in your rspec files or whatever, try this:

User.skip_callback(:save, :after, :generate_nick_name)
User.create!()

NOTE: once this is done, if you are not in rspec environment, you should reset the callbacks:

User.set_callback(:save, :after, :generate_nick_name)

works fine for me on rails 3.0.5

Caporetto answered 30/11, 2011 at 2:59 Comment(0)
K
30

If the goal is to simply insert 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, consider using a "shadow object" pointing to your existing db table. Like so:

class ImportedPerson < ActiveRecord::Base
  self.table_name = 'people'
end

This works with every version of Rails, is threadsafe, and completely eliminates all validations and callbacks with no modifications to your existing code. You can just toss that class declaration in right before your actual import, and you should be good to go. Just remember to use your new class to insert the object, like:

ImportedPerson.new( person_attributes )
Knitwear answered 9/12, 2015 at 22:41 Comment(4)
Best solution EVER. Elegant and simple!Nekton
This worked really nicely for me because it was something I wanted to do only in test, to simulate database "before" state, without polluting my production model object with machinery to optionally skip callbacks.Joub
By far the best answerPeursem
Upvoted because it shows how to work-around existing rails constraints and helped me understand how the whole object MVC really works. So simple and clean.Juror
G
21

rails 3:

MyModel.send("_#{symbol}_callbacks") # list  
MyModel.reset_callbacks symbol # reset
Gibbosity answered 26/11, 2010 at 8:3 Comment(2)
Nice. Also MyModel.skip_callback(:create, :after, :my_callback) for precise control.. see ActiveSupport::Callbacks::ClassMethods docs for all the lobangBennion
Useful info: the 'symbol' in reset_callbacks is not :after_save, but rather :save. apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/…Urticaceous
A
17

You could try something like this in your Person model:

after_save :something_cool, :unless => :skip_callbacks

def skip_callbacks
  ENV[RAILS_ENV] == 'development' # or something more complicated
end

EDIT: after_save is not a symbol, but that's at least the 1,000th time I've tried to make it one.

Agnostic answered 11/3, 2009 at 0:2 Comment(1)
I really think this is the best answer here. This way the logic that determines when the callback is skipped is available in the model, and you don't have crazy code fragments everywhere pealing back the business logic, or circumventing encapsulation with send. KOODOSMiele
W
11

You can use update_columns:

User.first.update_columns({:name => "sebastian", :age => 25})

Updates the given attributes of an object, without calling save, hence skipping validations and callbacks.

Workable answered 2/4, 2013 at 23:5 Comment(0)
E
7

The only way to prevent all after_save callbacks is to have the first one return false.

Perhaps you could try something like (untested):

class MyModel < ActiveRecord::Base
  attr_accessor :skip_after_save

  def after_save
    return false if @skip_after_save
    ... blah blah ...
  end
end

...

m = MyModel.new # ... etc etc
m.skip_after_save = true
m.save
Epiphenomenon answered 11/3, 2009 at 3:7 Comment(2)
I love trying (untested). Thrill ride.Shopper
Tested and it works. I think this is a very good and clean solution, thanks!Pung
V
6

Looks like one way to handle this in Rails 2.3 (since update_without_callbacks is gone, etc.), would be to use update_all, which is one of the methods that skips callbacks as per section 12 of the Rails Guide to validations and callbacks.

Also, note that if you are doing something in your after_ callback, that does a calculation based on many association (i.e. a has_many assoc, where you also do accepts_nested_attributes_for), you will need to reload the association, in case as part of the save, one of its members was deleted.

Verne answered 13/4, 2010 at 5:27 Comment(0)
M
5

The most up-voted answer might seem confusing in some cases.

You can use just a simple if check if you would like to skip a callback, like this:

after_save :set_title, if: -> { !new_record? && self.name_changed? }
Moore answered 23/2, 2017 at 9:57 Comment(0)
P
4

with Rails 6 you can now use the insert methods

from the documentation:

Inserts multiple records into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record's type casting and serialization.

Pyrolysis answered 26/5, 2021 at 8:57 Comment(0)
S
3

https://gist.github.com/576546

just dump this monkey-patch into config/initializers/skip_callbacks.rb

then

Project.skip_callbacks { @project.save }

or the like.

all credit to the author

Sent answered 24/2, 2012 at 23:26 Comment(0)
D
3

A solution that should work across all versions of Rails without the use of a gem or plugin is simply to issue update statements directly. eg

ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"

This may (or may not) be an option depending on how complex your update is. This works well for eg updating flags on a record from within an after_save callback (without retriggering the callback).

Disordered answered 8/2, 2013 at 18:2 Comment(2)
Not sure why the downvote, but I still think the above answer is legitimate. Sometimes the best way to avoid issues with ActiveRecord behaviour is to avoid using ActiveRecord.Disordered
Upvoted on principle to counter the -1. We just had a production issue (with a long story behind it) that required us to create a new record (not an update) and firing callbacks would have been catastrophic. All the above answers are hacks whether they admit it or not and going to the DB was the best solution. There ARE legitimate conditions for this. Though one should beware of SQL injection with the #{...}.Expressivity
B
3

I needed a solution for Rails 4, so I came up with this:

app/models/concerns/save_without_callbacks.rb

module SaveWithoutCallbacks

  def self.included(base)
    base.const_set(:WithoutCallbacks,
      Class.new(ActiveRecord::Base) do
        self.table_name = base.table_name
      end
      )
  end

  def save_without_callbacks
    new_record? ? create_without_callbacks : update_without_callbacks
  end

  def create_without_callbacks
    plain_model = self.class.const_get(:WithoutCallbacks)
    plain_record = plain_model.create(self.attributes)
    self.id = plain_record.id
    self.created_at = Time.zone.now
    self.updated_at = Time.zone.now
    @new_record = false
    true
  end

  def update_without_callbacks
    update_attributes = attributes.except(self.class.primary_key)
    update_attributes['created_at'] = Time.zone.now
    update_attributes['updated_at'] = Time.zone.now
    update_columns update_attributes
  end

end

in any model:

include SaveWithoutCallbacks

then you can:

record.save_without_callbacks

or

Model::WithoutCallbacks.create(attributes)
Blackandwhite answered 12/8, 2016 at 15:5 Comment(0)
A
2

When I need full control over the callback, I create another attribute that is used as a switch. Simple and effective:

Model:

class MyModel < ActiveRecord::Base
  before_save :do_stuff, unless: :skip_do_stuff_callback
  attr_accessor :skip_do_stuff_callback

  def do_stuff
    puts 'do stuff callback'
  end
end

Test:

m = MyModel.new()

# Fire callbacks
m.save

# Without firing callbacks
m.skip_do_stuff_callback = true
m.save

# Fire callbacks again
m.skip_do_stuff_callback = false
m.save
Amati answered 14/4, 2014 at 8:39 Comment(0)
C
1

None of these points to without_callbacks plugin that just does what you need ...

class MyModel < ActiveRecord::Base
  before_save :do_something_before_save

  def after_save
    raise RuntimeError, "after_save called"
  end

  def do_something_before_save
    raise RuntimeError, "do_something_before_save called"
  end
end

o = MyModel.new
MyModel.without_callbacks(:before_save, :after_save) do
  o.save # no exceptions raised
end

http://github.com/cjbottaro/without_callbacks works with Rails 2.x

Catie answered 15/10, 2010 at 15:26 Comment(0)
I
1
# for rails 3
  if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
    def update_without_callbacks
      attributes_with_values = arel_attributes_values(false, false, attribute_names)
      return false if attributes_with_values.empty?
      self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
    end
  end
Insatiate answered 17/12, 2010 at 7:21 Comment(0)
D
1

You can use sneaky-save gem: https://rubygems.org/gems/sneaky-save.

Note this cannot help in saving associations along without validations. It throws error 'created_at cannot be null' as it directly inserts the sql query unlike a model. To implement this, we need to update all auto generated columns of db.

Demibastion answered 26/10, 2011 at 3:47 Comment(0)
S
1

I wrote a plugin that implements update_without_callbacks in Rails 3:

http://github.com/dball/skip_activerecord_callbacks

The right solution, I think, is to rewrite your models to avoid callbacks in the first place, but if that's impractical in the near term, this plugin may help.

Senlac answered 13/4, 2012 at 14:59 Comment(0)
A
1

If you are using Rails 2. You could use SQL query for updating your column without running callbacks and validations.

YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")

I think it should work in any rails versions.

Aesculapian answered 31/8, 2012 at 11:31 Comment(0)
D
1

For creating test data in Rails you use this hack:

record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call

https://coderwall.com/p/y3yp2q/edit

Daye answered 29/5, 2014 at 16:15 Comment(0)
N
1

For custom callbacks, use an attr_accessor and an unless in the callback.

Define your model as follows:

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

And then if you need to save the record without hitting the after_save callbacks you defined, set the skip_after_save_callbacks virtual attribute to true.

person.skip_after_save_callbacks #=> nil
person.save # By default, this *will* call `do_something` after saving.

person.skip_after_save_callbacks = true
person.save # This *will not* call `do_something` after saving.

person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.
Neldanelia answered 16/1, 2020 at 20:16 Comment(0)
B
0

Why would you want to be able to do this in development? Surely this will mean you are building your application with invalid data and as such it will behave strangely and not as you expect in production.

If you want to populate your dev db with data a better approach would be to build a rake task that used the faker gem to build valid data and import it into the db creating as many or few records as you desire, but if you are heel bent on it and have a good reason I guess that update_without_callbacks and create_without_callbacks will work fine, but when you are trying to bend rails to your will, ask yourself you have a good reason and if what you are doing is really a good idea.

Bearden answered 11/3, 2009 at 9:18 Comment(2)
I'm not trying to save without validations, just without callbacks. My app is using callbacks to write some static HTML to the filesystem (sort of like a CMS). I don't want to do that while loading dev data.Haldis
Was just a thought, I guess whenever in the past I've seen this kind of question it's trying to get around stuff for bad reasons.Bearden
A
0

Another way would be to use validation hooks instead of callbacks. For example:

class Person < ActiveRecord::Base
  validate_on_create :do_something
  def do_something
    "something clever goes here"
  end
end

That way you can get the do_something by default, but you can easily override it with:

@person = Person.new
@person.save(false)
Arizona answered 14/8, 2009 at 21:44 Comment(1)
This seems like a bad idea - you should use things for their intended purpose. The last thing you want is your validations to have side effects.Remontant
A
0

One option is to have a separate model for such manipulations, using the same table:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Same approach might make things easier for bypassing validations)

Stephan

Africah answered 6/6, 2011 at 19:0 Comment(0)
P
0

Something that should work with all versions of ActiveRecord without depending on options or activerecord methods that may or may not exist.

module PlainModel
  def self.included(base)
    plainclass = Class.new(ActiveRecord::Base) do
      self.table_name = base.table_name
    end
    base.const_set(:Plain, plainclass)
  end
end


# usage
class User < ActiveRecord::Base
  include PlainModel

  validates_presence_of :email
end

User.create(email: "")        # fail due to validation
User::Plain.create(email: "") # success. no validation, no callbacks

user = User::Plain.find(1)
user.email = ""
user.save

TLDR: use a "different activerecord model" over the same table

Photocopy answered 7/3, 2016 at 5:17 Comment(0)
T
0

I faced the same problem when I wanted to run a Rake Task but without running the callbacks for every record I was saving. This worked for me (Rails 5), and it must work for almost every version of Rails:

class MyModel < ApplicationRecord
  attr_accessor :skip_callbacks
  before_create :callback1
  before_update :callback2
  before_destroy :callback3

  private
  def callback1
    return true if @skip_callbacks
    puts "Runs callback1"
    # Your code
  end
  def callback2
    return true if @skip_callbacks
    puts "Runs callback2"
    # Your code
  end
  # Same for callback3 and so on....
end

The way it works is that it just returns true in the first line of the method it skip_callbacks is true, so it doesn't run the rest of the code in the method. To skip callbacks you just need to set skip_callbacks to true before saving, creating, destroying:

rec = MyModel.new() # Or Mymodel.find()
rec.skip_callbacks = true
rec.save
Toast answered 14/8, 2020 at 0:24 Comment(0)
S
0

In Rails 7, we can do something like this to skip all callbacks -

person.save!(callbacks: false)
Sink answered 27/11, 2023 at 9:18 Comment(0)
W
-8

Not the cleanest way, but you could wrap the callback code in a condition that checks the Rails environment.

if Rails.env == 'production'
  ...
Warwickshire answered 10/3, 2009 at 23:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.