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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +15 -5
  5. data/doc/advantages.md +33 -16
  6. data/doc/attacher.md +2 -2
  7. data/doc/carrierwave.md +78 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/direct_s3.md +1 -0
  11. data/doc/external/articles.md +57 -45
  12. data/doc/external/extensions.md +41 -35
  13. data/doc/external/misc.md +23 -8
  14. data/doc/getting_started.md +177 -112
  15. data/doc/metadata.md +79 -43
  16. data/doc/multiple_files.md +6 -4
  17. data/doc/paperclip.md +119 -42
  18. data/doc/plugins/activerecord.md +1 -1
  19. data/doc/plugins/add_metadata.md +112 -35
  20. data/doc/plugins/atomic_helpers.md +41 -3
  21. data/doc/plugins/backgrounding.md +12 -2
  22. data/doc/plugins/column.md +36 -7
  23. data/doc/plugins/data_uri.md +2 -2
  24. data/doc/plugins/default_url.md +6 -3
  25. data/doc/plugins/derivation_endpoint.md +26 -28
  26. data/doc/plugins/derivatives.md +238 -171
  27. data/doc/plugins/determine_mime_type.md +2 -2
  28. data/doc/plugins/download_endpoint.md +5 -5
  29. data/doc/plugins/dynamic_storage.md +1 -1
  30. data/doc/plugins/form_assign.md +5 -5
  31. data/doc/plugins/included.md +25 -5
  32. data/doc/plugins/infer_extension.md +11 -2
  33. data/doc/plugins/instrumentation.md +1 -1
  34. data/doc/plugins/metadata_attributes.md +22 -10
  35. data/doc/plugins/mirroring.md +1 -1
  36. data/doc/plugins/persistence.md +11 -1
  37. data/doc/plugins/refresh_metadata.md +5 -4
  38. data/doc/plugins/remote_url.md +8 -3
  39. data/doc/plugins/remove_invalid.md +9 -1
  40. data/doc/plugins/signature.md +11 -2
  41. data/doc/plugins/store_dimensions.md +12 -2
  42. data/doc/plugins/type_predicates.md +96 -0
  43. data/doc/plugins/upload_endpoint.md +7 -11
  44. data/doc/plugins/upload_options.md +1 -1
  45. data/doc/plugins/url_options.md +4 -4
  46. data/doc/plugins/validation.md +14 -4
  47. data/doc/plugins/validation_helpers.md +3 -3
  48. data/doc/plugins/versions.md +7 -7
  49. data/doc/processing.md +290 -127
  50. data/doc/refile.md +39 -18
  51. data/doc/release_notes/2.19.0.md +1 -1
  52. data/doc/release_notes/2.8.0.md +1 -1
  53. data/doc/release_notes/3.0.0.md +1 -1
  54. data/doc/release_notes/3.0.1.md +4 -0
  55. data/doc/release_notes/3.1.0.md +73 -0
  56. data/doc/release_notes/3.2.0.md +96 -0
  57. data/doc/release_notes/3.2.1.md +31 -0
  58. data/doc/release_notes/3.2.2.md +14 -0
  59. data/doc/release_notes/3.3.0.md +105 -0
  60. data/doc/securing_uploads.md +3 -3
  61. data/doc/storage/file_system.md +1 -1
  62. data/doc/storage/memory.md +19 -0
  63. data/doc/storage/s3.md +105 -82
  64. data/doc/testing.md +2 -2
  65. data/doc/upgrading_to_3.md +97 -49
  66. data/doc/validation.md +3 -2
  67. data/lib/shrine.rb +8 -8
  68. data/lib/shrine/attacher.rb +24 -14
  69. data/lib/shrine/attachment.rb +5 -5
  70. data/lib/shrine/plugins.rb +22 -0
  71. data/lib/shrine/plugins/activerecord.rb +1 -1
  72. data/lib/shrine/plugins/add_metadata.rb +18 -7
  73. data/lib/shrine/plugins/backgrounding.rb +2 -2
  74. data/lib/shrine/plugins/default_storage.rb +6 -6
  75. data/lib/shrine/plugins/default_url.rb +1 -1
  76. data/lib/shrine/plugins/derivation_endpoint.rb +12 -7
  77. data/lib/shrine/plugins/derivatives.rb +61 -29
  78. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  79. data/lib/shrine/plugins/entity.rb +6 -6
  80. data/lib/shrine/plugins/mirroring.rb +8 -8
  81. data/lib/shrine/plugins/model.rb +3 -3
  82. data/lib/shrine/plugins/presign_endpoint.rb +16 -4
  83. data/lib/shrine/plugins/pretty_location.rb +1 -1
  84. data/lib/shrine/plugins/processing.rb +1 -1
  85. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  86. data/lib/shrine/plugins/remote_url.rb +3 -3
  87. data/lib/shrine/plugins/remove_attachment.rb +5 -0
  88. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  89. data/lib/shrine/plugins/sequel.rb +1 -1
  90. data/lib/shrine/plugins/signature.rb +7 -6
  91. data/lib/shrine/plugins/store_dimensions.rb +22 -11
  92. data/lib/shrine/plugins/type_predicates.rb +113 -0
  93. data/lib/shrine/plugins/upload_endpoint.rb +10 -5
  94. data/lib/shrine/plugins/upload_options.rb +2 -2
  95. data/lib/shrine/plugins/url_options.rb +2 -2
  96. data/lib/shrine/plugins/validation.rb +9 -7
  97. data/lib/shrine/storage/linter.rb +4 -4
  98. data/lib/shrine/storage/memory.rb +5 -3
  99. data/lib/shrine/storage/s3.rb +117 -38
  100. data/lib/shrine/uploaded_file.rb +0 -1
  101. data/lib/shrine/version.rb +2 -2
  102. data/shrine.gemspec +7 -8
  103. 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
- ## Context
98
+ ## Uploader options
99
99
 
100
- The upload context will *not* contain `:record` and `:name` values, as the
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, context, request) do
121
- Shrine.upload(io, :cache, context)
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, **options) do
16
+ plugin :upload_options, store: -> (io, options) do
17
17
  if options[:derivative]
18
18
  { acl: "public-read" }
19
19
  else
@@ -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: -> (io, options) do
19
- { response_content_disposition: ContentDisposition.attachment(io.original_filename) }
18
+ plugin :url_options, store: -> (file, options) do
19
+ { response_content_disposition: ContentDisposition.attachment(file.original_filename) }
20
20
  end
21
21
  ```
22
22
 
@@ -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 #=> #<VideoUploader::Attacher>
37
+ self #=> #<Shrine::Attacher>
38
38
 
39
- record #=> #<Movie>
40
- name #=> :video
41
- file #=> #<Shrine::UploadedFile>
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: -> (max) { I18n.t("errors.file.min_size", min: min) },
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: -> (max) { I18n.t("errors.file.min_width", min: min) },
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: -> (max) { I18n.t("errors.file.min_height", min: min) },
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) },
@@ -17,7 +17,7 @@ require "image_processing/mini_magick"
17
17
 
18
18
  plugin :processing
19
19
 
20
- process(:store) do |io, context|
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, context|
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, context|
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, context)
167
- "uploads/#{context[:version]}-#{super}"
166
+ def generate_location(io, version: nil, **)
167
+ "uploads/#{version}-#{super}"
168
168
  end
169
169
 
170
- Attacher.default_url do |options|
171
- "/images/defaults/#{options[:version]}.jpg"
170
+ Attacher.default_url do |version: nil, **|
171
+ "/images/defaults/#{version}.jpg"
172
172
  end
173
173
  ```
174
174
 
@@ -2,18 +2,18 @@
2
2
  title: File Processing
3
3
  ---
4
4
 
5
- Shrine allows you to process attached files up front or on-the-fly. For
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 [ImageMagick]/[GraphicsMagick] (using the [MiniMagick] gem) or
13
- [libvips] (using the [ruby-vips] gem; see the [libvips section](#libvips)).
14
- Here is an example of generating a thumbnail with ImageProcessing:
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
- ```sh
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!(600, 400)
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
- ## Processing up front
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 `derivatives` plugin to
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.derivatives_processor do |original|
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
- photo.image_derivatives! # calls derivatives processor
60
- photo.save
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
- ### Automatic processing
81
+ ### Conditional derivatives
75
82
 
76
- If you would like derivatives to be automatically created with promotion, you
77
- can override `Attacher#promote` for call `Attacher#create_derivatives` before
78
- promotion:
83
+ The `Attacher.derivatives` block is evaluated in context of a
84
+ `Shrine::Attacher` instance:
79
85
 
80
86
  ```rb
81
- class Shrine::Attacher
82
- def promote(*)
83
- create_derivatives
84
- super
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
- #### With promotion
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
- #### Separate from promotion
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
- #### Concurrent processing
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.derivatives_processor do |original, name:|
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
- ### Processing other filetypes
241
+ ### URL fallbacks
208
242
 
209
- So far we've only been talking about processing images. However, there is
210
- nothing image-specific in Shrine's processing API, you can just as well process
211
- any other types of files. The processing tool doesn't need to have any special
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
- To demonstrate, here is an example of transcoding videos using
216
- [streamio-ffmpeg]:
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
- # Gemfile
220
- gem "streamio-ffmpeg"
256
+ Attacher.default_url do |derivative: nil, **|
257
+ file&.url if derivative
258
+ end
221
259
  ```
222
260
  ```rb
223
- require "streamio-ffmpeg"
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
- class VideoUploader < Shrine
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
- movie = FFMPEG::Movie.new(original.path)
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
- { transcoded: transcoded, screenshot: screenshot }
235
- end
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
- Generating image thumbnails on upload can be a pain to maintain, because
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 uploads (see the [Managing Derivatives]
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
- As an alternative, it's very common to instead generate thumbnails dynamically
247
- as they're requested, and then cache them for future requests. This strategy is
248
- known as "on-the-fly processing", and it's suitable for short-running
249
- processing such as creating image thumbnails or document previews.
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. The basic setup is the
253
- following:
254
-
255
- 1. load the plugin with a secret key and a path prefix for the endpoint
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
- mount ImageUploader.derivation_endpoint => "derivations/image"
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
- ```sh
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 uploading
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
- Since processing is so dynamic, you're not limited to using the ImageProcessing
355
- gem, you can also use a 3rd-party service to generate thumbnails for you. Here
356
- is an example of generating thumbnails on-the-fly using [ImageOptim.com] (not
357
- to be confused with the [image_optim] gem):
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
- plugin :derivation_endpoint,
370
- secret_key: "secret",
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
- [Cloudinary] is a popular commercial service for on-the-fly image processing,
385
- so it's a good alternative to the `derivation_endpoint` plugin. The
386
- [shrine-cloudinary] gem provides a Shrine storage that we can set for our
387
- temporary and permanent storage:
388
-
389
- ```rb
390
- # Gemfile
391
- gem "shrine-cloudinary"
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
- [ImageMagick]: https://www.imagemagick.org
421
- [GraphicsMagick]: http://www.graphicsmagick.org
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
- [image_optim]: https://github.com/toy/image_optim
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