Как я могу обновить атрибуты в after_save, не вызывая рекурсию в rails 2.3? - PullRequest
13 голосов
/ 13 июля 2011

У меня есть модель, к которой прикреплено видео с Скрепка . После сохранения я использую сохраненное видео для создания эскиза. Мне нужно делать это после каждого сохранения, даже если новое видео не было загружено, поскольку пользователь может изменить время захвата миниатюры.

В настоящее время я использую after_post_process для этого, но он генерирует только миниатюру при загрузке файла (это обратный вызов, который является частью Paperclip).

В идеале я бы использовал обратный вызов after_save, например:

after_save :save_thumbnail
def save_thumbnail
  #generate thumbnail...
  self.update_attributes(
    :thumbnail_file_name => File.basename(thumb), 
    :thumbnail_content_type => 'image/jpeg'
  )
end

К сожалению, update_attributes вызывает save, который затем вызывает обратный вызов before_save, вызывая бесконечный цикл. Есть ли простой способ обойти это поведение?

Ответы [ 5 ]

9 голосов
/ 28 февраля 2014

Любой update_attribute в обратном вызове after_save вызовет рекурсию в Rails3 +.Что нужно сделать, это:

after_save :updater!
# Awesome Ruby code
# ...
# ...

private

  def updater!
    self.update_column(:column_name, new_value) # This will skip validation gracefully.
  end

Вот некоторая документация об этом: https://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks

9 голосов
/ 13 июля 2011

Вы можете обернуть его в условное выражение, что-то вроде:

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

Таким образом, он будет работать только один раз.

8 голосов
/ 13 июля 2011

Рельсы 2:

Model.send(:create_without_callbacks)
Model.send(:update_without_callbacks)

Рельсы 3:

Vote.skip_callback(:save, :after, :add_points_to_user)

Смотри этот вопрос:

Как пропустить обратные вызовы ActiveRecord?

2 голосов
/ 13 июля 2011

Вы можете (и должны) проверить, действительно ли вам нужно обновить миниатюру:

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

Здесь вы можете прочитать больше об атрибутах 'dirty': http://apidock.com/rails/ActiveRecord/Dirty

Хотя яЯ не уверен, что он все еще может видеть изменения атрибута в after_save.Вы можете использовать переменную-член, чтобы указать изменения, если она не может.

0 голосов
/ 13 июля 2011

Вместо этого вы можете запустить его как before_save.

После того, как он будет проверен, обновите миниатюру, затем сохраните ее, но просто используйте методы присвоения

before_save :save_thumbnail
def save_thumbnail
  self.thumbnail_file_name = File.basename(thumb), 
  self.thumbnail_content_type = 'image/jpeg'
end

Так как это не вызовет save, вы не будете рекурсировать, но оно будет сразу же сохранено после выхода из метода.

Что-то подобное должно работать, если нет явной причины, по которой вам это нужно послеsave.

Поскольку вы обновляете не отдельный объект, а тот же, это также спасет вас от вызова базы данных.Вот как я делаю метки времени и тому подобное.

...