How can I avoid running ActiveRecord callbacks?

匿名 (未验证) 提交于 2019-12-03 02:03:01

问题:

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.

回答1:

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!



回答2:

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 


回答3:


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 

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



回答4:

rails 3:

MyModel.send("_#{symbol}_callbacks") # list   MyModel.reset_callbacks symbol # reset 


回答5:

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.



回答6:

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 

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 ) 


回答7:

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.



回答8:

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 


回答9:

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.



回答10:

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



回答11:

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? } 


回答12:

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).



回答13:

# 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 


回答14:

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

class MyModel 

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



回答15:

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.



回答16:

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.



回答17:

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

Model:

class MyModel 

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 


回答18:

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.



回答19:

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) 


回答20:

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.



回答21:

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

class NoCallbacksModel 

(Same approach might make things easier for bypassing validations)

Stephan



回答22:

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

class Person 

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

@person = Person.new @person.save(false) 


回答23:

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



回答24:

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 

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



回答25:

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

if Rails.env == 'production'   ... 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!