开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜