How can I update attributes in after_save without causing a recursion in rails 2.3?
I've got a model which has a video attached with Paperclip. After it saves I use the saved video to generate a thumbnail. I need to do this after every save, even when a new video hasn't been uploaded, because the user can change the time where the thumbnail is captured.
I am currently using after_post_process to do this, but it will only generate the thumbnail when uploading a file (this is a callback which is part of Paperclip).
I would ideally use an after_save callback like this:
after_save :save_thumbnail
def save_thumbnail
#gen开发者_JS百科erate thumbnail...
self.update_attributes(
:thumbnail_file_name => File.basename(thumb),
:thumbnail_content_type => 'image/jpeg'
)
end
Unfortunately update_attributes calls save, which then calls the before_save callback causing an infinite loop. Is there a simple way to circumvent this behaviour?
Any update_attribute
in an after_save
callback will cause recursion, in Rails3+.
What should be done is:
after_save :updater!
# Awesome Ruby code
# ...
# ...
private
def updater!
self.update_column(:column_name, new_value) # This will skip validation gracefully.
end
Here is some documentation about it: https://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks
You could wrap it in a conditional, something like:
def save_thumbnail
if File.basename(thumb) != thumbnail_file_name
self.update_attributes(
:thumbnail_file_name => File.basename(thumb),
:thumbnail_content_type => 'image/jpeg'
)
end
end
That way it would only run once.
Rails 2:
Model.send(:create_without_callbacks)
Model.send(:update_without_callbacks)
Rails 3:
Vote.skip_callback(:save, :after, :add_points_to_user)
See this question:
How to skip ActiveRecord callbacks?
You can(and should) check if you actually need to update the thumbnail:
after_save :save_thumbnail
def save_thumbnail
if capture_time_changed? #assuming capture_time contains time when the thumbnail has to be captured
#generate thumbnail...
self.update_attributes(
:thumbnail_file_name => File.basename(thumb),
:thumbnail_content_type => 'image/jpeg'
)
end
end
Here you can read more about 'dirty' attributes: http://apidock.com/rails/ActiveRecord/Dirty
Although I'm not sure if it still can see the attribute changes in after_save. You can use a member variable to indicate changes in case it can't.
You can run it as a before_save
instead.
After it has been validated, update the thumbnail, then let it go on to be saved, but just use the assignment methods
before_save :save_thumbnail
def save_thumbnail
self.thumbnail_file_name = File.basename(thumb),
self.thumbnail_content_type = 'image/jpeg'
end
Since that won't call save, you wont recurse, but it will immediately be saved after the method exits.
Something like that should work, unless there is an explicit reason you need it in after save.
Since you are not updating a separate object, but the same one, this will save you a database call as well. This is How i do timestamps and things like that too.
精彩评论