The backgrounding plugin enables you to move promoting and
deleting of files from record's lifecycle into background jobs. This is
especially useful if you're doing processing and/or you're storing files on an
external storage service.
The plugin provides Attacher.promote and Attacher.delete methods, which
allow you to hook up to promoting and deleting and spawn background jobs, by
passing a block.
Shrine.plugin :backgrounding
# makes all uploaders use background jobs
Shrine::Attacher.promote { |data| PromoteJob.perform_async(data) }
Shrine::Attacher.delete { |data| DeleteJob.perform_async(data) }If you don't want to apply backgrounding for all uploaders, you can declare the hooks only for specific uploaders (in this case it's still recommended to keep the plugin loaded globally).
class MyUploader < Shrine
# makes this uploader use background jobs
Attacher.promote { |data| PromoteJob.perform_async(data) }
Attacher.delete { |data| DeleteJob.perform_async(data) }
endThe yielded data variable is a serializable hash containing all context
needed for promotion/deletion. Now you just need to declare the job classes,
and inside them call Attacher.promote or Attacher.delete, this time with
the received data.
class PromoteJob
include Sidekiq::Worker
def perform(data)
Shrine::Attacher.promote(data)
end
end
class DeleteJob
include Sidekiq::Worker
def perform(data)
Shrine::Attacher.delete(data)
end
endThis example used Sidekiq, but obviously you could just as well use any other backgrounding library. This setup will be applied globally for all uploaders.
If you're generating versions, and you want to process some versions in the
foreground before kicking off a background job, you can use the recache
plugin.
In your application you can use Attacher#cached? and Attacher#stored?
to differentiate between your background job being in progress and
having completed.
if user.avatar_attacher.cached? # background job is still in progress
# ...
elsif user.avatar_attacher.stored? # background job has finished
# ...
endIn background jobs, Attacher.promote and Attacher.delete will resolve all
necessary objects, and do the promotion/deletion. If Attacher.find_record is
defined (which comes with ORM plugins), model instances will be treated as
database records, with the #id attribute assumed to represent the primary
key. Then promotion will have the following behaviour:
- retrieves the database record
- if record is not found, it finishes
- if record is found but attachment has changed, it finishes
- uploads cached file to permanent storage
- reloads the database record
- if record is not found, it deletes the promoted files and finishes
- if record is found but attachment has changed, it deletes the promoted files and finishes
- updates the record with the promoted files
Both Attacher.promote and Attacher.delete return a Shrine::Attacher
instance (if the action hasn't aborted), so you can use it to perform
additional tasks:
def perform(data)
attacher = Shrine::Attacher.promote(data)
attacher.record.update(published: true) if attacher && attacher.record.is_a?(Post)
endYou can also do backgrounding with plain models which don't represent database
records; the plugin will use that mode if Attacher.find_record is not
defined. In that case promotion will have the following behaviour:
- instantiates the model
- uploads cached file to permanent storage
- writes promoted files to the model instance
You can then retrieve the promoted files via the attacher object that
Attacher.promote returns, and do any additional tasks if you need to.
The plugin modifies Attacher#_promote and Attacher#_delete to call the
registered blocks with serializable attacher data, and these methods are
internally called by the attacher. Attacher#promote and Attacher#delete!
remain synchronous.
# asynchronous (spawn background jobs)
attacher._promote
attacher._delete(attachment)
# synchronous
attacher.promote
attacher.delete!(attachment)The plugin adds Attacher.dump and Attacher.load methods for serializing
attacher object and loading it back up. You can use them to spawn background
jobs for custom tasks.
data = Shrine::Attacher.dump(attacher)
SomethingJob.perform_async(data)
# ...
class SomethingJob
def perform(data)
attacher = Shrine::Attacher.load(data)
# ...
end
end