使用carrierwave将一个图像上传到多个存储位置

时间:2012-10-17 09:35:23

标签: ruby-on-rails file-upload carrierwave

我希望能够将一个图像上传到两个不同的位置:一个位置在(服务器的)本地文件系统上,另一个位置是Amazon S3(Amazon S3位置是可选的)。

我目前的环境是Rails 3.2.8,Ruby 1.9.3,Carrierwave用于上传文件。

我使用以下方法取得了一些成功:

模型

class Image < ActiveRecord:Base

attt_accessor :remote

before_save :configure_for_remote

mount_uploader :image, ImageUploader #stores images locally
mount_uploader :image_remote, ImageRemoteUploader #store images on S3

def configure_for_remote
  if self.remote=="1"
    self.image_remote = self.image.dup
  end
end

end

相关视图表单字段(简单表单语法)

<p><%= f.input :image, as: :file %></p>
<p><%= f.input :remote, as: :boolean %></p>

用户选中表单中的“远程”复选框,然后选择要上传的图像。 before_save回调将图像副本存储到image_remote中,文件由各自的上传者处理,我得到了我想要的结果。

但是,当我想要更新该字段时,我开始遇到问题。例如,如果用户选择首先在本地上载文件而不是S3(不检查远程复选框),则稍后返回到表单并检查远程复选框。在这种情况下,before_save回调不会运行,因为没有更改真正的活动记录列(只有远程标志)。我尝试使用before_validation,但是这不起作用(image_remote上传器在image_remote列中存储了正确的文件名,但图像没有上传到S3)。显然,before_validation和before_save之间正在发生变化(图像属性正在被转换为上传者?)但我似乎无法弄清楚为什么这不起作用。

说了这么多,我认为我使用dup的做法有点像黑客,我希望有人能以更优雅的方式告诉我达到我的目标。

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

我是要解决这个问题,虽然我仍然不确定它是否是最优雅的解决方案。

首先,我在我的问题中提到,当我使用before_validation回调注册config_for_remote_upload时,文件未上传到S3,但是填充了image_remote列。经过进一步检查,情况更糟。在before_validation回调中初始化image_remote上传器时,S3存储桶上的所有文件都被删除了!我复制了几次。我只测试了在上传时将store_dir设置为nil,从而将文件放在存储桶的根目录。

在before_save回调期间初始化image_remote列没有此问题。为了强制记录保存(它不会保存,因为只更改了非db列属性)我添加了一个更改记录的update_at字段的before_validation。

before_validation: :change_record_updated_at
...
def change_record_updated_at
   self.update_at=Time.current
end

我也不再使用dup,不是因为它不起作用,而是因为我不知道它为什么有效。相反,我为该文件创建了一个StringIO对象,并将其分配给image_remote列。

  def config_for_remote_upload
    if self.remote.to_i==1 
      #self.image_remote = self.image.dup
      #this will open the file as binary
      img_binary = File.open(self.image.file.path){ |i| i.read }
      img_encoded = Base64.encode64(img_binary)
      io = FilelessIO.new(Base64.decode64(img_encoded))
      io.original_filename = self.image.file.original_filename
      self.image_remote = io
    elsif self.remote.to_i==0
      #delete remote image and clear field
      self.remove_image_remote = true
     end
 end

有关FilelessIO(带有original_filename的StringIO)的详细信息,请参阅here

使用此配置,初始上传后,文件可以上传到第二个存储位置(在我的情况下为S3)。

希望这有助于其他人。