可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I need to calculate values when saving a model in Rails. So I call calculate_averages
as a callback for a Survey
class:
before_save :calculate_averages
However, occasionally (and initially I have 10k records that need this operation) I need to manually update all the averages for every record. No problem, I have code like the following:
Survey.all.each do |survey|
survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
#and some more averages...
survey.save!
end
Before even running this code, I'm worried the calculate_averages
is going to get called and duplicate this and probably even cause some problems with the way I'm doing things. Ok, so then I think, well I'll just do nothing and let calculate_averages
get called and do its thing. Problem there is, first, is there a way to force callbacks to get called even if you made no changes to the record?
Secondly, the way averages are calculated it's far more efficient to simply not let the callbacks get called at all and do the averages for everything all at once. Is this possible to not let callbacks get called?
回答1:
I believe what you are asking for can be achieved with ActiveSupport::Callbacks
. Have a look at set_callback
and skip_callback
.
In order to "force callbacks to get called even if you made no changes to the record", you need to register the callback to some event e.g. save, validate etc.
.
set_callback :save, :before, :my_before_save_callback
To skip the before_save
callback, you would do:
Survey.skip_callback(:save, :before, :calculate_average).
Please reference the linked ActiveSupport::Callbacks
on other supported options such as conditions and blocks to set_callback
and skip_callback
.
回答2:
To disable en-mass callbacks use...
Survey.skip_callback(:save, :before, :calculate_averages)
Then to enable them...
Survey.set_callback(:save, :before, :calculate_average)
This skips/sets for all instances.
回答3:
update_column
is an ActiveRecord
function which does not run any callbacks, and it also does not run validation.
回答4:
If you want to conditionally skip callbacks after checking for each survey you can write your custom method.
For ex.
`
before_save :calculate_averages, if: Proc.new{ |survey| !survey.skip_callback }
`
`
def skip_callback(value = false)
@skip_callback = @skip_callback ? @skip_callback : value
end
`
- Script to update surveys-
`
Survey.all.each do |survey|
survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
#and some more averages...
survey.skip_callback(true)
survey.save!
end
`
Its kinda hack but hope will work for you.
回答5:
hopefully this is what you're looking for.
https://stackoverflow.com/a/6587546/2238259
For your second issue, I suspect it would be better to inspect when this calculation needs to happen, it would be best if it could be handled in batch at a specified time where network traffic is at its trough.
EDIT: Woops. I actually found 2 links but lost the first one, apparently. Hopefully you have it fixed.
回答6:
For Rails 3 ActiveSupport::Callbacks
gives you the necessary control. You can reset_callbacks
en-masse, or use skip_callback
to disable judiciously like this:
Vote.skip_callback(:save, :after, :add_points_to_user)
…after which you can operate on Vote instances with :add_points_to_user
inhibited
回答7:
Doesn't work for Rails 5
Survey.skip_callback(:save, :before, :calculate_average)
Works for Rails 5
Survey.skip_callback(:save, :before, :calculate_average, raise: false)
https://github.com/thoughtbot/factory_bot/issues/931