Backgrounding
The backgrounding
plugin enables you to move promoting and
deleting of files into background jobs. This is especially useful if you're
processing derivatives and storing files to a remote storage service.
Shrine.plugin :backgrounding # load the plugin globally
Setup
Define background jobs that will promote and destroy attachments:
class PromoteJob
include Sidekiq::Worker
def perform(attacher_class, record_class, record_id, name, file_data)
attacher_class = Object.const_get(attacher_class)
record = Object.const_get(record_class).find(record_id) # if using Active Record
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
attacher.atomic_promote
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
# attachment has changed or record has been deleted, nothing to do
end
end
class DestroyJob
include Sidekiq::Worker
def perform(attacher_class, data)
attacher_class = Object.const_get(attacher_class)
attacher = attacher_class.from_data(data)
attacher.destroy
end
end
Then, in your initializer, you can configure all uploaders to use these jobs:
Shrine::Attacher.promote_block do
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name.to_s, file_data)
end
Shrine::Attacher.destroy_block do
DestroyJob.perform_async(self.class.name, data)
end
Alternatively, you can setup backgrounding only for specific uploaders:
class MyUploader < Shrine
Attacher.promote_block do
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name.to_s, file_data)
end
Attacher.destroy_block do
DestroyJob.perform_async(self.class.name, data)
end
end
How it works
If backgrounding blocks are registered, they will be automatically called on
Attacher#promote_cached
and Attacher#destroy_previous
(called by
Attacher#finalize
), and Attacher#destroy_attached
.
attacher.assign(file)
attacher.finalize # spawns promote job
attacher.destroy_attached # spawns destroy job
These methods are automatically called as part of the attachment flow if you're
using Shrine::Attachment
with a persistence plugin such as activerecord
or
sequel
.
photo = Photo.new
photo.image = file
photo.save # spawns promote job
photo.destroy # spawns destroy job
Atomic promotion
Inside the promote job, we use Attacher.retrieve
and
Attacher#atomic_promote
for concurrency safety. These methods are provided
by the atomic_helpers
plugin, which is loaded automatically
by your persistence plugin (activerecord
, sequel
).
attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
attacher.atomic_promote
Without concurrency safety, promotion would look like this:
attacher = record.send(:"#{name}_attacher")
attacher.promote
attacher.persist
Registering backgrounding blocks
The blocks registered by Attacher.promote_block
and Attacher#destroy_block
are by default evaluated in context of a Shrine::Attacher
instance. You can
also use the explicit version by declaring an attacher argument:
Shrine::Attacher.promote_block do |attacher|
PromoteJob.perform_async(
attacher.class.name,
attacher.record.class.name,
attacher.record.id,
attacher.name.to_s,
attacher.file_data,
)
end
Shrine::Attacher.destroy_block do |attacher|
DestroyJob.perform_async(
attacher.class.name,
attacher.data,
)
end
You can also register backgrounding blocks on attacher instances for more flexibility:
photo.image_attacher.promote_block do |attacher|
PromoteJob.perform_async(
attacher.class.name,
attacher.record.class.name,
attacher.record.id,
attacher.name.to_s,
attacher.file_data,
current_user.id, # pass arguments known at the controller level
)
end
photo.image = file
photo.save # executes the promote block above
Calling backgrounding blocks
If you want to call backgrounding blocks directly, you can do that by calling
Attacher#promote_background
and Attacher#destroy_background
.
attacher.promote_background # calls promote block directly
attacher.destroy_background # calls destroy block directly
Any options passed to these methods will be forwarded to the background block:
attacher.promote_background(foo: "bar")
# with instance eval
Shrine::Attacher.promote_block do |**options|
options #=> { foo: "bar" }
end
# without instance eval
Shrine::Attacher.promote_block do |attacher, **options|
options #=> { foo: "bar" }
end
Disabling backgrounding
If you've registered backgrounding blocks, but want to temporarily disable them and make the execution synchronous, you can override them on the attacher level and call the default behaviour:
photo.image_attacher.promote_block { promote } # promote synchronously
photo.image_attacher.destroy_block { destroy } # destroy synchronously
# ... now promotion and deletion will be synchronous ...
You can also do this on the class level if you want to disable backgrounding that was set up by a superclass:
class MyUploader < Shrine
Attacher.promote_block { promote } # promote synchronously
Attacher.destroy_block { destroy } # destroy synchronously
end