shrine 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

@@ -40,9 +40,12 @@ The default URL block is evaluated in the context of an instance of
40
40
  Attacher.default_url do |options|
41
41
  self #=> #<Shrine::Attacher>
42
42
 
43
+ file #=> #<Shrine::UploadedFile>
43
44
  name #=> :avatar
44
45
  record #=> #<User>
45
46
  context #=> { ... }
47
+
48
+ # ...
46
49
  end
47
50
  ```
48
51
 
@@ -44,43 +44,45 @@ end
44
44
  ```
45
45
  ```rb
46
46
  photo = Photo.new(image: file)
47
- photo.image_derivatives! # calls derivatives processor and uploads results
48
- photo.save
49
- ```
50
-
51
- If you're allowing the attached file to be updated later on, in your update
52
- route make sure to create derivatives for new attachments:
53
47
 
54
- ```rb
55
- photo.image_derivatives! if photo.image_changed?
48
+ if photo.valid?
49
+ photo.image_derivatives! if photo.image_changed? # create derivatives
50
+ photo.save
51
+ end
56
52
  ```
57
53
 
58
- You can then retrieve created derivatives as follows:
54
+ You can then retrieve the URL of a processed derivative:
59
55
 
60
56
  ```rb
61
- photo.image(:large) #=> #<Shrine::UploadedFile ...>
62
- photo.image(:large).url #=> "https://s3.amazonaws.com/path/to/large.jpg"
63
- photo.image(:large).size #=> 43843
64
- photo.image(:large).mime_type #=> "image/jpeg"
57
+ photo.image_url(:large) #=> "https://s3.amazonaws.com/path/to/large.jpg"
65
58
  ```
66
59
 
67
- The derivatives data is stored in the `#<name>_data` record attribute, alongside
68
- the main file data:
60
+ The derivatives data is stored in the `<attachment>_data` column alongside the
61
+ main file:
69
62
 
70
63
  ```rb
71
64
  photo.image_data #=>
72
65
  # {
73
- # "id": "original.jpg",
66
+ # "id": "path/to/original.jpg",
74
67
  # "store": "store",
75
68
  # "metadata": { ... },
76
69
  # "derivatives": {
77
- # "small": { "id": "small.jpg", "storage": "store", "metadata": { ... } },
78
- # "medium": { "id": "medium.jpg", "storage": "store", "metadata": { ... } },
79
- # "large": { "id": "large.jpg", "storage": "store", "metadata": { ... } },
70
+ # "small": { "id": "path/to/small.jpg", "storage": "store", "metadata": { ... } },
71
+ # "medium": { "id": "path/to/medium.jpg", "storage": "store", "metadata": { ... } },
72
+ # "large": { "id": "path/to/large.jpg", "storage": "store", "metadata": { ... } },
80
73
  # }
81
74
  # }
82
75
  ```
83
76
 
77
+ And they can be retrieved as `Shrine::UploadedFile` objects:
78
+
79
+ ```rb
80
+ photo.image(:large) #=> #<Shrine::UploadedFile id="path/to/large.jpg" storage=:store metadata={...}>
81
+ photo.image(:large).url #=> "https://s3.amazonaws.com/path/to/large.jpg"
82
+ photo.image(:large).size #=> 5825949
83
+ photo.image(:large).mime_type #=> "image/jpeg"
84
+ ```
85
+
84
86
  ## Retrieving derivatives
85
87
 
86
88
  The list of stored derivatives can be retrieved with `#<name>_derivatives`:
@@ -135,7 +137,7 @@ You can use the [`default_url`][default_url] plugin to set up URL fallbacks:
135
137
 
136
138
  ```rb
137
139
  Attacher.default_url do |derivative: nil, **|
138
- "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
140
+ "/fallbacks/#{derivative}.jpg" if derivative
139
141
  end
140
142
  ```
141
143
  ```rb
@@ -279,11 +281,14 @@ The storage block is evaluated in the context of a `Shrine::Attacher` instance:
279
281
 
280
282
  ```rb
281
283
  Attacher.derivatives_storage do |derivative|
282
- self #=> #<Shrine::Attacher>
284
+ self #=> #<Shrine::Attacher>
283
285
 
286
+ file #=> #<Shrine::UploadedFile>
284
287
  record #=> #<Photo>
285
288
  name #=> :image
286
289
  context #=> { ... }
290
+
291
+ # ...
287
292
  end
288
293
  ```
289
294
 
@@ -352,6 +357,7 @@ which allows you to change your processing logic based on the record data.
352
357
  Attacher.derivatives :my_processor do |original|
353
358
  self #=> #<Shrine::Attacher>
354
359
 
360
+ file #=> #<Shrine::UploadedFile>
355
361
  record #=> #<Photo>
356
362
  name #=> :image
357
363
  context #=> { ... }
@@ -756,11 +762,13 @@ plugin :derivatives
756
762
  Processing derivatives will trigger a `derivatives.shrine` event with the
757
763
  following payload:
758
764
 
759
- | Key | Description |
760
- | :-- | :---- |
761
- | `:processor` | Name of the derivatives processor |
762
- | `:processor_options` | Any options passed to the processor |
763
- | `:uploader` | The uploader class that sent the event |
765
+ | Key | Description |
766
+ | :-- | :---- |
767
+ | `:processor` | Name of the derivatives processor |
768
+ | `:processor_options` | Any options passed to the processor |
769
+ | `:io` | The source file passed to the processor |
770
+ | `:attacher` | The attacher instance doing the processing |
771
+ | `:uploader` | The uploader class that sent the event |
764
772
 
765
773
  A default log subscriber is added as well which logs these events:
766
774
 
@@ -776,7 +784,7 @@ plugin :derivatives, log_subscriber: -> (event) {
776
784
  }
777
785
  ```
778
786
  ```
779
- {"name":"derivatives","duration":2133,"processor":"thumbnails","processor_options":{},"uploader":"ImageUploader"}
787
+ {"name":"derivatives","duration":2133,"processor":"thumbnails","processor_options":{},"io":"#<File:...>","uploader":"ImageUploader"}
780
788
  ```
781
789
 
782
790
  Or disable logging altogether:
@@ -7,15 +7,35 @@ of the attachment module, and call additional methods on the model that
7
7
  includes it.
8
8
 
9
9
  ```rb
10
- plugin :included do |name|
11
- # called when attachment module is included into a model
10
+ class ImageUploader < Shrine
11
+ plugin :included do |name|
12
+ # called when attachment module is included into a model
12
13
 
13
- self #=> #<Photo>
14
- name #=> :image
14
+ self #=> Photo (the model class)
15
+ name #=> :image
16
+ end
15
17
  end
16
18
  ```
17
19
  ```rb
18
- Photo.include Shrine::Attachment(:image)
20
+ class Photo
21
+ include ImageUploader::Attachment(:image) # triggers the included block
22
+ end
23
+ ```
24
+
25
+ For example, you can use it to define additional methods on the model:
26
+
27
+ ```rb
28
+ class ImageUploader < Shrine
29
+ plugin :included do |name|
30
+ define_method(:"#{name}_width") { send(name)&.width }
31
+ define_method(:"#{name}_height") { send(name)&.height }
32
+ end
33
+ end
34
+ ```
35
+ ```rb
36
+ photo = Photo.new(image: file)
37
+ photo.image_width #=> 1200
38
+ photo.image_height #=> 800
19
39
  ```
20
40
 
21
41
  [included]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/included.rb
@@ -11,9 +11,17 @@ plugin :remove_invalid
11
11
  ```
12
12
 
13
13
  ```rb
14
- photo.image = file # invalid file
14
+ # without previous file
15
+ photo.image #=> nil
16
+ photo.image = file # validation fails, assignment is reverted
15
17
  photo.valid? #=> false
16
18
  photo.image #=> nil
19
+
20
+ # with previous file
21
+ photo.image #=> #<Shrine::UploadedFile id="foo" ...>
22
+ photo.image = file # validation fails, assignment is reverted
23
+ photo.valid? #=> false
24
+ photo.image #=> #<Shrine::UploadedFile id="foo" ...>
17
25
  ```
18
26
 
19
27
  [remove_invalid]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/remove_invalid.rb
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: Type Predicates
3
+ ---
4
+
5
+ The [`type_predicates`][type_predicates] plugin adds predicate methods to
6
+ `Shrine::UploadedFile` based on the MIME type. By default, it uses the
7
+ [MiniMime] gem for looking up MIME types.
8
+
9
+ ```rb
10
+ # Gemfile
11
+ gem "mini_mime"
12
+ ```
13
+ ```rb
14
+ Shrine.plugin :type_predicates
15
+ ```
16
+
17
+ ## General predicates
18
+
19
+ The plugin adds four predicate methods based on the general type of the file:
20
+
21
+ ```rb
22
+ file.image? # returns true for any "image/*" MIME type
23
+ file.video? # returns true for any "video/*" MIME type
24
+ file.audio? # returns true for any "audio/*" MIME type
25
+ file.text? # returns true for any "text/*" MIME type
26
+ ```
27
+
28
+ If `mime_type` metadata value is nil, `Shrine::Error` will be raised.
29
+
30
+ ## Specific predicates
31
+
32
+ The `UploadedFile#type?` method takes a file extension, and returns whether the
33
+ `mime_type` metadata value of the uploaded file matches the MIME type
34
+ associated to the given file extension.
35
+
36
+ ```rb
37
+ file.type?(:jpg) # returns true if MIME type is "image/jpeg"
38
+ file.type?(:svg) # returns true if MIME type is "image/svg+xml"
39
+ file.type?(:mov) # returns true if MIME type is "video/quicktime"
40
+ file.type?(:ppt) # returns true if MIME type is "application/vnd.ms-powerpoint"
41
+ ...
42
+ ```
43
+
44
+ For convenience, you can create predicate methods for specific file types:
45
+
46
+ ```rb
47
+ Shrine.plugin :type_predicates, methods: %i[jpg svg mov ppt]
48
+ ```
49
+ ```rb
50
+ file.jpg? # returns true if MIME type is "image/jpeg"
51
+ file.svg? # returns true if MIME type is "image/svg+xml"
52
+ file.mov? # returns true if MIME type is "video/quicktime"
53
+ file.ppt? # returns true if MIME type is "application/vnd.ms-powerpoint"
54
+ ```
55
+
56
+ If `mime_type` metadata value is nil, or the underlying MIME type library
57
+ doesn't recognize a given type, `Shrine::Error` will be raised.
58
+
59
+ ### MIME database
60
+
61
+ The MIME type lookup by file extension is done by the underlying MIME type
62
+ library ([MiniMime] by default). You can change the MIME type library via the
63
+ `:mime` plugin option:
64
+
65
+ ```rb
66
+ Shrine.plugin :type_predicates, mime: :marcel # requires adding "marcel" gem to the Gemfile
67
+ ```
68
+
69
+ The following MIME type libraries are supported:
70
+
71
+ | Name | Description |
72
+ | :---- | :--------- |
73
+ | `:mini_mime` | (**Default**.) Uses [MiniMime] gem to look up MIME type by extension. |
74
+ | `:mime_types` | Uses [mime-types] gem to look up MIME type by extension. |
75
+ | `:mimemagic` | Uses [MimeMagic] gem to look up MIME type by extension. |
76
+ | `:marcel` | Uses [Marcel] gem to look up MIME type by extension. |
77
+ | `:rack_mime` | Uses [Rack::Mime] to look up MIME type by extension. |
78
+
79
+ You can also specify a custom block, which receives the extension and is
80
+ expected to return the corresponding MIME type. Inside the block you can call
81
+ into existing MIME type libraries:
82
+
83
+ ```rb
84
+ Shrine.plugin :type_predicates, mime: -> (extension) do
85
+ mime_type = Shrine.type_lookup(extension, :marcel)
86
+ mime_type ||= Shrine.type_lookup(extension, :mini_mime)
87
+ mime_type
88
+ end
89
+ ```
90
+
91
+ [type_predicates]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/type_predicates.rb
92
+ [MiniMime]: https://github.com/discourse/mini_mime
93
+ [mime-types]: https://github.com/mime-types/ruby-mime-types
94
+ [MimeMagic]: https://github.com/minad/mimemagic
95
+ [Marcel]: https://github.com/basecamp/marcel
96
+ [Rack::Mime]: https://github.com/rack/rack/blob/master/lib/rack/mime.rb
@@ -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
  ```
@@ -2,16 +2,16 @@
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
16
  ```
17
17
  $ brew install imagemagick
@@ -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
@@ -71,27 +75,55 @@ photo.image(:large).size #=> 5825949
71
75
  photo.image(:large).mime_type #=> "image/jpeg"
72
76
  ```
73
77
 
74
- ### Automatic processing
78
+ ### Conditional derivatives
75
79
 
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:
80
+ The `Attacher.derivatives` block is evaluated in context of a
81
+ `Shrine::Attacher` instance:
79
82
 
80
83
  ```rb
81
- class Shrine::Attacher
82
- def promote(*)
83
- create_derivatives
84
- super
84
+ Attacher.derivatives do |original|
85
+ self #=> #<Shrine::Attacher>
86
+
87
+ file #=> #<Shrine::UploadedFile>
88
+ record #=> #<Photo>
89
+ name #=> :image
90
+ context #=> { ... }
91
+
92
+ # ...
93
+ end
94
+ ```
95
+
96
+ This gives you the ability to branch the processing logic based on the
97
+ attachment information:
98
+
99
+ ```rb
100
+ Attacher.derivatives do |original|
101
+ magick = ImageProcessing::MiniMagick.source(original)
102
+ result = {}
103
+
104
+ if record.is_a?(Photo)
105
+ result[:jpg] = magick.convert!("jpeg")
106
+ result[:gray] = magick.colorspace!("grayscale")
85
107
  end
108
+
109
+ if file.mime_type == "image/svg+xml"
110
+ result[:png] = magick.loader(transparent: "white").convert!("png")
111
+ end
112
+
113
+ result
86
114
  end
87
115
  ```
88
116
 
117
+ If you find yourself branching a lot based on MIME type, the
118
+ [`type_predicates`][type_predicates] plugin provides convenient predicate
119
+ methods for that.
120
+
89
121
  ### Backgrounding
90
122
 
91
123
  Since file processing can be time consuming, it's recommended to move it into a
92
124
  background job.
93
125
 
94
- #### With promotion
126
+ #### A) Creating derivatives with promotion
95
127
 
96
128
  The simplest way is to use the [`backgrounding`][backgrounding] plugin to move
97
129
  promotion into a background job, and then create derivatives as part of
@@ -120,7 +152,7 @@ class PromoteJob
120
152
  end
121
153
  ```
122
154
 
123
- #### Separate from promotion
155
+ #### B) Creating derivatives separately from promotion
124
156
 
125
157
  Derivatives don't need to be created as part of the attachment flow, you can
126
158
  create them at any point after promotion:
@@ -151,7 +183,7 @@ class DerivativesJob
151
183
  end
152
184
  ```
153
185
 
154
- #### Concurrent processing
186
+ #### C) Creating derivatives concurrently
155
187
 
156
188
  You can also generate derivatives concurrently:
157
189
 
@@ -204,67 +236,97 @@ class DerivativeJob
204
236
  end
205
237
  ```
206
238
 
207
- ### Processing other filetypes
239
+ ### URL fallbacks
208
240
 
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.
241
+ If you're creating derivatives in a background job, you'll likely want to use
242
+ some fallbacks for derivative URLs while the background job is still
243
+ processing. You can do that with the [`default_url`][default_url] plugin.
214
244
 
215
- To demonstrate, here is an example of transcoding videos using
216
- [streamio-ffmpeg]:
245
+ ```rb
246
+ Shrine.plugin :default_url
247
+ ```
248
+
249
+ #### A) Fallback to original
250
+
251
+ You can fall back to the original file URL when the derivative is missing:
217
252
 
218
253
  ```rb
219
- # Gemfile
220
- gem "streamio-ffmpeg"
254
+ Attacher.default_url do |derivative: nil, **|
255
+ file&.url if derivative
256
+ end
221
257
  ```
222
258
  ```rb
223
- require "streamio-ffmpeg"
259
+ photo.image_url(:large) #=> "https://example.com/path/to/original.jpg"
260
+ # ... background job finishes ...
261
+ photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
262
+ ```
224
263
 
225
- class VideoUploader < Shrine
226
- Attacher.derivatives do |original|
227
- transcoded = Tempfile.new ["transcoded", ".mp4"]
228
- screenshot = Tempfile.new ["screenshot", ".jpg"]
264
+ #### B) Fallback to derivative
229
265
 
230
- movie = FFMPEG::Movie.new(original.path)
231
- movie.transcode(transcoded.path)
232
- movie.screenshot(screenshot.path)
266
+ You can fall back to another derivative URL when the derivative is missing:
233
267
 
234
- { transcoded: transcoded, screenshot: screenshot }
235
- end
268
+ ```rb
269
+ Attacher.default_url do |derivative: nil, **|
270
+ derivatives[:optimized]&.url if derivative
271
+ end
272
+ ```
273
+ ```rb
274
+ photo.image_url(:large) #=> "https://example.com/path/to/optimized.jpg"
275
+ # ... background job finishes ...
276
+ photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
277
+ ```
278
+
279
+ #### C) Fallback to on-the-fly
280
+
281
+ You can also fall back to [on-the-fly processing](#on-the-fly-processing),
282
+ which should generally provide the best user experience.
283
+
284
+ ```rb
285
+ THUMBNAILS = {
286
+ small: [300, 300],
287
+ medium: [500, 500],
288
+ large: [800, 800],
289
+ }
290
+
291
+ Attacher.default_url do |derivative: nil, **|
292
+ file&.derivation_url(:thumbnail, *THUMBNAILS.fetch(derivative)) if derivative
236
293
  end
237
294
  ```
295
+ ```rb
296
+ photo.image_url(:large) #=> "../derivations/thumbnail/800/800/..."
297
+ # ... background job finishes ...
298
+ photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
299
+ ```
238
300
 
239
301
  ## On-the-fly processing
240
302
 
241
- Generating image thumbnails on upload can be a pain to maintain, because
303
+ Having eagerly created image thumbnails can be a pain to maintain, because
242
304
  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).
305
+ retroactively apply it to all existing attachments (see the [Managing
306
+ Derivatives] guide for more details).
245
307
 
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.
308
+ Sometimes it makes more sense to generate thumbnails dynamically as they're
309
+ requested, and then cache them for future requests. This strategy is known as
310
+ processing "**on-the-fly**" or "**on-demand**", and it's suitable for
311
+ short-running processing such as creating image thumbnails or document
312
+ previews.
250
313
 
251
314
  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:
315
+ **[`derivation_endpoint`][derivation_endpoint]** plugin. You set it up by
316
+ loading the plugin with a secret key and a path prefix, mount its Rack app in
317
+ your routes on the configured path prefix, and define processing you want to
318
+ perform:
260
319
 
320
+ ```rb
321
+ # config/initializers/shrine.rb (Rails)
322
+ # ...
323
+ Shrine.plugin :derivation_endpoints, secret_key: "<YOUR_SECRET_KEY>"
324
+ ```
261
325
  ```rb
262
326
  require "image_processing/mini_magick"
263
327
 
264
328
  class ImageUploader < Shrine
265
- plugin :derivation_endpoint,
266
- secret_key: "<YOUR SECRET KEY>",
267
- prefix: "derivations/image"
329
+ plugin :derivation_endpoint, prefix: "derivations/image" # matches mount point
268
330
 
269
331
  derivation :thumbnail do |file, width, height|
270
332
  ImageProcessing::MiniMagick
@@ -273,11 +335,11 @@ class ImageUploader < Shrine
273
335
  end
274
336
  end
275
337
  ```
276
-
277
338
  ```rb
278
339
  # config/routes.rb (Rails)
279
340
  Rails.application.routes.draw do
280
- mount ImageUploader.derivation_endpoint => "derivations/image"
341
+ # ...
342
+ mount ImageUploader.derivation_endpoint => "/derivations/image"
281
343
  end
282
344
  ```
283
345
 
@@ -293,8 +355,137 @@ The plugin is highly customizable, be sure to check out the
293
355
  [documentation][derivation_endpoint], especially the [performance
294
356
  section][derivation_endpoint performance].
295
357
 
358
+ ### Dynamic derivation
359
+
360
+ If you have multiple types of transformations and don't want to have a
361
+ derivation for each one, you can set up a single derivation that applies any
362
+ series of transformations:
363
+
364
+ ```rb
365
+ class ImageUploader < Shrine
366
+ derivation :transform do |original, transformations|
367
+ transformations = Shrine.urlsafe_deserialize(transformations)
368
+
369
+ vips = ImageProcessing::Vips.source(original)
370
+ vips.apply!(transformations)
371
+ end
372
+ end
373
+ ```
374
+ ```rb
375
+ photo.image.derivation_url :transform, Shrine.urlsafe_serialize(
376
+ crop: [10, 10, 500, 500],
377
+ resize_to_fit: [300, 300],
378
+ gaussblur: 1,
379
+ )
380
+ ```
381
+
382
+ You can create a helper method for convenience:
383
+
384
+ ```rb
385
+ def derivation_url(file, transformations)
386
+ file.derivation_url(:transform, Shrine.urlsafe_serialize(transformations))
387
+ end
388
+ ```
389
+ ```rb
390
+ derivation_url photo.image,
391
+ crop: [10, 10, 500, 500],
392
+ resize_to_fit: [300, 300],
393
+ gaussblur: 1
394
+ ```
395
+
396
+ ## Processing other filetypes
397
+
398
+ So far we've only been talking about processing images. However, there is
399
+ nothing image-specific in Shrine's processing API, you can just as well process
400
+ any other types of files. The processing tool doesn't need to have any special
401
+ Shrine integration, the ImageProcessing gem that we saw earlier is a completely
402
+ generic gem.
403
+
404
+ To demonstrate, here is an example of transcoding videos using
405
+ [streamio-ffmpeg]:
406
+
407
+ ```rb
408
+ # Gemfile
409
+ gem "streamio-ffmpeg"
410
+ ```
411
+ ```rb
412
+ require "streamio-ffmpeg"
413
+
414
+ class VideoUploader < Shrine
415
+ Attacher.derivatives do |original|
416
+ transcoded = Tempfile.new ["transcoded", ".mp4"]
417
+ screenshot = Tempfile.new ["screenshot", ".jpg"]
418
+
419
+ movie = FFMPEG::Movie.new(original.path)
420
+ movie.transcode(transcoded.path)
421
+ movie.screenshot(screenshot.path)
422
+
423
+ { transcoded: transcoded, screenshot: screenshot }
424
+ end
425
+ end
426
+ ```
427
+
428
+ ### Polymorphic uploader
429
+
430
+ Sometimes you might want an attachment attribute to accept multiple types of
431
+ files, and apply different processing depending on the type. Since Shrine's
432
+ processing blocks are evaluated dynamically, you can use conditional logic:
433
+
434
+ ```rb
435
+ class PolymorphicUploader < Shrine
436
+ IMAGE_TYPES = %w[image/jpeg image/png image/webp]
437
+ VIDEO_TYPES = %w[video/mp4 video/quicktime]
438
+ PDF_TYPES = %w[application/pdf]
439
+
440
+ Attacher.validate do
441
+ validate_mime_type IMAGE_TYPES + VIDEO_TYPES + PDF_TYPES
442
+ # ...
443
+ end
444
+
445
+ Attacher.derivatives do |original|
446
+ case file.mime_type
447
+ when *IMAGE_TYPES then process_derivatives(:image, original)
448
+ when *VIDEO_TYPES then process_derivatives(:video, original)
449
+ when *PDF_TYPES then process_derivatives(:pdf, original)
450
+ end
451
+ end
452
+
453
+ Attacher.derivatives :image do |original|
454
+ # ...
455
+ end
456
+
457
+ Attacher.derivatives :video do |original|
458
+ # ...
459
+ end
460
+
461
+ Attacher.derivatives :pdf do |original|
462
+ # ...
463
+ end
464
+ end
465
+ ```
466
+
296
467
  ## Extras
297
468
 
469
+ ### Automatic derivatives
470
+
471
+ If you would like derivatives to be automatically created with promotion, you
472
+ can override `Attacher#promote` for call `Attacher#create_derivatives` before
473
+ promotion:
474
+
475
+ ```rb
476
+ class Shrine::Attacher
477
+ def promote(*)
478
+ create_derivatives
479
+ super
480
+ end
481
+ end
482
+ ```
483
+
484
+ This shouldn't be needed if you're processing in the
485
+ [background](#backgrounding), as in that case you have a background worker that
486
+ will be called for each attachment, so you can call
487
+ `Attacher#create_derivatives` there.
488
+
298
489
  ### libvips
299
490
 
300
491
  As mentioned, ImageProcessing gem also has an alternative backend for
@@ -304,7 +495,7 @@ characteristics – it's often **multiple times faster** than ImageMagick and ha
304
495
  low memory usage (see [Why is libvips quick]).
305
496
 
306
497
  Using libvips is as easy as installing it and switching to the
307
- `ImageProcessing::Vips` backend:
498
+ [`ImageProcessing::Vips`][ImageProcessing::Vips] backend:
308
499
 
309
500
  ```
310
501
  $ brew install vips
@@ -326,7 +517,7 @@ thumbnail = ImageProcessing::Vips
326
517
  thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
327
518
  ```
328
519
 
329
- ### Parallelize uploading
520
+ ### Parallelize uploads
330
521
 
331
522
  If you're generating derivatives, you can parallelize the uploads using the
332
523
  [concurrent-ruby] gem:
@@ -351,86 +542,60 @@ Concurrent::Promises.zip(*tasks).wait!
351
542
 
352
543
  ### External processing
353
544
 
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):
545
+ You can also integrate Shrine with 3rd-party processing services such as
546
+ [Cloudinary] and [Imgix]. In the most common case, you'd serve images directly
547
+ from these services, see the corresponding plugin docs for more details
548
+ ([shrine-cloudinary], [shrine-imgix] and [others][external storages])
549
+
550
+ You can also choose to use these services as an implementation detail of your
551
+ application, by downloading the processed images and saving them to your
552
+ storage. Here is how you might store files processed by Imgix as derivatives:
358
553
 
359
554
  ```rb
360
555
  # Gemfile
361
556
  gem "down", "~> 5.0"
362
557
  gem "http", "~> 4.0"
558
+ gem "shrine-imgix", "~> 0.5"
559
+ ```
560
+ ```rb
561
+ Shrine.plugin :derivatives
562
+ Shrine.plugin :imgix, client: { host: "my-app.imgix.net", secure_url_token: "secret" }
363
563
  ```
364
-
365
564
  ```rb
366
565
  require "down/http"
367
566
 
368
567
  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}")
568
+ IMGIX_THUMBNAIL = -> (file, width, height) do
569
+ Down::Http.download(file.imgix_url(w: width, h: height))
378
570
  end
379
- end
380
- ```
381
571
 
382
- ### Cloudinary
383
-
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"
572
+ Attacher.derivatives do
573
+ {
574
+ large: IMGIX_THUMBNAIL[file, 800, 800],
575
+ medium: IMGIX_THUMBNAIL[file, 500, 500],
576
+ small: IMGIX_THUMBNAIL[file, 300, 300],
577
+ }
578
+ end
579
+ end
416
580
  ```
417
581
 
418
582
  [`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
419
583
  [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
584
+ [ImageProcessing::MiniMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
585
+ [ImageProcessing::Vips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
425
586
  [streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
426
587
  [Managing Derivatives]: https://shrinerb.com/docs/changing-derivatives
427
- [Cloudinary]: https://cloudinary.com
588
+ [Cloudinary]: https://cloudinary.com/
589
+ [Imgix]: https://www.imgix.com/
428
590
  [shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
591
+ [shrine-imgix]: https://github.com/shrinerb/shrine-imgix
429
592
  [backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
430
- [ruby-vips]: https://github.com/libvips/ruby-vips
431
- [MiniMagick]: https://github.com/minimagick/minimagick
432
593
  [derivation_endpoint]: https://shrinerb.com/docs/plugins/derivation_endpoint
433
594
  [derivation_endpoint performance]: https://shrinerb.com/docs/plugins/derivation_endpoint#performance
434
595
  [derivatives]: https://shrinerb.com/docs/plugins/derivatives
435
596
  [concurrent-ruby]: https://github.com/ruby-concurrency/concurrent-ruby
436
- [image_optim]: https://github.com/toy/image_optim
597
+ [default_url]: https://shrinerb.com/docs/plugins/default_url
598
+ [external storages]: https://shrinerb.com/docs/external/extensions#storages
599
+ [libvips]: https://libvips.github.io/libvips/
600
+ [Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
601
+ [type_predicates]: https://shrinerb.com/docs/plugins/type_predicates