Jack Pope
Jack Pope

Reputation: 164

Carrierwave backgrounder and Heroku - tmp issue

I am uploading images to my Picture model. I am using Carrierwave, Carrierwave-Backgrounder, Carrierwave-FTP for SFTP, and DelayedJob.

The goal is to have the images processed and stored via SFTP in the background. My approach is working fine in my development environment, but fails in production on Heroku.

I understand this is because Heroku's tmp directory is wiped after the create action, but I am looking for a way around that.

Is it possible to use S3/Fog to store the tmp files? Then access them from there with Carrierwavebackgrounder. Or is there a better approach?

Uploader:

# encoding: utf-8

class ImageUploader < CarrierWave::Uploader::Base

  def cache_dir
    "#{Rails.root}/tmp/uploads"
  end

  include ::CarrierWave::Backgrounder::Delay
  include CarrierWave::MiniMagick

  storage :sftp

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    # "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
    "/"
  end

  def rotate(degree)
    manipulate! do |img|
      img.rotate(degree)
      img
    end
  end

  version :tiny, :if => :initial? do
    process :resize_to_fit => [75, 75]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-0.jpg"
    end
  end

  version :small, :if => :initial? do
    process :resize_to_fit => [200, 200]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-1.jpg"
    end
  end

  version :medium, :if => :initial? do
    process :resize_to_fit => [500, 500]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-2T.jpg"
    end
  end

  version :large, :if => :initial? do
    process :resize_to_fit => [1000, 1000]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-2.jpg"
    end
  end

  version :medium_alt, :if => :initial? do
    process :resize_to_fit => [100, 50]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-2S.jpg"
    end
  end

  version :medium_additional, :if => :additional? do
    process :resize_to_fit => [500, 500]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-#{model.version_number}" + "T.jpg"
    end
  end

  version :large_additional, :if => :additional? do
    process :resize_to_fit => [1000, 1000]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-#{model.version_number}" + ".jpg"
    end
  end

  version :medium_alt_additional, :if => :additional? do
    process :resize_to_fit => [100, 50]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-#{model.version_number}" + "S.jpg"
    end
  end

  version :ebay_initial, :if => :initial? do
    process :resize_to_fit => [1600, 1600]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-ebay.jpg"
    end
  end

  version :ebay_additional, :if => :additional? do
    process :resize_to_fit => [1600, 1600]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-#{model.version_number}" + "-ebay.jpg"
    end
  end

  version :display_initial, :if => :initial? do
    process :resize_to_fit => [200, 1000]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-display.jpg"
    end
  end

  version :display_additional, :if => :additional? do
    process :resize_to_fit => [200, 1000]
    def full_filename(for_file = model.image.file)
      model.product_code.to_s + "-#{model.version_number}" + "-display.jpg"
    end
  end

  protected

  def initial?(new_file)
    original_filename.to_i == 0
  end

  def additional?(new_file)
    original_filename.to_i > 0
  end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # Currently inactive.
  # def extension_white_list
  #   %w(jpg jpeg)
  # end

end

Picture.rb:

class Picture < ActiveRecord::Base

  mount_uploader :image, ImageUploader

  store_in_background :image

  belongs_to :item
end

Controller upload:

def import_pictures(pictures, item)
    pictures['image'].each do |a|
      if a.original_filename.downcase.include?('.jpg') || a.original_filename.downcase.include?('.jpeg')
        count = @item.pictures.count
        photo_number = count + 2
        a.original_filename = count.to_s
        @picture = @item.pictures.create(:image => a, :item_id => item.id, :product_code => item.product_code, :version_number => photo_number)
      else
        flash[:error] = "Only .jpg and .jpeg files are allowed. Try again."
        redirect_to edit_item_path(@item)
      end
    end
  end

Picture Schema:

create_table "pictures", force: true do |t|
    t.integer  "item_id"
    t.string   "image"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "product_code"
    t.integer  "version_number"
    t.string   "image_tmp"
  end

Heroku Logs:

2015-03-19T20:27:56.804863+00:00 app[worker.1]: 2015-03-19T20:27:56+0000: [Worker(host:f84dfd71-bd05-4a44-ac83-1d1818d0bfe3 pid:3)] Job CarrierWave::Workers::StoreAsset (id=36) FAILED (5 prior attempts) with Errno::ENOENT: No such file or directory - /app/tmp/uploads/1426795849-9-9664/3

Upvotes: 1

Views: 593

Answers (1)

Phil
Phil

Reputation: 145

At the moment you can't use store_in_background on heroku. From the README:

# Heroku may deploy workers on separate servers from where your dyno cached the files.
#
# IMPORTANT: Only use this method if you have full control over your tmp storage directory.

So you have to store directly (without backgrounder) and can process_in_background delayed.

Upvotes: 0

Related Questions