shrine 2.14.0 → 2.15.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.

Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +384 -374
  3. data/README.md +132 -63
  4. data/doc/advantages.md +191 -109
  5. data/doc/attacher.md +1 -1
  6. data/doc/carrierwave.md +4 -4
  7. data/doc/creating_storages.md +2 -2
  8. data/doc/design.md +2 -2
  9. data/doc/direct_s3.md +3 -3
  10. data/doc/metadata.md +1 -1
  11. data/doc/multiple_files.md +2 -2
  12. data/doc/paperclip.md +3 -3
  13. data/doc/plugins/activerecord.md +92 -0
  14. data/doc/plugins/add_metadata.md +93 -0
  15. data/doc/plugins/backgrounding.md +148 -0
  16. data/doc/plugins/backup.md +29 -0
  17. data/doc/plugins/cached_attachment_data.md +23 -0
  18. data/doc/plugins/copy.md +22 -0
  19. data/doc/plugins/data_uri.md +92 -0
  20. data/doc/plugins/default_storage.md +16 -0
  21. data/doc/plugins/default_url.md +33 -0
  22. data/doc/plugins/default_url_options.md +22 -0
  23. data/doc/plugins/delete_promoted.md +10 -0
  24. data/doc/plugins/delete_raw.md +16 -0
  25. data/doc/plugins/derivation_endpoint.md +747 -0
  26. data/doc/plugins/determine_mime_type.md +64 -0
  27. data/doc/plugins/direct_upload.md +170 -0
  28. data/doc/plugins/download_endpoint.md +83 -0
  29. data/doc/plugins/dynamic_storage.md +20 -0
  30. data/doc/plugins/hooks.md +56 -0
  31. data/doc/plugins/included.md +15 -0
  32. data/doc/plugins/infer_extension.md +57 -0
  33. data/doc/plugins/keep_files.md +20 -0
  34. data/doc/plugins/logging.md +39 -0
  35. data/doc/plugins/metadata_attribues.md +43 -0
  36. data/doc/plugins/migration_helpers.md +58 -0
  37. data/doc/plugins/module_include.md +40 -0
  38. data/doc/plugins/moving.md +17 -0
  39. data/doc/plugins/multi_delete.md +18 -0
  40. data/doc/plugins/parallelize.md +14 -0
  41. data/doc/plugins/parsed_json.md +9 -0
  42. data/doc/plugins/presign_endpoint.md +133 -0
  43. data/doc/plugins/pretty_location.md +29 -0
  44. data/doc/plugins/processing.md +68 -0
  45. data/doc/plugins/rack_file.md +49 -0
  46. data/doc/plugins/rack_response.md +96 -0
  47. data/doc/plugins/recache.md +27 -0
  48. data/doc/plugins/refresh_metadata.md +31 -0
  49. data/doc/plugins/remote_url.md +104 -0
  50. data/doc/plugins/remove_attachment.md +16 -0
  51. data/doc/plugins/remove_invalid.md +9 -0
  52. data/doc/plugins/restore_cached_data.md +14 -0
  53. data/doc/plugins/sequel.md +64 -0
  54. data/doc/plugins/signature.md +49 -0
  55. data/doc/plugins/store_dimensions.md +68 -0
  56. data/doc/plugins/tempfile.md +40 -0
  57. data/doc/plugins/upload_endpoint.md +123 -0
  58. data/doc/plugins/upload_options.md +28 -0
  59. data/doc/plugins/validation_helpers.md +129 -0
  60. data/doc/plugins/versions.md +179 -0
  61. data/doc/processing.md +217 -247
  62. data/doc/refile.md +3 -3
  63. data/doc/release_notes/1.0.0.md +143 -0
  64. data/doc/release_notes/1.1.0.md +184 -0
  65. data/doc/release_notes/1.2.0.md +37 -0
  66. data/doc/release_notes/1.3.0.md +90 -0
  67. data/doc/release_notes/1.4.0.md +167 -0
  68. data/doc/release_notes/1.4.1.md +9 -0
  69. data/doc/release_notes/1.4.2.md +20 -0
  70. data/doc/release_notes/2.0.0.md +173 -0
  71. data/doc/release_notes/2.0.1.md +12 -0
  72. data/doc/release_notes/2.1.0.md +59 -0
  73. data/doc/release_notes/2.1.1.md +8 -0
  74. data/doc/release_notes/2.10.0.md +52 -0
  75. data/doc/release_notes/2.10.1.md +6 -0
  76. data/doc/release_notes/2.11.0.md +69 -0
  77. data/doc/release_notes/2.12.0.md +65 -0
  78. data/doc/release_notes/2.13.0.md +146 -0
  79. data/doc/release_notes/2.14.0.md +278 -0
  80. data/doc/release_notes/2.15.0.md +82 -0
  81. data/doc/release_notes/2.2.0.md +98 -0
  82. data/doc/release_notes/2.3.0.md +50 -0
  83. data/doc/release_notes/2.3.1.md +10 -0
  84. data/doc/release_notes/2.4.0.md +87 -0
  85. data/doc/release_notes/2.4.1.md +29 -0
  86. data/doc/release_notes/2.5.0.md +130 -0
  87. data/doc/release_notes/2.6.0.md +254 -0
  88. data/doc/release_notes/2.6.1.md +14 -0
  89. data/doc/release_notes/2.7.0.md +180 -0
  90. data/doc/release_notes/2.8.0.md +95 -0
  91. data/doc/release_notes/2.9.0.md +82 -0
  92. data/doc/retrieving_uploads.md +1 -1
  93. data/doc/storage/file_system.md +96 -0
  94. data/doc/storage/s3.md +293 -0
  95. data/doc/validation.md +1 -1
  96. data/lib/shrine/plugins/_urlsafe_serialization.rb +33 -125
  97. data/lib/shrine/plugins/activerecord.rb +0 -78
  98. data/lib/shrine/plugins/add_metadata.rb +0 -80
  99. data/lib/shrine/plugins/backgrounding.rb +0 -134
  100. data/lib/shrine/plugins/backup.rb +0 -22
  101. data/lib/shrine/plugins/cached_attachment_data.rb +0 -15
  102. data/lib/shrine/plugins/copy.rb +0 -14
  103. data/lib/shrine/plugins/data_uri.rb +0 -73
  104. data/lib/shrine/plugins/default_storage.rb +0 -11
  105. data/lib/shrine/plugins/default_url.rb +0 -25
  106. data/lib/shrine/plugins/default_url_options.rb +0 -16
  107. data/lib/shrine/plugins/delete_promoted.rb +0 -6
  108. data/lib/shrine/plugins/delete_raw.rb +0 -10
  109. data/lib/shrine/plugins/derivation_endpoint.rb +652 -0
  110. data/lib/shrine/plugins/determine_mime_type.rb +1 -85
  111. data/lib/shrine/plugins/direct_upload.rb +0 -155
  112. data/lib/shrine/plugins/download_endpoint.rb +11 -73
  113. data/lib/shrine/plugins/dynamic_storage.rb +0 -17
  114. data/lib/shrine/plugins/hooks.rb +0 -48
  115. data/lib/shrine/plugins/included.rb +0 -12
  116. data/lib/shrine/plugins/infer_extension.rb +0 -49
  117. data/lib/shrine/plugins/keep_files.rb +0 -19
  118. data/lib/shrine/plugins/logging.rb +0 -39
  119. data/lib/shrine/plugins/metadata_attributes.rb +0 -35
  120. data/lib/shrine/plugins/migration_helpers.rb +0 -50
  121. data/lib/shrine/plugins/module_include.rb +0 -32
  122. data/lib/shrine/plugins/moving.rb +0 -12
  123. data/lib/shrine/plugins/multi_delete.rb +0 -13
  124. data/lib/shrine/plugins/parallelize.rb +0 -8
  125. data/lib/shrine/plugins/parsed_json.rb +0 -5
  126. data/lib/shrine/plugins/presign_endpoint.rb +2 -117
  127. data/lib/shrine/plugins/pretty_location.rb +0 -22
  128. data/lib/shrine/plugins/processing.rb +0 -55
  129. data/lib/shrine/plugins/rack_file.rb +0 -39
  130. data/lib/shrine/plugins/rack_response.rb +0 -81
  131. data/lib/shrine/plugins/recache.rb +0 -21
  132. data/lib/shrine/plugins/refresh_metadata.rb +0 -24
  133. data/lib/shrine/plugins/remote_url.rb +0 -85
  134. data/lib/shrine/plugins/remove_attachment.rb +0 -10
  135. data/lib/shrine/plugins/remove_invalid.rb +0 -6
  136. data/lib/shrine/plugins/restore_cached_data.rb +0 -10
  137. data/lib/shrine/plugins/sequel.rb +0 -54
  138. data/lib/shrine/plugins/signature.rb +0 -37
  139. data/lib/shrine/plugins/store_dimensions.rb +0 -63
  140. data/lib/shrine/plugins/tempfile.rb +4 -35
  141. data/lib/shrine/plugins/upload_endpoint.rb +2 -109
  142. data/lib/shrine/plugins/upload_options.rb +0 -20
  143. data/lib/shrine/plugins/validation_helpers.rb +0 -36
  144. data/lib/shrine/plugins/versions.rb +0 -156
  145. data/lib/shrine/storage/file_system.rb +0 -77
  146. data/lib/shrine/storage/s3.rb +0 -249
  147. data/lib/shrine/version.rb +1 -1
  148. data/shrine.gemspec +2 -2
  149. metadata +86 -6
@@ -1,154 +1,98 @@
1
1
  # File Processing
2
2
 
3
- Shrine allows you to process files before they're uploaded to a storage. It's
4
- generally best to process cached files when they're being promoted to permanent
5
- storage, because (a) at that point the file has already been successfully
6
- validated, (b) the parent record has been saved and the database transaction
7
- has been committed, and (c) this can be delayed into a background job.
3
+ Shrine allows you to process files in two ways. One is processing "[on
4
+ upload](#processing-on-upload)", where the processing gets triggered when the file is
5
+ attached to a record. The other is "[on-the-fly](#on-the-fly-processing)"
6
+ processing, where the processing is performed lazily at the moment the file is
7
+ requested.
8
8
 
9
- You can define processing using the `processing` plugin, which we'll use to
10
- hook into the `:store` phase (when cached file is uploaded to permanent
11
- storage).
9
+ With both ways you need to define some kind of processing block, which accepts
10
+ a source file and is expected to return the processed result file.
12
11
 
13
12
  ```rb
14
- class ImageUploader < Shrine
15
- plugin :processing
16
-
17
- process(:store) do |io, context|
18
- io #=> #<Shrine::UploadedFile ...>
19
- context #=> {:record=>#<Photo...>,:name=>:image,...}
20
- end
13
+ some_process_block do |source_file|
14
+ # process source file and return the result
21
15
  end
22
16
  ```
23
17
 
24
- The processing block yields two arguments: `io`, a [`Shrine::UploadedFile`]
25
- object that's uploaded to temporary storage, and `context`, a Hash that
26
- contains additional data such as the model instance and attachment name. The
27
- block result should be file(s) that will be uploaded to permanent storage.
28
-
29
- Shrine treats processing as a functional transformation; you are given the
30
- original file, and how you're going to perform processing is entirely up to
31
- you, you only need to return the processed files at the end of the block that
32
- you want to save. Then Shrine will continue to upload those files to the
33
- storage. Note that **it's recommended to always keep the original file**, just
34
- in case you'll ever need to reprocess it.
35
-
36
- It's a good idea to also load the `delete_raw` plugin to automatically delete
37
- processed files after they're uploaded.
18
+ How you're going to implement processing is entirely up to you. For images it's
19
+ recommended to use the **[ImageProcessing]** gem, which provides wrappers for
20
+ processing with [ImageMagick]/[GraphicsMagick] (using the [MiniMagick] gem) or
21
+ [libvips] (using the [ruby-vips] gem; see the [libvips section](#libvips)).
22
+ Here is an example of generating a thumbnail with ImageProcessing:
38
23
 
39
- ```rb
40
- class ImageUploader < Shrine
41
- plugin :processing
42
- plugin :delete_raw # automatically delete processed files after uploading
43
-
44
- # ...
45
- end
24
+ ```sh
25
+ $ brew install imagemagick
46
26
  ```
47
27
 
48
- ## Single file
49
-
50
- Let's say that you have an image that you want to optimize before it's saved
51
- to permanent storage. This is how you might do it with the [image_optim] gem:
52
-
53
28
  ```rb
54
29
  # Gemfile
55
- gem "image_optim"
56
- gem "image_optim_pack" # precompiled binaries
30
+ gem "image_processing", "~> 1.0"
57
31
  ```
58
32
 
59
33
  ```rb
60
- require "image_optim"
61
-
62
- class ImageUploader < Shrine
63
- plugin :processing
64
- plugin :delete_raw
65
-
66
- process(:store) do |io, context|
67
- original = io.download
68
-
69
- image_optim = ImageOptim.new
70
- optimized_path = image_optim.optimize_image(original.path)
34
+ require "image_processing/mini_magick"
71
35
 
72
- original.close!
36
+ thumbnail = ImageProcessing::MiniMagick
37
+ .source(image)
38
+ .resize_to_limit!(600, 400)
73
39
 
74
- File.open(optimized_path, binmode: true)
75
- end
76
- end
40
+ thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
77
41
  ```
78
42
 
79
- Notice that, because the image_optim gem works with files on disk, we had to
80
- download the cached file from temporary storage before optimizing it.
81
- Afterwards we also close and delete it using `Tempfile#close!`.
82
-
83
- ## Versions
43
+ ## Processing on upload
84
44
 
85
- When you're handling images, it's very common to want to generate various
86
- thumbnails from the original image, and display them on your site. It's
87
- recommended to use the **[ImageProcessing]** gem for generating image
88
- thumbnails, as it has a convenient and flexible API, and comes with good
89
- defaults for the web.
90
-
91
- Since we'll be storing multiple derivates of the original file, we'll need to
92
- also load the `versions` plugin, which allows us to return a Hash of processed
93
- files. For processing we'll be using the `ImageProcessing::MiniMagick` backend,
94
- which performs processing with [ImageMagick] or [GraphicsMagick].
45
+ Shrine allows you to process files before they're uploaded to a storage. It's
46
+ generally best to process cached files when they're being promoted to permanent
47
+ storage, because (a) at that point the file has already been successfully
48
+ [validated][validation], (b) the parent record has been saved and the database
49
+ transaction has been committed, and (c) this can be delayed into a [background
50
+ job][backgrounding].
95
51
 
96
- ```sh
97
- $ brew install imagemagick
98
- ```
99
- ```rb
100
- # Gemfile
101
- gem "image_processing", "~> 1.0"
102
- ```
52
+ You can define processing using the `processing` plugin, which we'll use to
53
+ hook into the `:store` phase (when cached file is uploaded to permanent
54
+ storage).
103
55
 
104
56
  ```rb
105
- require "image_processing/mini_magick"
106
-
107
57
  class ImageUploader < Shrine
108
58
  plugin :processing
109
- plugin :versions
110
- plugin :delete_raw
111
59
 
112
60
  process(:store) do |io, context|
113
- versions = { original: io } # retain original
114
-
115
- io.download do |original|
116
- pipeline = ImageProcessing::MiniMagick.source(original)
117
-
118
- versions[:large] = pipeline.resize_to_limit!(800, 800)
119
- versions[:medium] = pipeline.resize_to_limit!(500, 500)
120
- versions[:small] = pipeline.resize_to_limit!(300, 300)
121
- end
61
+ io #=> #<Shrine::UploadedFile ...>
62
+ context #=> {:record=>#<Photo...>,:name=>:image,...}
122
63
 
123
- versions # return the hash of processed files
64
+ # ...
124
65
  end
125
66
  end
126
67
  ```
127
68
 
128
- ### libvips
69
+ The processing block yields two arguments: a [`Shrine::UploadedFile`] object
70
+ representing the file uploaded to temporary storage, and a Hash containing
71
+ additional data such as the model instance and attachment name. The block
72
+ result should be file(s) that will be uploaded to permanent storage.
129
73
 
130
- Alternatively, you can also process files with **[libvips]**, which has shown
131
- to be multiple times faster than ImageMagick, with lower memory usage on top of
132
- that (see [Why is libvips quick]). Using libvips is as easy as installing libvips
133
- and switching to the `ImageProcessing::Vips` backend.
74
+ ### Versions
134
75
 
135
- ```sh
136
- $ brew install vips
137
- ```
76
+ Let's say we're handling images, and want to generate thumbnails of various
77
+ dimensions. In this case we can use the ImageProcessing gem to generate the
78
+ thumbnails, and return a hash of processed files at the end of the block. We'll
79
+ need to load the `versions` plugin which extends Shrine with the ability to
80
+ handle collections of files inside the same attachment.
138
81
 
139
82
  ```rb
140
- require "image_processing/vips"
83
+ require "image_processing/mini_magick"
141
84
 
142
85
  class ImageUploader < Shrine
143
- plugin :processing
144
- plugin :versions
145
- plugin :delete_raw
86
+ plugin :processing # allows hooking into promoting
87
+ plugin :versions # enable Shrine to handle a hash of files
88
+ plugin :delete_raw # delete processed files after uploading
146
89
 
147
90
  process(:store) do |io, context|
148
91
  versions = { original: io } # retain original
149
92
 
93
+ # download the uploaded file from the temporary storage
150
94
  io.download do |original|
151
- pipeline = ImageProcessing::Vips.source(original) # instead of ImageProcessing::MiniMagick
95
+ pipeline = ImageProcessing::MiniMagick.source(original)
152
96
 
153
97
  versions[:large] = pipeline.resize_to_limit!(800, 800)
154
98
  versions[:medium] = pipeline.resize_to_limit!(500, 500)
@@ -160,89 +104,43 @@ class ImageUploader < Shrine
160
104
  end
161
105
  ```
162
106
 
163
- ### External
164
-
165
- Since processing is so dynamic, you're not limited to using the ImageProcessing
166
- gem, you can also use a 3rd-party service to generate thumbnails for you. Here
167
- is the same example as above, but this time using [ImageOptim.com] to do the
168
- processing (not to be confused with the [image_optim] gem):
169
-
170
- ```rb
171
- # Gemfile
172
- gem "down", "~> 4.4"
173
- gem "http", "~> 3.2"
174
- ```
175
-
176
- ```rb
177
- require "down/http"
178
-
179
- class ImageUploader < Shrine
180
- plugin :processing
181
- plugin :versions
182
- plugin :delete_raw
183
-
184
- IMAGE_OPTIM_URL = "https://im2.io/<USERNAME>"
185
-
186
- process(:store) do |io, context|
187
- down = Down::Http.new(method: :post)
188
-
189
- size_800 = down.download("#{IMAGE_OPTIM_URL}/800x800/#{io.url}")
190
- size_500 = down.download("#{IMAGE_OPTIM_URL}/500x500/#{io.url}")
191
- size_300 = down.download("#{IMAGE_OPTIM_URL}/300x300/#{io.url}")
192
-
193
- { original: io, large: size_800, medium: size_500, small: size_300 }
194
- end
195
- end
196
- ```
197
-
198
- We used the [Down] gem to download response bodies into tempfiles, specifically
199
- its [HTTP.rb] backend, as it supports changing the request method and uses an
200
- order of magnitude less memory than the default backend. Notice that we didn't
201
- have to download the original file from temporary storage as ImageOptim.com
202
- allows us to provide a URL.
107
+ **NOTE: It's recommended to always keep the original file, just in case you'll
108
+ ever need to reprocess it.**
203
109
 
204
- ## Conditional processing
110
+ ### Conditional processing
205
111
 
206
- As we've seen, Shrine's processing API allows us to process files with regular
207
- Ruby code. This means that we can make processing dynamic by using regular Ruby
208
- conditionals.
112
+ The process block yields the attached file uploaded to temporary storage, so we
113
+ have information like file extension and MIME type available. Together with
114
+ ImageProcessing's chainable API, it's easy to do conditional proccessing.
209
115
 
210
116
  For example, let's say we want our thumbnails to be either JPEGs or PNGs, and
211
117
  we also want to save JPEGs as progressive (interlaced). Here's how the code for
212
118
  this might look like:
213
119
 
214
120
  ```rb
215
- require "image_processing/vips"
216
-
217
- class ImageUploader < Shrine
218
- plugin :processing
219
- plugin :versions
220
- plugin :delete_raw
221
-
222
- process(:store) do |io, context|
223
- versions = { original: io } # retain original
224
-
225
- io.download do |original|
226
- pipeline = ImageProcessing::Vips.source(original)
121
+ process(:store) do |io, context|
122
+ versions = { original: io }
227
123
 
228
- # Shrine::UploadedFile object contains information about the MIME type
229
- unless %w[image/png].include?(io.mime_type)
230
- pipeline = pipeline
231
- .convert("jpeg")
232
- .saver(interlace: true)
233
- end
124
+ io.download do |original|
125
+ pipeline = ImageProcessing::Vips.source(original)
234
126
 
235
- versions[:large] = pipeline.resize_to_limit!(800, 800)
236
- versions[:medium] = pipeline.resize_to_limit!(500, 500)
237
- versions[:small] = pipeline.resize_to_limit!(300, 300)
127
+ # Shrine::UploadedFile object contains information about the MIME type
128
+ unless io.mime_type == "image/png"
129
+ pipeline = pipeline
130
+ .convert("jpeg")
131
+ .saver(interlace: true)
238
132
  end
239
133
 
240
- versions # return the hash of processed files
134
+ versions[:large] = pipeline.resize_to_limit!(800, 800)
135
+ versions[:medium] = pipeline.resize_to_limit!(500, 500)
136
+ versions[:small] = pipeline.resize_to_limit!(300, 300)
241
137
  end
138
+
139
+ versions
242
140
  end
243
141
  ```
244
142
 
245
- ## Processing other file types
143
+ ### Processing other file types
246
144
 
247
145
  So far we've only been talking about processing images. However, there is
248
146
  nothing image-specific in Shrine's processing API, you can just as well process
@@ -255,6 +153,7 @@ To demonstrate, here is an example of transcoding videos using
255
153
 
256
154
  ```rb
257
155
  require "streamio-ffmpeg"
156
+ require "tempfile"
258
157
 
259
158
  class VideoUploader < Shrine
260
159
  plugin :processing
@@ -262,18 +161,22 @@ class VideoUploader < Shrine
262
161
  plugin :delete_raw
263
162
 
264
163
  process(:store) do |io, context|
265
- original = io.download
266
- transcoded = Tempfile.new(["transcoded", ".mp4"], binmode: true)
267
- screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
164
+ versions = { original: io }
268
165
 
269
- movie = FFMPEG::Movie.new(mov.path)
270
- movie.transcode(transcoded.path)
271
- movie.screenshot(screenshot.path)
166
+ io.download do |original|
167
+ transcoded = Tempfile.new(["transcoded", ".mp4"], binmode: true)
168
+ screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
169
+
170
+ movie = FFMPEG::Movie.new(original.path)
171
+ movie.transcode(transcoded.path)
172
+ movie.screenshot(screenshot.path)
173
+
174
+ [transcoded, screenshot].each(&:open) # refresh file descriptors
272
175
 
273
- [transcoded, screenshot].each(&:open) # refresh file descriptors
274
- original.close!
176
+ versions.merge!(transcoded: transcoded, screenshot: screenshot)
177
+ end
275
178
 
276
- { original: io, transcoded: transcoded, screenshot: screenshot }
179
+ versions
277
180
  end
278
181
  end
279
182
  ```
@@ -282,81 +185,152 @@ end
282
185
 
283
186
  Generating image thumbnails on upload can be a pain to maintain, because
284
187
  whenever you need to add a new version or change an existing one, you need to
285
- perform this change for all existing uploads. [This guide][reprocessing
286
- versions] explains the process in more detail.
287
-
288
- As an alternative, it's very common to generate thumbnails dynamically, when
289
- their URL is first requested, and then cache the processing result for future
290
- requests. This strategy is known as "on-the-fly processing", and it's suitable
291
- for smaller files such as images.
188
+ retroactively apply it to all existing uploads (see the [Reprocessing Versions]
189
+ guide for more details).
292
190
 
293
- Shrine doesn't ship with on-the-fly processing functionality, as that's a
294
- separate responsibility that belongs in its own project. There are various
295
- open source solutions that provide this functionality:
191
+ As an alternative, it's very common to instead generate thumbnails dynamically
192
+ as they're requested, and then cache them for future requests. This strategy is
193
+ known as "on-the-fly processing", and it's suitable for generating thumbnails
194
+ or document previews.
296
195
 
297
- * [Dragonfly]
298
- * [imgproxy]
299
- * [imaginary]
300
- * [thumbor]
301
- * [flyimg]
302
- * ...
196
+ Shrine provides on-the-fly processing functionality via the
197
+ [`derivation_endpoint`][derivation_endpoint] plugin. The basic setup is the
198
+ following:
303
199
 
304
- as well as many commercial solutions. To prove that you can really use them,
305
- let's see how we can hook up [Dragonfly] with Shrine. We'll also see how we
306
- can use [Cloudinary], as an example of a commercial solution.
200
+ 1. load the plugin with a secret key and a path prefix for the endpoint
201
+ 2. mount the endpoint into your main app's router
202
+ 3. define a processing block for the type files you want to generate
307
203
 
308
- ### Dragonfly
204
+ Together it might look something like this:
309
205
 
310
- Dragonfly is a mature file attachment library that comes with functionality for
311
- on-the-fly processing. At first it might appear that Dragonfly can only be used
312
- as an alternative to Shrine, but Dragonfly's app that performs on-the-fly
313
- processing can actually be used standalone.
206
+ ```rb
207
+ require "image_processing/mini_magick"
314
208
 
315
- To set up Dragonfly, we'll insert its middleware that serves files and add
316
- basic [configuration][Dragonfly configuration]:
209
+ class ImageUploader < Shrine
210
+ plugin :derivation_endpoint,
211
+ secret_key: "<YOUR SECRET KEY>",
212
+ prefix: "derivations/image"
213
+
214
+ derivation :thumbnail do |file, width, height|
215
+ ImageProcessing::MiniMagick
216
+ .source(file)
217
+ .resize_to_limit!(width.to_i, height.to_i)
218
+ end
219
+ end
220
+ ```
317
221
 
318
222
  ```rb
319
- Dragonfly.app.configure do
320
- url_format "/attachments/:job"
321
- secret "my secure secret" # used to generate the protective SHA
322
- plugin :imagemagick
223
+ # config/routes.rb (Rails)
224
+ Rails.application.routes.draw do
225
+ mount ImageUploader.derivation_endpoint => "derivations/image"
323
226
  end
227
+ ```
324
228
 
325
- use Dragonfly::Middleware
229
+ Now you can generate thumbnail URLs from attached files, and the actual
230
+ thumbnail will be generated when the URL is requested:
231
+
232
+ ```rb
233
+ photo.image.derivation_url(:thumbnail, "600", "400")
234
+ #=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
326
235
  ```
327
236
 
328
- If you're storing files in a cloud service like AWS S3, you should give them
329
- public access so that you can generate non-expiring URLs. This way Dragonfly
330
- URLs will not change and thus be cacheable, without having to use Dragonfly's
331
- own S3 data store which requires pulling in [fog-aws].
237
+ The plugin is highly customizable, be sure to check out the
238
+ [documentation][derivation_endpoint], especially the [performance
239
+ section][derivation_endpoint performance].
240
+
241
+ ## Extras
242
+
243
+ ### libvips
332
244
 
333
- To give new S3 objects public access, add `{ acl: "public-read" }` to upload
334
- options (note that any existing S3 objects' ACLs will have to be manually
335
- updated):
245
+ As mentioned, ImageProcessing gem also has an alternative backend for
246
+ processing images with **[libvips]**. libvips is a full-featured image
247
+ processing library like ImageMagick, with impressive performance
248
+ characteristics – it's often **multiple times faster** than ImageMagick and has
249
+ low memory usage (see [Why is libvips quick]).
250
+
251
+ Using libvips is as easy as installing it and switching to the
252
+ `ImageProcessing::Vips` backend:
253
+
254
+ ```sh
255
+ $ brew install vips
256
+ ```
336
257
 
337
258
  ```rb
338
- Shrine::Storage::S3.new(upload_options: { acl: "public-read" }, **other_options)
339
- # ...
340
- Shrine.plugin :default_url_options, cache: { public: true }, store: { public: true }
259
+ # Gemfile
260
+ gem "image_processing", "~> 1.0"
341
261
  ```
342
262
 
343
- Now you can generate Dragonfly URLs from `Shrine::UploadedFile` objects:
263
+ ```rb
264
+ require "image_processing/vips"
265
+
266
+ # all we did was replace `ImageProcessing::MiniMagick` with `ImageProcessing::Vips`
267
+ thumbnail = ImageProcessing::Vips
268
+ .source(image)
269
+ .resize_to_limit!(600, 400)
270
+
271
+ thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
272
+ ```
273
+
274
+ ### Optimizing thumbnails
275
+
276
+ If you're generating image thumbnails, you can additionally use the
277
+ [image_optim] gem to further reduce their filesize:
344
278
 
345
279
  ```rb
346
- def thumbnail_url(uploaded_file, dimensions)
347
- Dragonfly.app
348
- .fetch(uploaded_file.url)
349
- .thumb(dimensions)
350
- .url
351
- end
280
+ # Gemfile
281
+ gem "image_processing", "~> 1.0"
282
+ gem "image_optim"
283
+ gem "image_optim_pack" # precompiled binaries
352
284
  ```
285
+
353
286
  ```rb
354
- thumbnail_url(photo.image, "500x400") #=> "/attachments/W1siZnUiLCJodHRwOi8vd3d3LnB1YmxpY2RvbWFpbn..."
287
+ require "image_processing/mini_magick"
288
+
289
+ thumbnail = ImageProcessing::MiniMagick
290
+ .source(image)
291
+ .resize_to_limit!(600, 400)
292
+
293
+ image_optim = ImageOptim.new
294
+ image_optim.optimize_image!(thumbnail.path)
295
+
296
+ thumbnail.open # refresh file descriptor
297
+ thumbnail
298
+ ```
299
+
300
+ ### External processing
301
+
302
+ Since processing is so dynamic, you're not limited to using the ImageProcessing
303
+ gem, you can also use a 3rd-party service to generate thumbnails for you. Here
304
+ is an example of generating thumbnails on-the-fly using [ImageOptim.com] (not
305
+ to be confused with the [image_optim] gem):
306
+
307
+ ```rb
308
+ # Gemfile
309
+ gem "down", "~> 4.4"
310
+ gem "http", "~> 4.0"
311
+ ```
312
+
313
+ ```rb
314
+ require "down/http"
315
+
316
+ class ImageUploader < Shrine
317
+ plugin :derivation_endpoint,
318
+ secret_key: "secret",
319
+ prefix: "derivations/image",
320
+ download: false
321
+
322
+ derivation :thumbnail do |source, width, height|
323
+ # generate thumbnails using ImageOptim.com
324
+ down = Down::Http.new(method: :post)
325
+ down.download("https://im2.io/<USERNAME>/#{width}x#{height}/#{source.url}")
326
+ end
327
+ end
355
328
  ```
356
329
 
357
330
  ### Cloudinary
358
331
 
359
- [Cloudinary] is a nice service for on-the-fly image processing. The
332
+ [Cloudinary] is a popular commercial service for on-the-fly image processing,
333
+ so it's a good alternative to the `derivation_endpoint` plugin. The
360
334
  [shrine-cloudinary] gem provides a Shrine storage that we can set for our
361
335
  temporary and permanent storage:
362
336
 
@@ -389,25 +363,21 @@ photo.image.url(width: 100, height: 100, crop: :fit)
389
363
  #=> "http://res.cloudinary.com/myapp/image/upload/w_100,h_100,c_fit/nature.jpg"
390
364
  ```
391
365
 
392
- [`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/Base/FileMethods.html
393
- [image_optim]: https://github.com/toy/image_optim
394
- [ImageProcessing]: https://github.com/janko-m/image_processing
395
- [`ImageProcessing::MiniMagick`]: https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md
366
+ [`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
367
+ [ImageProcessing]: https://github.com/janko/image_processing
396
368
  [ImageMagick]: https://www.imagemagick.org
397
369
  [GraphicsMagick]: http://www.graphicsmagick.org
398
370
  [libvips]: http://libvips.github.io/libvips/
399
371
  [Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
372
+ [image_optim]: https://github.com/toy/image_optim
400
373
  [ImageOptim.com]: https://imageoptim.com/api
401
- [Down]: https://github.com/janko-m/down
402
- [HTTP.rb]: https://github.com/httprb/http
403
374
  [streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
404
- [reprocessing versions]:http://shrinerb.com/rdoc/files/doc/regenerating_versions_md.html
405
- [Dragonfly]: http://markevans.github.io/dragonfly/
406
- [imgproxy]: https://github.com/DarthSim/imgproxy
407
- [imaginary]: https://github.com/h2non/imaginary
408
- [thumbor]: http://thumbor.org
409
- [flyimg]: http://flyimg.io
375
+ [Reprocessing Versions]: /doc/regenerating_versions.md#readme
410
376
  [Cloudinary]: https://cloudinary.com
411
- [Dragonfly configuration]: http://markevans.github.io/dragonfly/configuration
412
- [fog-aws]: https://github.com/fog/fog-aws
413
377
  [shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
378
+ [backgrounding]: /doc/plugins/backgrounding.md#readme
379
+ [validation]: /doc/validation.md#readme
380
+ [ruby-vips]: https://github.com/libvips/ruby-vips
381
+ [MiniMagick]: https://github.com/minimagick/minimagick
382
+ [derivation_endpoint]: /doc/plugins/derivation_endpoint.md#readme
383
+ [derivation_endpoint performance]: /doc/plugins/derivation_endpoint.md#performance