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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +3 -3
- data/doc/advantages.md +3 -3
- data/doc/carrierwave.md +17 -8
- data/doc/design.md +134 -85
- data/doc/external/articles.md +1 -0
- data/doc/external/extensions.md +38 -35
- data/doc/getting_started.md +135 -79
- data/doc/metadata.md +79 -43
- data/doc/paperclip.md +13 -4
- data/doc/plugins/add_metadata.md +92 -35
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/default_url.md +3 -0
- data/doc/plugins/derivatives.md +35 -27
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/url_options.md +2 -2
- data/doc/plugins/validation.md +5 -4
- data/doc/processing.md +286 -121
- data/doc/refile.md +9 -9
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/securing_uploads.md +1 -1
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +102 -81
- data/doc/testing.md +2 -2
- data/doc/upgrading_to_3.md +94 -32
- data/lib/shrine.rb +3 -2
- data/lib/shrine/attacher.rb +14 -9
- data/lib/shrine/plugins/add_metadata.rb +13 -0
- data/lib/shrine/plugins/derivatives.rb +8 -7
- data/lib/shrine/plugins/determine_mime_type.rb +3 -3
- data/lib/shrine/plugins/model.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/validation.rb +8 -6
- data/lib/shrine/storage/s3.rb +34 -28
- data/lib/shrine/version.rb +1 -1
- metadata +7 -3
data/doc/plugins/default_url.md
CHANGED
@@ -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
|
|
data/doc/plugins/derivatives.md
CHANGED
@@ -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
|
-
|
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
|
54
|
+
You can then retrieve the URL of a processed derivative:
|
59
55
|
|
60
56
|
```rb
|
61
|
-
photo.
|
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
|
68
|
-
|
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
|
-
"
|
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
|
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
|
-
| `:
|
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:
|
data/doc/plugins/included.md
CHANGED
@@ -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
|
-
|
11
|
-
|
10
|
+
class ImageUploader < Shrine
|
11
|
+
plugin :included do |name|
|
12
|
+
# called when attachment module is included into a model
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
self #=> Photo (the model class)
|
15
|
+
name #=> :image
|
16
|
+
end
|
15
17
|
end
|
16
18
|
```
|
17
19
|
```rb
|
18
|
-
Photo
|
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
|
-
|
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
|
data/doc/plugins/url_options.md
CHANGED
@@ -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
|
```
|
data/doc/processing.md
CHANGED
@@ -2,16 +2,16 @@
|
|
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
|
@@ -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
|
@@ -71,27 +75,55 @@ photo.image(:large).size #=> 5825949
|
|
71
75
|
photo.image(:large).mime_type #=> "image/jpeg"
|
72
76
|
```
|
73
77
|
|
74
|
-
###
|
78
|
+
### Conditional derivatives
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
promotion:
|
80
|
+
The `Attacher.derivatives` block is evaluated in context of a
|
81
|
+
`Shrine::Attacher` instance:
|
79
82
|
|
80
83
|
```rb
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
####
|
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
|
-
####
|
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
|
-
####
|
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
|
-
###
|
239
|
+
### URL fallbacks
|
208
240
|
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
-
|
216
|
-
|
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
|
-
|
220
|
-
|
254
|
+
Attacher.default_url do |derivative: nil, **|
|
255
|
+
file&.url if derivative
|
256
|
+
end
|
221
257
|
```
|
222
258
|
```rb
|
223
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
235
|
-
|
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
|
-
|
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
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
processing such as creating image thumbnails or document
|
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.
|
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:
|
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
|
-
|
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
|
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
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
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
|
-
|
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}")
|
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
|
-
|
383
|
-
|
384
|
-
[
|
385
|
-
|
386
|
-
[
|
387
|
-
|
388
|
-
|
389
|
-
|
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
|
-
[
|
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
|
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
|
-
[
|
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
|