In the past we've had a lot of discussions (and some attempts) regarding rewriting the versions plugin into something more flexible (1, 2, 3, 4, 5, 6).
Note that this issue isn't for discussion, it's more for tracking progress. We'll hopefully discuss and figure out details while making pull requests.
The current versions plugin has couple of downsides and limitations:
1️⃣ Adding new versions after promotion. If you're allowing users to crop uploaded images after attaching, it's currently fairly difficult to add the cropped version to an existing attachment. The versions plugin only gives you the API for generating versions as part of attachment.
2️⃣ Data structure changing. The original file needs to be fetched differently depending on whether it's still cached (photo.image) or stored (photo.image[:original]). This brings inconsistency to using Shrine, as shown by some issues posted by users (e.g. https://github.com/shrinerb/shrine/issues/364, https://github.com/shrinerb/shrine/issues/305).
3️⃣ Different storage for different versions. It's common to want to store the original file in storage A, but versions in storage B. Currently this is somewhat possible, but it's hacky and most people won't figure that out.
Some related plugins are have also shown to be confusing/problematic in the past, and those could be deprecated if we rewrote versions:
versionsprocessing (maybe)recachedelete_rawparallelize (this one is ok, but now user can do their own parallelization)parsed_json (this functionality would be merged into base)The new plugin would be called derivatives (as these files would be _"derived"_ from a source). Given the following setup:
class ImageUploader < Shrine
plugin :derivatives
# or
plugin :derivatives, storage: :thumbnails
end
# ...
class Photo < Sequel::Model
include ImageUploader::Attachment.new(:image)
end
The API could look something like this:
add_derivative(s)This method uploads given files to storage, assigns uploaded file data to the attachment column, and _deletes_ the source files.
photo.image_attacher.add_derivative(:small, thumbnail_300)
photo.image_attacher.add_derivative(:medium, thumbnail_500)
photo.image_attacher.add_derivative(:large, thumbnail_800)
# or
photo.image_attacher.add_derivatives(
small: thumbnail_300,
medium: thumbnail_500,
large: thumbnail_800,
)
photo.image_data #=> '{
# "id": "...",
# "storage": "...",
# "metadata": {...},
# "derivatives": {
# "small": { "id": "...", "storage": "...", "metadata": {...} },
# "medium": { "id": "...", "storage": "...", "metadata": {...} },
# "large": { "id": "...", "storage": "...", "metadata": {...} },
# }
# }'
photo.image #=> #<Shrine::UploadedFile> (original)
photo.image_derivatives #=> {
# small: #<Shrine::UploadedFile>,
# medium: #<Shrine::UploadedFile>,
# large: #<Shrine::UploadedFile>,
# }
This method can also receive Shrine::UploadedFile objects, in case the files have already been uploaded elsewhere. You can also customize the upload:
photo.image_attacher.add_derivative(:small, thumbnail_300, {
storage: :other_store,
location: "/path/to/file",
metadata: { "filename" => "..." },
})
remove_derivative(s)Does the opposite of add_derivative(s), removes the derivative from the column (and deletes the uploaded file).
By default, derivatives would be stored in the same attachment column as the original, under the derivatives field. However, if it would be too difficult to ensure there are no race conditions, I'm open for ditching this approach.
I also would like to support an alternative way of storing files, via blobs and attachments tables like Active Storage has, for original file and derivatives. That way people would only need to create tables once, they could query files more easily, and it would be easier to eliminate race conditions. This would be only possible after https://github.com/shrinerb/shrine/issues/381.
This plan is by no means set in stone, there are still tons of things to consider when designing the API:
The API should be convenient enough to use. Currently I have the following, which might not be the enough:
photo.image.download do |original|
processor = ImageProcessing::Vips.source(original)
photo.image_attacher.add_derivatives(
small: processor.resize_to_limit!(300, 300),
medium: processor.resize_to_limit!(500, 500),
large: processor.resize_to_limit!(800, 800),
)
end
backgrounding plugin).derivation declarations from the derivation_endpoint plugin, if people want to use both derivatives and derivation_endpoint plugins in tandem.The derivatives plugin has been implemented on master and will be available in Shrine 3.0.
https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivatives.md#readme
Most helpful comment
The
derivativesplugin has been implemented onmasterand will be available in Shrine 3.0.https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivatives.md#readme