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:
include Sidekiq::Worker 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 endend
include Sidekiq::Worker attacher_class = Object.const_get(attacher_class) attacher = attacher_class.from_data(data) attacher.destroy endend
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, file_data)endShrine::Attacher.destroy_block do DestroyJob.perform_async(self.class.name, data)end
Alternatively, you can setup backgrounding only for specific uploaders:
Attacher.promote_block do PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data) end Attacher.destroy_block do DestroyJob.perform_async(self.class.name, data) endend
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.newphoto.image = filephoto.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(:" _attacher")attacher.promoteattacher.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, 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, attacher.file_data, current_user.id, # pass arguments known at the controller level )end photo.image = filephoto.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:
Attacher.promote_block { promote } # promote synchronously Attacher.destroy_block { destroy } # destroy synchronously end