【问题标题】:Rails: using sidekiq to asynchronously create records that mount a carrierwave uploaderRails:使用 sidekiq 异步创建挂载载波上传器的记录
【发布时间】:2014-10-15 03:48:28
【问题描述】:

我正在尝试为Post(创建记录以及上传和处理)创建关联图像(PostPhoto),并通过 SideKiq 将它们移动到后台进程。以下是模型:

class Post < ActiveRecord::Base
  has_many :post_photos
  accepts_nested_attributes_for :post_photos, allow_destroy: true

class PostPhoto < ActiveRecord::Base
  mount_uploader :photo, PhotoUploader
  belongs_to :post

这是我的上传者:

class PhotoUploader < CarrierWave::Uploader::Base

  include CarrierWave::RMagick
  storage :fog

  include CarrierWave::MimeTypes
  process :set_content_type

最初,在帖子保存后,我在我的帖子控制器中运行了这个:

params[:post_photos]['photo'].each do |p|
    @post.post_photos.create!(:photo => p, :post_id => @post.id)
end

这很好用。现在我正在尝试这样做:

params[:post_photos]['photo'].each do |p|
    PostPhotoWorker.perform_async p, @post.id
end

这是我的 PostPhotoWorker

class PostPhotoWorker
  include Sidekiq::Worker
  sidekiq_options queue: "high"
  sidekiq_options retry: false

  def perform(p, post_id)
    post = Post.find_by_id(post_id);
    post.post_photos.create!(:photo => p, :post_id => post_id)
  end
end

作业失败。在我看到的 sidekiq 日志中:

2014-08-21T12:23:09.583Z 24655 TID-ovhl6hlbw 警告:没有将 nil 隐式转换为字符串

我知道post_id 和照片临时文件p 都已正确传递,并且post = Post.find_by_id(post_id); 正在查找该帖子。但似乎在创建操作中有些东西失败了。是否有某些原因导致相关照片记录的创建以及上传和处理在后台进程中失败但作为主要进程的一部分成功? sidekiq 日志中没有堆栈跟踪,所以我无法弄清楚这个问题是在哪里出现的。

知道是什么原因造成的吗?

仅供参考

我不能使用 Carrierwave Backgrounder,因为我在 Heroku 上进行生产,并且 Carrierwave Backgrounder 不支持在 Heroku 上存储图像的背景,因为 Ephemeral File System(这是我想要后台处理的主要原因首先)。我不想使用 Carrierwave Direct,因为它似乎需要为帖子和图像提供单独的表单,而且我还没有准备好重构我的代码来支持它,至少在我找到一种方法之前不会这样做优雅。

我有一种感觉,将整个照片临时文件作为值传递给 redis 是一个坏主意。但是翻阅redis的文档,好像是should support it

【问题讨论】:

    标签: ruby-on-rails heroku carrierwave sidekiq


    【解决方案1】:

    首先使用载波直接。它将图像从 Web 客户端直接上传到 S3 等。这解决了有关文件上传的所有 herokus 问题,如果它是临时文件结构,或者它如何在大文件上传时超时。

    https://github.com/dwilkie/carrierwave_direct

    您可以按照正常设置进行操作,但不是将详细信息保存到实际模型中,而是将上传信息放入临时属性中,然后 after_commit 将该信息用于创建后台任务,或者在控制器中action 创建后台任务。

    PhotoProcessorWorker.perform_async(params[:key], @object.id)
    

    然后才是真正的逻辑。您获取对象,设置上传密钥,通过执行 remote_photo_url 强制下载对象,并在保存照片时发生处理。

    class PhotoProcessorWorker
      include Sidekiq::Worker
      sidekiq_options :queue => :default
    
      def perform(key, obj_id)
    
        obj = Object.where(:id => org_id).first
        obj.photo.key = key
        obj.remote_photo_url = obj.photo.direct_fog_url(:with_path => true)
        obj.save
    
      end
    end
    

    编辑:抱歉混淆了仅供参考的部分,这是您可以多次执行此操作的方法,它需要多个请求,但应该可以。

    @post.post_photos.each do |pp|
    
      = direct_upload_form_for pp.photo, :remote => true, :class => "photo_upload" do |f|
        .avatar.form-group
          = f.label :photo
          = f.file_field :photo, :class => "btn btn-default has-image-preview"
    
    
    #submit_all_images.button_class
    

    有了这个,您将拥有一段 javascript,在点击#submit_all_images 后提交每个表单。每次上传都会单独进行,但这很好,因为在前台进行的唯一工作就是后台任务的排队。

    另外,如果您同时创建帖子,您可以通过将帖子表单远程,创建成功后返回 id,然后在确认后以与上述相同的方式提交所有照片上传来完成这项工作。

    【讨论】:

    • 如果我能让它正确而优雅地工作,我很想把它制作成CD。但是,现在我正在使用一个支持多个嵌套 post_photos 的表单。我看到的 Carrierwave Direct 的每个示例(包括 railscasts)都表明您需要一个用于关联模型 PostPhoto 的表单和一个用于主要模型 Post 的不同表单,因此需要一个两步上传过程,我会喜欢避免。我已经看到很多其他人试图做到这一点,但还没有看到成功的实施。你知道怎么做我所描述的吗?
    • Err 对不起,我猜我的大脑过滤掉了 FYI。你可以做的是每个对象都有一个表单,没有提交按钮。然后在表单之外有一个按钮,它将遍历每个表单并将它们作为单独的操作提交。将更新答案。
    • 谢谢。我敢肯定,我可能很快就会这样做。但是随着我的发布期限即将到来,除非我能弄清楚如何让我的“快速而肮脏”的解决方案发挥作用,否则我的后备方案是在我的 Web 服务器完成所有工作时依赖于无休止的“上传/处理”指标。很想知道为什么我目前的解决方案不起作用……
    猜你喜欢
    • 2015-06-08
    • 1970-01-01
    • 2016-07-22
    • 1970-01-01
    • 2015-10-13
    • 2017-03-09
    • 1970-01-01
    • 1970-01-01
    • 2017-04-21
    相关资源
    最近更新 更多