【问题标题】:Trying to reprocess picture in after_update callback leads to infinite loop尝试在 after_update 回调中重新处理图片会导致无限循环
【发布时间】:2013-12-29 00:25:30
【问题描述】:

我有这个模型的图片附加到另一个模型(Work):

class WorkPicture < ActiveRecord::Base
  after_initialize :default_values
  after_update :reprocess_picture, if: :cropping_changed?

  belongs_to :work, inverse_of: :pictures

  has_attached_file    :picture, styles: lambda { |attachment| { big: { geometry: "1500x>", format: :png },
                                                                 thumb: attachment.instance.thumb_options } },
                                 url:             "/pictures/:hash.:extension",
                                 hash_secret:     ENV["PAPERCLIP_SECRET_KEY"]
  validates_attachment :picture, presence:        true,
                                 content_type:    { content_type: /image/ }

  scope :illustrative, -> { where(is_cover: false) }

  # Default values
  def default_values
    self.is_cover = false if self.is_cover.nil?
    self.crop_x = 0 if self.crop_x.nil?
    self.crop_y = 0 if self.crop_y.nil?
  end

  def thumb_options
    return { geometry: "", format: :png, convert_options: "-crop 360x222+#{crop_x.to_i}+#{crop_y.to_i}" }
  end

  private
    def cropping_changed?
      self.crop_x_changed? || self.crop_y_changed?
    end

    def reprocess_picture
      picture.reprocess!
    end
end

这些图片有双重用途:如果将is_cover 设置为false,它们将显示在相关作品的图库页面中。如果 is_cover 设置为 true,它们将被裁剪并用作列出所有作品的页面中相关作品的“图标”。

在工作编辑页面上,您可以更改裁剪的 x 和 y 坐标。这意味着一旦新坐标进入,图像应该被重新处理,以便显示正确的区域。

为此,我在模型上设置了两个属性crop_xcrop_y,它们存储了裁剪窗口的位置。通过after_update,我首先检查这些坐标是否已更改,如果是,我告诉Paperclip 重新处理图像以正确裁剪。

运行此代码时,执行进入无限循环。我想我已经确定了造成这种情况的原因:after_updatedoesn't clearActiveRecord::Dirty 属性(例如self.crop_x_changed?)使其对开发人员更有用,但是当我在 Paperclip 本身制作的图片上调用reprocess! 时更改我的模型并调用save,这反过来又会导致再次触发after_update 回调。由于self.crop_x_changed?self.crop_y_changed? 从该链的开头仍然是true(这是因为它们似乎只有在after_update 运行后才被清除),这导致我的代码再次调用reprocess!,这继续这个无限循环。

我已经尝试了许多类似的代码变体,但似乎都无休止地循环或失败。有没有办法让我的代码正确执行这个功能?

【问题讨论】:

    标签: ruby-on-rails ruby callback paperclip infinite-loop


    【解决方案1】:

    多亏了朋友的帮助,我设法找到了让它工作的方法。以下是我的解决方法:

    我在app/models/work_picture/recropper.rb 中创建了一个新类WorkPicture::Recropper(作为比较,work_picture.rb 文件在app/models/ 中)。这是它的代码:

    class WorkPicture::Recropper
      def initialize work_picture
        @work_picture = work_picture
        @crop_x = @work_picture.crop_x
        @crop_y = @work_picture.crop_y
      end
    
      def recrop
        @work_picture.reload
    
        if @work_picture.crop_x != @crop_x || @work_picture.crop_y != @crop_y
          @work_picture.picture.reprocess!
        end
      end
    end
    

    我将控制器的更新操作更改为使用WorkPicture::Recropper

    class WorksController < ApplicationController
    
      # ... Other actions ...
    
      def update
        @work = Work.find(params[:id])
        recropper = WorkPicture::Recropper.new(@work.cover_picture)
    
        if @work.update_attributes(work_params)
          recropper.recrop
          redirect_to @work
        else
          render :edit
        end
      end
    
    private
    
      def work_params
        params.require(:work).permit(:name, :description, :category_id, pictures_attributes: [:crop_x, :crop_y, :is_cover, :picture, :id])
      end
    end
    

    简而言之,我所做的就是在更新其属性之前通过将封面图片传递给 recropper 实例,以便它有机会存储旧的裁剪坐标。更新属性后,recrop 方法会重新加载模型并将属性的新值与其存储的值进行比较。如果它们发生了变化,它会重新处理图片。

    此解决方案有效,但可能不是最干净的。如果有人有更好的想法,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-01
      相关资源
      最近更新 更多