尝试在after_update回调中重新处理图片会导致无限循环

时间:2013-12-29 00:25:31

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

我有这个模型用于附加到另一个模型(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_update doesn't clear ActiveRecord::Dirty属性(例如self.crop_x_changed?),以使其对开发人员更有用,但是当我致电{图片上的{1}} Paperclip本身会对我的模型进行更改并调用reprocess!,这会导致save回调再次被触发。由于after_updateself.crop_x_changed?从此链的开头仍然是self.crop_y_changed?(这是因为它们似乎只在true运行后才被清除),这会导致我的代码再次致电after_update,继续这无休止的循环。

我已尝试过许多这类代码的类似变体,但似乎无论是循环还是失败。有没有办法让我的代码正确执行这个功能?

1 个答案:

答案 0 :(得分:0)

感谢朋友的帮助,我设法找到了一种方法让它发挥作用。这就是我解决它的方法:

我在WorkPicture::Recropper中创建了一个新课程app/models/work_picture/recropper.rb(为了进行比较,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

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

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