Skip to content

Instantly share code, notes, and snippets.

@owen2345
Last active April 6, 2022 15:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save owen2345/ca4cabca74e2ceea1589b261dbd9e5d8 to your computer and use it in GitHub Desktop.
Save owen2345/ca4cabca74e2ceea1589b261dbd9e5d8 to your computer and use it in GitHub Desktop.
Rails ActiveStorage: Add the ability to upload photos in background
class DelayedUploaderJob < ApplicationJob
queue_as :default
attr_reader :model, :attr_name
def perform(model_klass, id, attr_name)
@model = model_klass.constantize.find(id)
@attr_name = attr_name
upload_photo if tmp_file_data
end
private
def upload_photo
data = { io: file_io, filename: tmp_file_data['name'], content_type: tmp_file_data['content_type'] }
model.send(attr_name).attach(data)
remove_tmp_data
file_io.close
rescue => e
Rails.logger.error("#{self.class.name}-> Failed uploading files: #{e.message}")
end
def remove_tmp_data
model[attr_tmp_data] = nil
model.save!
end
def file_io
@file_io ||= StringIO.new(Base64.decode64(tmp_file_data['body']))
end
def tmp_file_data
@tmp_file_data ||= model[attr_tmp_data]
end
def attr_tmp_data
"tmp_#{attr_name}_data"
end
end
# frozen_string_literal: true
module DelayedFileConcern
extend ActiveSupport::Concern
included do
def self.delayed_attach(attr_name, required: true)
tmp_name = "tmp_#{attr_name}".to_sym
attr_accessor tmp_name
before_validation { parse_tmp_delayed_file(tmp_name) if send(tmp_name) }
validates tmp_name, presence: true, unless: ->(o) { o.send(attr_name).blob } if required
validates attr_name, presence: true, unless: -> (o) { o.send(tmp_name) } if required
after_save_commit do
DelayedUploaderJob.perform_later(self.class.name, id, attr_name) if send(tmp_name)
send("#{tmp_name}=", nil) if send(tmp_name)
end
end
end
def parse_tmp_delayed_file(tmp_name)
file = send(tmp_name)
body = Base64.encode64(file.read)
if file.respond_to?(:original_filename) # fileUploaded
content_type = file.content_type
name = file.original_filename
else # File.open
content_type = Marcel::MimeType.for(file)
name = File.basename(file.path)
end
self["#{tmp_name}_data"] = { 'body' => body, 'name' => name, 'content_type' => content_type }
end
end
# == Schema Information
#
# Table name: photos
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# tmp_file_data :json <<<< THIS IS REQUIRED WHERE TO SAVE TMP_DATA (tmp_<attr_name>_data)
class Photo < ApplicationRecord
include DelayedFileConcern
has_one_attached :file
delayed_attach(:file, required: true)
end
# Sample:
photo = Photo.create!(tmp_file: File.open('/my_image.png')) # >> Background job should be scheduled
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment