shrine 3.0.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/LICENSE.txt +1 -1
- data/README.md +15 -5
- data/doc/advantages.md +33 -16
- data/doc/attacher.md +2 -2
- data/doc/carrierwave.md +78 -34
- data/doc/changing_derivatives.md +39 -39
- data/doc/design.md +134 -85
- data/doc/direct_s3.md +1 -0
- data/doc/external/articles.md +57 -45
- data/doc/external/extensions.md +41 -35
- data/doc/external/misc.md +23 -8
- data/doc/getting_started.md +177 -112
- data/doc/metadata.md +79 -43
- data/doc/multiple_files.md +6 -4
- data/doc/paperclip.md +119 -42
- data/doc/plugins/activerecord.md +1 -1
- data/doc/plugins/add_metadata.md +112 -35
- data/doc/plugins/atomic_helpers.md +41 -3
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/column.md +36 -7
- data/doc/plugins/data_uri.md +2 -2
- data/doc/plugins/default_url.md +6 -3
- data/doc/plugins/derivation_endpoint.md +26 -28
- data/doc/plugins/derivatives.md +238 -171
- data/doc/plugins/determine_mime_type.md +2 -2
- data/doc/plugins/download_endpoint.md +5 -5
- data/doc/plugins/dynamic_storage.md +1 -1
- data/doc/plugins/form_assign.md +5 -5
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/infer_extension.md +11 -2
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/metadata_attributes.md +22 -10
- data/doc/plugins/mirroring.md +1 -1
- data/doc/plugins/persistence.md +11 -1
- data/doc/plugins/refresh_metadata.md +5 -4
- data/doc/plugins/remote_url.md +8 -3
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/signature.md +11 -2
- data/doc/plugins/store_dimensions.md +12 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +7 -11
- data/doc/plugins/upload_options.md +1 -1
- data/doc/plugins/url_options.md +4 -4
- data/doc/plugins/validation.md +14 -4
- data/doc/plugins/validation_helpers.md +3 -3
- data/doc/plugins/versions.md +7 -7
- data/doc/processing.md +290 -127
- data/doc/refile.md +39 -18
- data/doc/release_notes/2.19.0.md +1 -1
- data/doc/release_notes/2.8.0.md +1 -1
- data/doc/release_notes/3.0.0.md +1 -1
- data/doc/release_notes/3.0.1.md +4 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/securing_uploads.md +3 -3
- data/doc/storage/file_system.md +1 -1
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +105 -82
- data/doc/testing.md +2 -2
- data/doc/upgrading_to_3.md +97 -49
- data/doc/validation.md +3 -2
- data/lib/shrine.rb +8 -8
- data/lib/shrine/attacher.rb +24 -14
- data/lib/shrine/attachment.rb +5 -5
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/plugins/activerecord.rb +1 -1
- data/lib/shrine/plugins/add_metadata.rb +18 -7
- data/lib/shrine/plugins/backgrounding.rb +2 -2
- data/lib/shrine/plugins/default_storage.rb +6 -6
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +12 -7
- data/lib/shrine/plugins/derivatives.rb +61 -29
- data/lib/shrine/plugins/determine_mime_type.rb +3 -3
- data/lib/shrine/plugins/entity.rb +6 -6
- data/lib/shrine/plugins/mirroring.rb +8 -8
- data/lib/shrine/plugins/model.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +16 -4
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/remove_attachment.rb +5 -0
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/sequel.rb +1 -1
- data/lib/shrine/plugins/signature.rb +7 -6
- data/lib/shrine/plugins/store_dimensions.rb +22 -11
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +10 -5
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/url_options.rb +2 -2
- data/lib/shrine/plugins/validation.rb +9 -7
- data/lib/shrine/storage/linter.rb +4 -4
- data/lib/shrine/storage/memory.rb +5 -3
- data/lib/shrine/storage/s3.rb +117 -38
- data/lib/shrine/uploaded_file.rb +0 -1
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +7 -8
- metadata +25 -31
@@ -95,16 +95,9 @@ plugin :upload_endpoint, max_size: 20*1024*1024 # 20 MB
|
|
95
95
|
If the uploaded file is larger than the specified value, a `413 Payload Too
|
96
96
|
Large` response will be returned.
|
97
97
|
|
98
|
-
##
|
98
|
+
## Uploader options
|
99
99
|
|
100
|
-
|
101
|
-
upload happens independently of a database record. The endpoint will send the
|
102
|
-
following upload context:
|
103
|
-
|
104
|
-
* `:action` – holds the value `:upload`
|
105
|
-
* `:request` – holds an instance of `Rack::Request`
|
106
|
-
|
107
|
-
You can update the upload context via `:upload_context`:
|
100
|
+
You can pass additional uploader options via `:upload_context`:
|
108
101
|
|
109
102
|
```rb
|
110
103
|
plugin :upload_endpoint, upload_context: -> (request) do
|
@@ -112,13 +105,16 @@ plugin :upload_endpoint, upload_context: -> (request) do
|
|
112
105
|
end
|
113
106
|
```
|
114
107
|
|
108
|
+
Note that the uploader will *not* receive `:record` and `:name` values, as the
|
109
|
+
upload happens independently of a database record.
|
110
|
+
|
115
111
|
## Upload
|
116
112
|
|
117
113
|
You can also customize the upload itself via the `:upload` option:
|
118
114
|
|
119
115
|
```rb
|
120
|
-
plugin :upload_endpoint, upload: -> (io,
|
121
|
-
Shrine.upload(io, :cache,
|
116
|
+
plugin :upload_endpoint, upload: -> (io, options, request) do
|
117
|
+
Shrine.upload(io, :cache, **options)
|
122
118
|
end
|
123
119
|
```
|
124
120
|
|
@@ -13,7 +13,7 @@ Keys are names of the registered storages, and values are either hashes or
|
|
13
13
|
blocks.
|
14
14
|
|
15
15
|
```rb
|
16
|
-
plugin :upload_options, store: -> (io,
|
16
|
+
plugin :upload_options, store: -> (io, options) do
|
17
17
|
if options[:derivative]
|
18
18
|
{ acl: "public-read" }
|
19
19
|
else
|
data/doc/plugins/url_options.md
CHANGED
@@ -4,10 +4,10 @@ title: URL Options
|
|
4
4
|
|
5
5
|
The [`url_options`][url_options] plugin allows you to specify
|
6
6
|
URL options that will be applied by default for uploaded files of specified
|
7
|
-
storages.
|
7
|
+
storages. `url_options` are parameters specific to the storage service.
|
8
8
|
|
9
9
|
```rb
|
10
|
-
plugin :url_options, store: { expires_in: 24*60*60 }
|
10
|
+
plugin :url_options, store: { expires_in: 24*60*60 } # `expires_in` is a URL option for AWS S3
|
11
11
|
```
|
12
12
|
|
13
13
|
You can also generate the default URL options dynamically by using a block,
|
@@ -15,8 +15,8 @@ which will receive the UploadedFile object along with any options that were
|
|
15
15
|
passed to `UploadedFile#url`.
|
16
16
|
|
17
17
|
```rb
|
18
|
-
plugin :url_options, store: -> (
|
19
|
-
{ response_content_disposition: ContentDisposition.attachment(
|
18
|
+
plugin :url_options, store: -> (file, options) do
|
19
|
+
{ response_content_disposition: ContentDisposition.attachment(file.original_filename) }
|
20
20
|
end
|
21
21
|
```
|
22
22
|
|
data/doc/plugins/validation.md
CHANGED
@@ -34,11 +34,12 @@ The validation block is executed in context of a `Shrine::Attacher` instance:
|
|
34
34
|
```rb
|
35
35
|
class VideoUploader < Shrine
|
36
36
|
Attacher.validate do
|
37
|
-
self
|
37
|
+
self #=> #<Shrine::Attacher>
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
file #=> #<Shrine::UploadedFile>
|
40
|
+
record #=> #<Movie>
|
41
|
+
name #=> :video
|
42
|
+
context #=> { ... }
|
42
43
|
end
|
43
44
|
end
|
44
45
|
```
|
@@ -83,5 +84,14 @@ You can also skip validation by passing `validate: false`:
|
|
83
84
|
attacher.assign(file, validate: false) # skips validation
|
84
85
|
```
|
85
86
|
|
87
|
+
## Manual validation
|
88
|
+
|
89
|
+
You can also run validation manually via `Attacher#validate`:
|
90
|
+
|
91
|
+
```rb
|
92
|
+
attacher.set(uploaded_file) # doesn't trigger validation
|
93
|
+
attacher.validate # runs validation
|
94
|
+
```
|
95
|
+
|
86
96
|
[validation]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/validation.rb
|
87
97
|
[validation_helpers]: https://shrinerb.com/docs/plugins/validation_helpers
|
@@ -150,11 +150,11 @@ the `:default_messages` option to the plugin:
|
|
150
150
|
```rb
|
151
151
|
plugin :validation_helpers, default_messages: {
|
152
152
|
max_size: -> (max) { I18n.t("errors.file.max_size", max: max) },
|
153
|
-
min_size: -> (
|
153
|
+
min_size: -> (min) { I18n.t("errors.file.min_size", min: min) },
|
154
154
|
max_width: -> (max) { I18n.t("errors.file.max_width", max: max) },
|
155
|
-
min_width: -> (
|
155
|
+
min_width: -> (min) { I18n.t("errors.file.min_width", min: min) },
|
156
156
|
max_height: -> (max) { I18n.t("errors.file.max_height", max: max) },
|
157
|
-
min_height: -> (
|
157
|
+
min_height: -> (min) { I18n.t("errors.file.min_height", min: min) },
|
158
158
|
max_dimensions: -> (dims) { I18n.t("errors.file.max_dimensions", dims: dims) },
|
159
159
|
min_dimensions: -> (dims) { I18n.t("errors.file.min_dimensions", dims: dims) },
|
160
160
|
mime_type_inclusion: -> (list) { I18n.t("errors.file.mime_type_inclusion", list: list) },
|
data/doc/plugins/versions.md
CHANGED
@@ -17,7 +17,7 @@ require "image_processing/mini_magick"
|
|
17
17
|
|
18
18
|
plugin :processing
|
19
19
|
|
20
|
-
process(:store) do |io,
|
20
|
+
process(:store) do |io, **options|
|
21
21
|
versions = { original: io } # retain original
|
22
22
|
|
23
23
|
io.download do |original|
|
@@ -120,7 +120,7 @@ In addition to Hashes, the plugin also supports Arrays of files. For example,
|
|
120
120
|
you might want to split a PDf into pages:
|
121
121
|
|
122
122
|
```rb
|
123
|
-
process(:store) do |io,
|
123
|
+
process(:store) do |io, **options|
|
124
124
|
versions = { pages: [] }
|
125
125
|
|
126
126
|
io.download do |pdf|
|
@@ -146,7 +146,7 @@ which you can do by adding the yielded `Shrine::UploadedFile` object as one of
|
|
146
146
|
the versions, by convention named `:original`:
|
147
147
|
|
148
148
|
```rb
|
149
|
-
process(:store) do |io,
|
149
|
+
process(:store) do |io, **options|
|
150
150
|
# processing thumbnail
|
151
151
|
{ original: io, thumbnail: thumbnail }
|
152
152
|
end
|
@@ -163,12 +163,12 @@ The version name will be available via `:version` when generating location or a
|
|
163
163
|
default URL.
|
164
164
|
|
165
165
|
```rb
|
166
|
-
def generate_location(io,
|
167
|
-
"uploads/#{
|
166
|
+
def generate_location(io, version: nil, **)
|
167
|
+
"uploads/#{version}-#{super}"
|
168
168
|
end
|
169
169
|
|
170
|
-
Attacher.default_url do |
|
171
|
-
"/images/defaults/#{
|
170
|
+
Attacher.default_url do |version: nil, **|
|
171
|
+
"/images/defaults/#{version}.jpg"
|
172
172
|
end
|
173
173
|
```
|
174
174
|
|
data/doc/processing.md
CHANGED
@@ -2,18 +2,18 @@
|
|
2
2
|
title: File Processing
|
3
3
|
---
|
4
4
|
|
5
|
-
Shrine allows you to process attached files
|
5
|
+
Shrine allows you to process attached files eagerly or on-the-fly. For
|
6
6
|
example, if your app is accepting image uploads, you can generate a predefined
|
7
7
|
set of of thumbnails when the image is attached to a record, or you can have
|
8
8
|
thumbnails generated dynamically as they're needed.
|
9
9
|
|
10
10
|
How you're going to implement processing is entirely up to you. For images it's
|
11
11
|
recommended to use the **[ImageProcessing]** gem, which provides wrappers for
|
12
|
-
processing with [
|
13
|
-
[libvips]
|
14
|
-
|
12
|
+
processing with [MiniMagick][ImageProcessing::MiniMagick] and
|
13
|
+
[libvips][ImageProcessing::Vips]. Here is an example of generating a thumbnail
|
14
|
+
with ImageProcessing:
|
15
15
|
|
16
|
-
```
|
16
|
+
```
|
17
17
|
$ brew install imagemagick
|
18
18
|
```
|
19
19
|
```rb
|
@@ -24,17 +24,21 @@ gem "image_processing", "~> 1.8"
|
|
24
24
|
require "image_processing/mini_magick"
|
25
25
|
|
26
26
|
thumbnail = ImageProcessing::MiniMagick
|
27
|
-
.source(image)
|
28
|
-
.resize_to_limit
|
27
|
+
.source(image) # input file
|
28
|
+
.resize_to_limit(600, 400) # resize macro
|
29
|
+
.colorspace("grayscale") # custom operation
|
30
|
+
.convert("jpeg") # output type
|
31
|
+
.saver(quality: 90) # output options
|
32
|
+
.call # run the pipeline
|
29
33
|
|
30
34
|
thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
|
31
35
|
```
|
32
36
|
|
33
|
-
##
|
37
|
+
## Eager processing
|
34
38
|
|
35
39
|
Let's say we're handling images, and want to generate a predefined set of
|
36
|
-
thumbnails with various dimensions. We can use the
|
37
|
-
upload and save the processed files:
|
40
|
+
thumbnails with various dimensions. We can use the
|
41
|
+
**[`derivatives`][derivatives]** plugin to upload and save the processed files:
|
38
42
|
|
39
43
|
```rb
|
40
44
|
Shrine.plugin :derivatives
|
@@ -43,7 +47,7 @@ Shrine.plugin :derivatives
|
|
43
47
|
require "image_processing/mini_magick"
|
44
48
|
|
45
49
|
class ImageUploader < Shrine
|
46
|
-
Attacher.
|
50
|
+
Attacher.derivatives do |original|
|
47
51
|
magick = ImageProcessing::MiniMagick.source(original)
|
48
52
|
|
49
53
|
{
|
@@ -56,8 +60,11 @@ end
|
|
56
60
|
```
|
57
61
|
```rb
|
58
62
|
photo = Photo.new(image: file)
|
59
|
-
|
60
|
-
photo.
|
63
|
+
|
64
|
+
if photo.valid?
|
65
|
+
photo.image_derivatives! if photo.image_changed? # creates derivatives
|
66
|
+
photo.save
|
67
|
+
end
|
61
68
|
```
|
62
69
|
|
63
70
|
After the processed files are uploaded, their data is saved into the
|
@@ -71,27 +78,54 @@ photo.image(:large).size #=> 5825949
|
|
71
78
|
photo.image(:large).mime_type #=> "image/jpeg"
|
72
79
|
```
|
73
80
|
|
74
|
-
###
|
81
|
+
### Conditional derivatives
|
75
82
|
|
76
|
-
|
77
|
-
|
78
|
-
promotion:
|
83
|
+
The `Attacher.derivatives` block is evaluated in context of a
|
84
|
+
`Shrine::Attacher` instance:
|
79
85
|
|
80
86
|
```rb
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
87
|
+
Attacher.derivatives do |original|
|
88
|
+
self #=> #<Shrine::Attacher>
|
89
|
+
|
90
|
+
file #=> #<Shrine::UploadedFile>
|
91
|
+
record #=> #<Photo>
|
92
|
+
name #=> :image
|
93
|
+
context #=> { ... }
|
94
|
+
|
95
|
+
# ...
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
This gives you the ability to branch the processing logic based on the
|
100
|
+
attachment information:
|
101
|
+
|
102
|
+
```rb
|
103
|
+
Attacher.derivatives do |original|
|
104
|
+
magick = ImageProcessing::MiniMagick.source(original)
|
105
|
+
result = {}
|
106
|
+
|
107
|
+
if record.is_a?(Photo)
|
108
|
+
result[:jpg] = magick.convert!("jpeg")
|
109
|
+
result[:gray] = magick.colorspace!("grayscale")
|
85
110
|
end
|
111
|
+
|
112
|
+
if file.mime_type == "image/svg+xml"
|
113
|
+
result[:png] = magick.loader(transparent: "white").convert!("png")
|
114
|
+
end
|
115
|
+
|
116
|
+
result
|
86
117
|
end
|
87
118
|
```
|
88
119
|
|
120
|
+
The [`type_predicates`][type_predicates] plugin provides convenient predicate
|
121
|
+
methods for branching based on the file type.
|
122
|
+
|
89
123
|
### Backgrounding
|
90
124
|
|
91
125
|
Since file processing can be time consuming, it's recommended to move it into a
|
92
126
|
background job.
|
93
127
|
|
94
|
-
####
|
128
|
+
#### A) Creating derivatives with promotion
|
95
129
|
|
96
130
|
The simplest way is to use the [`backgrounding`][backgrounding] plugin to move
|
97
131
|
promotion into a background job, and then create derivatives as part of
|
@@ -120,7 +154,7 @@ class PromoteJob
|
|
120
154
|
end
|
121
155
|
```
|
122
156
|
|
123
|
-
####
|
157
|
+
#### B) Creating derivatives separately from promotion
|
124
158
|
|
125
159
|
Derivatives don't need to be created as part of the attachment flow, you can
|
126
160
|
create them at any point after promotion:
|
@@ -151,7 +185,7 @@ class DerivativesJob
|
|
151
185
|
end
|
152
186
|
```
|
153
187
|
|
154
|
-
####
|
188
|
+
#### C) Creating derivatives concurrently
|
155
189
|
|
156
190
|
You can also generate derivatives concurrently:
|
157
191
|
|
@@ -163,7 +197,7 @@ class ImageUploader < Shrine
|
|
163
197
|
small: [300, 300],
|
164
198
|
}
|
165
199
|
|
166
|
-
Attacher.
|
200
|
+
Attacher.derivatives do |original, name:|
|
167
201
|
thumbnail = ImageProcessing::MiniMagick
|
168
202
|
.source(original)
|
169
203
|
.resize_to_limit!(*THUMBNAILS.fetch(name))
|
@@ -204,67 +238,97 @@ class DerivativeJob
|
|
204
238
|
end
|
205
239
|
```
|
206
240
|
|
207
|
-
###
|
241
|
+
### URL fallbacks
|
208
242
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
Shrine integration, the ImageProcessing gem that we saw earlier is a completely
|
213
|
-
generic gem.
|
243
|
+
If you're creating derivatives in a background job, you'll likely want to use
|
244
|
+
some fallbacks for derivative URLs while the background job is still
|
245
|
+
processing. You can do that with the [`default_url`][default_url] plugin.
|
214
246
|
|
215
|
-
|
216
|
-
|
247
|
+
```rb
|
248
|
+
Shrine.plugin :default_url
|
249
|
+
```
|
250
|
+
|
251
|
+
#### A) Fallback to original
|
252
|
+
|
253
|
+
You can fall back to the original file URL when the derivative is missing:
|
217
254
|
|
218
255
|
```rb
|
219
|
-
|
220
|
-
|
256
|
+
Attacher.default_url do |derivative: nil, **|
|
257
|
+
file&.url if derivative
|
258
|
+
end
|
221
259
|
```
|
222
260
|
```rb
|
223
|
-
|
261
|
+
photo.image_url(:large) #=> "https://example.com/path/to/original.jpg"
|
262
|
+
# ... background job finishes ...
|
263
|
+
photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
|
264
|
+
```
|
224
265
|
|
225
|
-
|
226
|
-
Attacher.derivatives_processor do |original|
|
227
|
-
transcoded = Tempfile.new ["transcoded", ".mp4"]
|
228
|
-
screenshot = Tempfile.new ["screenshot", ".jpg"]
|
266
|
+
#### B) Fallback to derivative
|
229
267
|
|
230
|
-
|
231
|
-
movie.transcode(transcoded.path)
|
232
|
-
movie.screenshot(screenshot.path)
|
268
|
+
You can fall back to another derivative URL when the derivative is missing:
|
233
269
|
|
234
|
-
|
235
|
-
|
270
|
+
```rb
|
271
|
+
Attacher.default_url do |derivative: nil, **|
|
272
|
+
derivatives[:optimized]&.url if derivative
|
236
273
|
end
|
237
274
|
```
|
275
|
+
```rb
|
276
|
+
photo.image_url(:large) #=> "https://example.com/path/to/optimized.jpg"
|
277
|
+
# ... background job finishes ...
|
278
|
+
photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
|
279
|
+
```
|
280
|
+
|
281
|
+
#### C) Fallback to on-the-fly
|
282
|
+
|
283
|
+
You can also fall back to [on-the-fly processing](#on-the-fly-processing),
|
284
|
+
which should generally provide the best user experience.
|
285
|
+
|
286
|
+
```rb
|
287
|
+
THUMBNAILS = {
|
288
|
+
small: [300, 300],
|
289
|
+
medium: [500, 500],
|
290
|
+
large: [800, 800],
|
291
|
+
}
|
292
|
+
|
293
|
+
Attacher.default_url do |derivative: nil, **|
|
294
|
+
file&.derivation_url(:thumbnail, *THUMBNAILS.fetch(derivative)) if derivative
|
295
|
+
end
|
296
|
+
```
|
297
|
+
```rb
|
298
|
+
photo.image_url(:large) #=> "../derivations/thumbnail/800/800/..."
|
299
|
+
# ... background job finishes ...
|
300
|
+
photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
|
301
|
+
```
|
238
302
|
|
239
303
|
## On-the-fly processing
|
240
304
|
|
241
|
-
|
305
|
+
Having eagerly created image thumbnails can be a pain to maintain, because
|
242
306
|
whenever you need to add a new version or change an existing one, you need to
|
243
|
-
retroactively apply it to all existing
|
244
|
-
guide for more details).
|
307
|
+
retroactively apply it to all existing attachments (see the [Managing
|
308
|
+
Derivatives] guide for more details).
|
245
309
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
processing such as creating image thumbnails or document
|
310
|
+
Sometimes it makes more sense to generate thumbnails dynamically as they're
|
311
|
+
requested, and then cache them for future requests. This strategy is known as
|
312
|
+
processing "**on-the-fly**" or "**on-demand**", and it's suitable for
|
313
|
+
short-running processing such as creating image thumbnails or document
|
314
|
+
previews.
|
250
315
|
|
251
316
|
Shrine provides on-the-fly processing functionality via the
|
252
|
-
[`derivation_endpoint`][derivation_endpoint] plugin.
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
2. mount the endpoint into your main app's router
|
257
|
-
3. define a processing block for the type files you want to generate
|
258
|
-
|
259
|
-
Together it might look something like this:
|
317
|
+
**[`derivation_endpoint`][derivation_endpoint]** plugin. You set it up by
|
318
|
+
loading the plugin with a secret key and a path prefix, mount its Rack app in
|
319
|
+
your routes on the configured path prefix, and define processing you want to
|
320
|
+
perform:
|
260
321
|
|
322
|
+
```rb
|
323
|
+
# config/initializers/shrine.rb (Rails)
|
324
|
+
# ...
|
325
|
+
Shrine.plugin :derivation_endpoints, secret_key: "<YOUR_SECRET_KEY>"
|
326
|
+
```
|
261
327
|
```rb
|
262
328
|
require "image_processing/mini_magick"
|
263
329
|
|
264
330
|
class ImageUploader < Shrine
|
265
|
-
plugin :derivation_endpoint,
|
266
|
-
secret_key: "<YOUR SECRET KEY>",
|
267
|
-
prefix: "derivations/image"
|
331
|
+
plugin :derivation_endpoint, prefix: "derivations/image" # matches mount point
|
268
332
|
|
269
333
|
derivation :thumbnail do |file, width, height|
|
270
334
|
ImageProcessing::MiniMagick
|
@@ -273,11 +337,11 @@ class ImageUploader < Shrine
|
|
273
337
|
end
|
274
338
|
end
|
275
339
|
```
|
276
|
-
|
277
340
|
```rb
|
278
341
|
# config/routes.rb (Rails)
|
279
342
|
Rails.application.routes.draw do
|
280
|
-
|
343
|
+
# ...
|
344
|
+
mount ImageUploader.derivation_endpoint => "/derivations/image"
|
281
345
|
end
|
282
346
|
```
|
283
347
|
|
@@ -293,8 +357,133 @@ The plugin is highly customizable, be sure to check out the
|
|
293
357
|
[documentation][derivation_endpoint], especially the [performance
|
294
358
|
section][derivation_endpoint performance].
|
295
359
|
|
360
|
+
### Dynamic derivation
|
361
|
+
|
362
|
+
If you have multiple types of transformations and don't want to have a
|
363
|
+
derivation for each one, you can set up a single derivation that applies any
|
364
|
+
series of transformations:
|
365
|
+
|
366
|
+
```rb
|
367
|
+
class ImageUploader < Shrine
|
368
|
+
derivation :transform do |original, transformations|
|
369
|
+
transformations = Shrine.urlsafe_deserialize(transformations)
|
370
|
+
|
371
|
+
vips = ImageProcessing::Vips.source(original)
|
372
|
+
vips.apply!(transformations)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
```
|
376
|
+
```rb
|
377
|
+
photo.image.derivation_url :transform, Shrine.urlsafe_serialize(
|
378
|
+
crop: [10, 10, 500, 500],
|
379
|
+
resize_to_fit: [300, 300],
|
380
|
+
gaussblur: 1,
|
381
|
+
)
|
382
|
+
```
|
383
|
+
|
384
|
+
You can create a helper method for convenience:
|
385
|
+
|
386
|
+
```rb
|
387
|
+
def derivation_url(file, transformations)
|
388
|
+
file.derivation_url(:transform, Shrine.urlsafe_serialize(transformations))
|
389
|
+
end
|
390
|
+
```
|
391
|
+
```rb
|
392
|
+
derivation_url photo.image,
|
393
|
+
crop: [10, 10, 500, 500],
|
394
|
+
resize_to_fit: [300, 300],
|
395
|
+
gaussblur: 1
|
396
|
+
```
|
397
|
+
|
398
|
+
## Processing other filetypes
|
399
|
+
|
400
|
+
So far we've only been talking about processing images. However, there is
|
401
|
+
nothing image-specific in Shrine's processing API, you can just as well process
|
402
|
+
any other types of files. The processing tool doesn't need to have any special
|
403
|
+
Shrine integration, the ImageProcessing gem that we saw earlier is a completely
|
404
|
+
generic gem.
|
405
|
+
|
406
|
+
To demonstrate, here is an example of transcoding videos using
|
407
|
+
[streamio-ffmpeg]:
|
408
|
+
|
409
|
+
```rb
|
410
|
+
# Gemfile
|
411
|
+
gem "streamio-ffmpeg"
|
412
|
+
```
|
413
|
+
```rb
|
414
|
+
require "streamio-ffmpeg"
|
415
|
+
|
416
|
+
class VideoUploader < Shrine
|
417
|
+
Attacher.derivatives do |original|
|
418
|
+
transcoded = Tempfile.new ["transcoded", ".mp4"]
|
419
|
+
screenshot = Tempfile.new ["screenshot", ".jpg"]
|
420
|
+
|
421
|
+
movie = FFMPEG::Movie.new(original.path)
|
422
|
+
movie.transcode(transcoded.path)
|
423
|
+
movie.screenshot(screenshot.path)
|
424
|
+
|
425
|
+
{ transcoded: transcoded, screenshot: screenshot }
|
426
|
+
end
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
### Polymorphic uploader
|
431
|
+
|
432
|
+
Sometimes you might want an attachment attribute to accept multiple types of
|
433
|
+
files, and apply different processing depending on the type. Since Shrine's
|
434
|
+
processing blocks are evaluated dynamically, you can use conditional logic:
|
435
|
+
|
436
|
+
```rb
|
437
|
+
class PolymorphicUploader < Shrine
|
438
|
+
IMAGE_TYPES = %w[image/jpeg image/png image/webp]
|
439
|
+
VIDEO_TYPES = %w[video/mp4 video/quicktime]
|
440
|
+
PDF_TYPES = %w[application/pdf]
|
441
|
+
|
442
|
+
Attacher.validate do
|
443
|
+
validate_mime_type IMAGE_TYPES + VIDEO_TYPES + PDF_TYPES
|
444
|
+
# ...
|
445
|
+
end
|
446
|
+
|
447
|
+
Attacher.derivatives do |original|
|
448
|
+
case file.mime_type
|
449
|
+
when *IMAGE_TYPES then process_derivatives(:image, original)
|
450
|
+
when *VIDEO_TYPES then process_derivatives(:video, original)
|
451
|
+
when *PDF_TYPES then process_derivatives(:pdf, original)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
Attacher.derivatives :image do |original|
|
456
|
+
# ...
|
457
|
+
end
|
458
|
+
|
459
|
+
Attacher.derivatives :video do |original|
|
460
|
+
# ...
|
461
|
+
end
|
462
|
+
|
463
|
+
Attacher.derivatives :pdf do |original|
|
464
|
+
# ...
|
465
|
+
end
|
466
|
+
end
|
467
|
+
```
|
468
|
+
|
296
469
|
## Extras
|
297
470
|
|
471
|
+
### Automatic derivatives
|
472
|
+
|
473
|
+
If you would like derivatives to be automatically created with promotion, you
|
474
|
+
can use the `create_on_promote` option built-in to the derivatives plugin.
|
475
|
+
|
476
|
+
```rb
|
477
|
+
class Shrine::Attacher
|
478
|
+
plugin :derivatives, create_on_promote: true
|
479
|
+
end
|
480
|
+
```
|
481
|
+
|
482
|
+
This shouldn't be needed if you're processing in the
|
483
|
+
[background](#backgrounding), as in that case you have a background worker that
|
484
|
+
will be called for each attachment, so you can call
|
485
|
+
`Attacher#create_derivatives` there.
|
486
|
+
|
298
487
|
### libvips
|
299
488
|
|
300
489
|
As mentioned, ImageProcessing gem also has an alternative backend for
|
@@ -304,9 +493,9 @@ characteristics – it's often **multiple times faster** than ImageMagick and ha
|
|
304
493
|
low memory usage (see [Why is libvips quick]).
|
305
494
|
|
306
495
|
Using libvips is as easy as installing it and switching to the
|
307
|
-
`ImageProcessing::Vips` backend:
|
496
|
+
[`ImageProcessing::Vips`][ImageProcessing::Vips] backend:
|
308
497
|
|
309
|
-
```
|
498
|
+
```
|
310
499
|
$ brew install vips
|
311
500
|
```
|
312
501
|
|
@@ -326,7 +515,7 @@ thumbnail = ImageProcessing::Vips
|
|
326
515
|
thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
|
327
516
|
```
|
328
517
|
|
329
|
-
### Parallelize
|
518
|
+
### Parallelize uploads
|
330
519
|
|
331
520
|
If you're generating derivatives, you can parallelize the uploads using the
|
332
521
|
[concurrent-ruby] gem:
|
@@ -351,86 +540,60 @@ Concurrent::Promises.zip(*tasks).wait!
|
|
351
540
|
|
352
541
|
### External processing
|
353
542
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
543
|
+
You can also integrate Shrine with 3rd-party processing services such as
|
544
|
+
[Cloudinary] and [Imgix]. In the most common case, you'd serve images directly
|
545
|
+
from these services, see the corresponding plugin docs for more details
|
546
|
+
([shrine-cloudinary], [shrine-imgix] and [others][external storages])
|
547
|
+
|
548
|
+
You can also choose to use these services as an implementation detail of your
|
549
|
+
application, by downloading the processed images and saving them to your
|
550
|
+
storage. Here is how you might store files processed by Imgix as derivatives:
|
358
551
|
|
359
552
|
```rb
|
360
553
|
# Gemfile
|
361
554
|
gem "down", "~> 5.0"
|
362
555
|
gem "http", "~> 4.0"
|
556
|
+
gem "shrine-imgix", "~> 0.5"
|
557
|
+
```
|
558
|
+
```rb
|
559
|
+
Shrine.plugin :derivatives
|
560
|
+
Shrine.plugin :imgix, client: { host: "my-app.imgix.net", secure_url_token: "secret" }
|
363
561
|
```
|
364
|
-
|
365
562
|
```rb
|
366
563
|
require "down/http"
|
367
564
|
|
368
565
|
class ImageUploader < Shrine
|
369
|
-
|
370
|
-
|
371
|
-
prefix: "derivations/image",
|
372
|
-
download: false
|
373
|
-
|
374
|
-
derivation :thumbnail do |width, height|
|
375
|
-
# generate thumbnails using ImageOptim.com
|
376
|
-
down = Down::Http.new(method: :post)
|
377
|
-
down.download("https://im2.io/<USERNAME>/#{width}x#{height}/#{source.url}")
|
566
|
+
IMGIX_THUMBNAIL = -> (file, width, height) do
|
567
|
+
Down::Http.download(file.imgix_url(w: width, h: height))
|
378
568
|
end
|
379
|
-
end
|
380
|
-
```
|
381
|
-
|
382
|
-
### Cloudinary
|
383
569
|
|
384
|
-
|
385
|
-
|
386
|
-
[
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
```
|
393
|
-
|
394
|
-
```rb
|
395
|
-
require "cloudinary"
|
396
|
-
require "shrine/storage/cloudinary"
|
397
|
-
|
398
|
-
Cloudinary.config(
|
399
|
-
cloud_name: "<YOUR_CLOUD_NAME>",
|
400
|
-
api_key: "<YOUR_API_KEY>",
|
401
|
-
api_secret: "<YOUR_API_SECRET>",
|
402
|
-
)
|
403
|
-
|
404
|
-
Shrine.storages = {
|
405
|
-
cache: Shrine::Storage::Cloudinary.new(prefix: "cache"),
|
406
|
-
store: Shrine::Storage::Cloudinary.new,
|
407
|
-
}
|
408
|
-
```
|
409
|
-
|
410
|
-
Now when we upload our images to Cloudinary, we can generate URLs with various
|
411
|
-
processing parameters:
|
412
|
-
|
413
|
-
```rb
|
414
|
-
photo.image_url(width: 100, height: 100, crop: :fit)
|
415
|
-
#=> "http://res.cloudinary.com/myapp/image/upload/w_100,h_100,c_fit/nature.jpg"
|
570
|
+
Attacher.derivatives do
|
571
|
+
{
|
572
|
+
large: IMGIX_THUMBNAIL[file, 800, 800],
|
573
|
+
medium: IMGIX_THUMBNAIL[file, 500, 500],
|
574
|
+
small: IMGIX_THUMBNAIL[file, 300, 300],
|
575
|
+
}
|
576
|
+
end
|
577
|
+
end
|
416
578
|
```
|
417
579
|
|
418
580
|
[`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
|
419
581
|
[ImageProcessing]: https://github.com/janko/image_processing
|
420
|
-
[
|
421
|
-
[
|
422
|
-
[libvips]: http://libvips.github.io/libvips/
|
423
|
-
[Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
|
424
|
-
[ImageOptim.com]: https://imageoptim.com/api
|
582
|
+
[ImageProcessing::MiniMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
|
583
|
+
[ImageProcessing::Vips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
|
425
584
|
[streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
|
426
585
|
[Managing Derivatives]: https://shrinerb.com/docs/changing-derivatives
|
427
|
-
[Cloudinary]: https://cloudinary.com
|
586
|
+
[Cloudinary]: https://cloudinary.com/
|
587
|
+
[Imgix]: https://www.imgix.com/
|
428
588
|
[shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
|
589
|
+
[shrine-imgix]: https://github.com/shrinerb/shrine-imgix
|
429
590
|
[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
|
430
|
-
[ruby-vips]: https://github.com/libvips/ruby-vips
|
431
|
-
[MiniMagick]: https://github.com/minimagick/minimagick
|
432
591
|
[derivation_endpoint]: https://shrinerb.com/docs/plugins/derivation_endpoint
|
433
592
|
[derivation_endpoint performance]: https://shrinerb.com/docs/plugins/derivation_endpoint#performance
|
434
593
|
[derivatives]: https://shrinerb.com/docs/plugins/derivatives
|
435
594
|
[concurrent-ruby]: https://github.com/ruby-concurrency/concurrent-ruby
|
436
|
-
[
|
595
|
+
[default_url]: https://shrinerb.com/docs/plugins/default_url
|
596
|
+
[external storages]: https://shrinerb.com/docs/external/extensions#storages
|
597
|
+
[libvips]: https://libvips.github.io/libvips/
|
598
|
+
[Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
|
599
|
+
[type_predicates]: https://shrinerb.com/docs/plugins/type_predicates
|