shrine 2.10.1 → 2.11.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.

@@ -13,7 +13,7 @@ current store (let's say that you're migrating from FileSystem to S3):
13
13
  ```rb
14
14
  Shrine.storages = {
15
15
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
16
- store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
16
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),
17
17
  new_store: Shrine::Storage::S3.new(**s3_options),
18
18
  }
19
19
 
@@ -83,8 +83,9 @@ end
83
83
  In order to allow the user to select multiple files in the form, we just need
84
84
  to add the `multiple` attribute to the file field.
85
85
 
86
- ```html
87
- <input type="file" multiple name="file">
86
+ ```rb
87
+ f.input :file, name: :file, multiple: true
88
+ # <input type="file" name="file" multiple />
88
89
  ```
89
90
 
90
91
  On the client side you can then asynchronously upload each of the selected
@@ -112,4 +113,4 @@ automatically take care of the attachment management.
112
113
 
113
114
  [`Sequel::Model.nested_attributes`]: http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html
114
115
  [`ActiveRecord::Base.accepts_nested_attributes_for`]: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
115
- [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
116
+ [Direct Uploads to S3]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
@@ -409,7 +409,7 @@ which you have to register:
409
409
  ```rb
410
410
  Shrine.storages = {
411
411
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
412
- store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
412
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),
413
413
  }
414
414
  ```
415
415
 
@@ -578,6 +578,6 @@ The Shrine storage has no replacement for the `:url` Paperclip option, and it
578
578
  isn't needed.
579
579
 
580
580
  [file]: http://linux.die.net/man/1/file
581
- [Reprocessing versions]: http://shrinerb.com/rdoc/files/doc/regenerating_versions_md.html
582
- [direct S3 uploads]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
583
- [`Shrine::Storage::S3`]: http://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
581
+ [Reprocessing versions]: https://shrinerb.com/rdoc/files/doc/regenerating_versions_md.html
582
+ [direct S3 uploads]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
583
+ [`Shrine::Storage::S3`]: https://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
@@ -0,0 +1,415 @@
1
+ # File Processing
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.
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).
12
+
13
+ ```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
21
+ end
22
+ ```
23
+
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.
38
+
39
+ ```rb
40
+ class ImageUploader < Shrine
41
+ plugin :processing
42
+ plugin :delete_raw # automatically delete processed files after uploading
43
+
44
+ # ...
45
+ end
46
+ ```
47
+
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
+ ```rb
54
+ # Gemfile
55
+ gem "image_optim"
56
+ gem "image_optim_pack" # precompiled binaries
57
+ ```
58
+
59
+ ```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)
71
+
72
+ original.close!
73
+
74
+ File.open(optimized_path, "rb")
75
+ end
76
+ end
77
+ ```
78
+
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
84
+
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]/[GraphicsMagick].
95
+
96
+ ```sh
97
+ $ brew install imagemagick
98
+ ```
99
+ ```rb
100
+ # Gemfile
101
+ gem "image_processing", "~> 1.0"
102
+ ```
103
+
104
+ ```rb
105
+ require "image_processing/mini_magick"
106
+
107
+ class ImageUploader < Shrine
108
+ plugin :processing
109
+ plugin :versions
110
+ plugin :delete_raw
111
+
112
+ process(:store) do |io, context|
113
+ original = io.download
114
+ pipeline = ImageProcessing::MiniMagick.source(original)
115
+
116
+ size_800 = pipeline.resize_to_limit!(800, 800)
117
+ size_500 = pipeline.resize_to_limit!(500, 500)
118
+ size_300 = pipeline.resize_to_limit!(300, 300)
119
+
120
+ original.close!
121
+
122
+ { original: io, large: size_800, medium: size_500, small: size_300 }
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### libvips
128
+
129
+ Alternatively, you can also process files with **[libvips]**, which has shown
130
+ to be multiple times faster than ImageMagick, with lower memory usage on top of
131
+ that (see [Why is libvips quick]). Using libvips is as easy as installing libvips
132
+ and switching to the `ImageProcessing::Vips` backend.
133
+
134
+ ```sh
135
+ $ brew install vips
136
+ ```
137
+
138
+ ```rb
139
+ require "image_processing/vips"
140
+
141
+ class ImageUploader < Shrine
142
+ plugin :processing
143
+ plugin :versions
144
+ plugin :delete_raw
145
+
146
+ process(:store) do |io, context|
147
+ original = io.download
148
+ pipeline = ImageProcessing::Vips.source(original)
149
+
150
+ size_800 = pipeline.resize_to_limit!(800, 800)
151
+ size_500 = pipeline.resize_to_limit!(500, 500)
152
+ size_300 = pipeline.resize_to_limit!(300, 300)
153
+
154
+ original.close!
155
+
156
+ { original: io, large: size_800, medium: size_500, small: size_300 }
157
+ end
158
+ end
159
+ ```
160
+
161
+ ### External
162
+
163
+ Since processing is so dynamic, you're not limited to using the ImageProcessing
164
+ gem, you can also use a 3rd-party service to generate thumbnails for you. Here
165
+ is the same example as above, but this time using [ImageOptim.com] to do the
166
+ processing (not to be confused with the [image_optim] gem):
167
+
168
+ ```rb
169
+ # Gemfile
170
+ gem "down", "~> 4.4"
171
+ gem "http", "~> 3.2"
172
+ ```
173
+
174
+ ```rb
175
+ require "down/http"
176
+
177
+ class ImageUploader < Shrine
178
+ plugin :processing
179
+ plugin :versions
180
+ plugin :delete_raw
181
+
182
+ IMAGE_OPTIM_URL = "https://im2.io/<USERNAME>"
183
+
184
+ process(:store) do |io, context|
185
+ down = Down::Http.new(method: :post)
186
+
187
+ size_800 = down.download("#{IMAGE_OPTIM_URL}/800x800/#{io.url}")
188
+ size_500 = down.download("#{IMAGE_OPTIM_URL}/500x500/#{io.url}")
189
+ size_300 = down.download("#{IMAGE_OPTIM_URL}/300x300/#{io.url}")
190
+
191
+ { original: io, large: size_800, medium: size_500, small: size_300 }
192
+ end
193
+ end
194
+ ```
195
+
196
+ We used the [Down] gem to download response bodies into tempfiles, specifically
197
+ its [HTTP.rb] backend, as it supports changing the request method and uses an
198
+ order of magnitude less memory than the default backend. Notice that we didn't
199
+ have to download the original file from temporary storage as ImageOptim.com
200
+ allows us to provide a URL.
201
+
202
+ ## Conditional processing
203
+
204
+ As we've seen, Shrine's processing API allows us to process files with regular
205
+ Ruby code. This means that we can make processing dynamic by using regular Ruby
206
+ conditionals.
207
+
208
+ For example, let's say we want our thumbnails to be either JPEGs or PNGs, and
209
+ we also want to save JPEGs as progressive (interlaced). Here's how the code for
210
+ this might look like:
211
+
212
+ ```rb
213
+ require "image_processing/vips"
214
+
215
+ class ImageUploader < Shrine
216
+ plugin :processing
217
+ plugin :versions
218
+ plugin :delete_raw
219
+
220
+ process(:store) do |io, context|
221
+ original = io.download
222
+ pipeline = ImageProcessing::Vips.source(original)
223
+
224
+ # the `io` object contains the MIME type of the original file
225
+ if io.mime_type != "image/png"
226
+ pipeline = pipeline
227
+ .convert("jpeg")
228
+ .saver(interlace: true)
229
+ end
230
+
231
+ size_800 = pipeline.resize_to_limit!(800, 800)
232
+ size_500 = pipeline.resize_to_limit!(500, 500)
233
+ size_300 = pipeline.resize_to_limit!(300, 300)
234
+
235
+ original.close!
236
+
237
+ { original: io, large: size_800, medium: size_500, small: size_300 }
238
+ end
239
+ end
240
+ ```
241
+
242
+ ## Processing other file types
243
+
244
+ So far we've only been talking about processing images. However, there is
245
+ nothing image-specific in Shrine's processing API, you can just as well process
246
+ any other types of files. The processing tool doesn't need to have any special
247
+ Shrine integration, the ImageProcessing gem that we saw earlier is a completely
248
+ generic gem.
249
+
250
+ To demonstrate, here is an example of transcoding videos using
251
+ [streamio-ffmpeg]:
252
+
253
+ ```sh
254
+ $ brew install ffmpeg
255
+ ```
256
+
257
+ ```rb
258
+ # Gemfile
259
+ gem "streamio-ffmpeg"
260
+ ```
261
+
262
+ ```rb
263
+ require "streamio-ffmpeg"
264
+
265
+ class VideoUploader < Shrine
266
+ plugin :processing
267
+ plugin :versions
268
+ plugin :delete_raw
269
+
270
+ process(:store) do |io, context|
271
+ original = io.download
272
+ transcoded = Tempfile.new(["transcoded", ".mp4"], binmode: true)
273
+ screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
274
+
275
+ movie = FFMPEG::Movie.new(mov.path)
276
+ movie.transcode(transcoded.path)
277
+ movie.screenshot(screenshot.path)
278
+
279
+ [transcoded, screenshot].each(&:open) # refresh file descriptors
280
+ original.close!
281
+
282
+ { original: io, transcoded: transcoded, screenshot: screenshot }
283
+ end
284
+ ```
285
+
286
+ ## On-the-fly processing
287
+
288
+ Generating image thumbnails on upload can be a pain to maintain, because
289
+ whenever you need to add a new version or change an existing one, you need to
290
+ perform this change for all existing uploads. [This guide][reprocessing
291
+ versions] explains the process in more detail.
292
+
293
+ As an alternative, it's very common to generate thumbnails dynamically, when
294
+ their URL is first requested, and then cache the processing result for future
295
+ requests. This strategy is known as "on-the-fly processing", and it's suitable
296
+ for smaller files such as images.
297
+
298
+ Shrine doesn't ship with on-the-fly processing functionality, as that's a
299
+ separate responsibility that belongs in its own project. There are various
300
+ open source solutions that provide this functionality:
301
+
302
+ * [Dragonfly]
303
+ * [imgproxy]
304
+ * [imaginary]
305
+ * [thumbor]
306
+ * [flyimg]
307
+ * ...
308
+
309
+ as well as many commercial solutions. To prove that you can really use them,
310
+ let's see how we can hook up [Dragonfly] with Shrine. We'll also see how we
311
+ can use [Cloudinary], as an example of a commercial solution.
312
+
313
+ ### Dragonfly
314
+
315
+ Dragonfly is a mature file attachment library that comes with functionality for
316
+ on-the-fly processing. At first it might appear that Dragonfly can only be used
317
+ as an alternative to Shrine, but Dragonfly's app that performs on-the-fly
318
+ processing can actually be used standalone.
319
+
320
+ To set up Dragonfly, we'll insert its middleware that serves files and add
321
+ basic [configuration][Dragonfly configuration]:
322
+
323
+ ```rb
324
+ Dragonfly.app.configure do
325
+ url_format "/attachments/:job"
326
+ secret "my secure secret" # used to generate the protective SHA
327
+ end
328
+
329
+ use Dragonfly::Middleware
330
+ ```
331
+
332
+ If you're storing files in a cloud service like AWS S3, you should give them
333
+ public access so that you can generate non-expiring URLs. This way Dragonfly
334
+ URLs will not change and thus be cacheable, without having to use Dragonfly's
335
+ own S3 data store which requires pulling in [fog-aws].
336
+
337
+ To give new S3 objects public access, add `{ acl: "public-read" }` to upload
338
+ options (note that any existing S3 objects' ACLs will have to be manually
339
+ updated):
340
+
341
+ ```rb
342
+ Shrine::Storage::S3.new(upload_options: { acl: "public-read" }, **other_options)
343
+ ```
344
+
345
+ Now you can generate Dragonfly URLs from `Shrine::UploadedFile` objects:
346
+
347
+ ```rb
348
+ def thumbnail_url(uploaded_file, dimensions)
349
+ Dragonfly.app
350
+ .fetch(uploaded_file.url(public: true))
351
+ .thumb(dimensions)
352
+ .url
353
+ end
354
+ ```
355
+ ```rb
356
+ thumbnail_url(photo.image, "500x400") #=> "/attachments/W1siZnUiLCJodHRwOi8vd3d3LnB1YmxpY2RvbWFpbn..."
357
+ ```
358
+
359
+ ### Cloudinary
360
+
361
+ [Cloudinary] is a nice service for on-the-fly image processing. The
362
+ [shrine-cloudinary] gem provides a Shrine storage that we can set for our
363
+ temporary and permanent storage:
364
+
365
+ ```rb
366
+ # Gemfile
367
+ gem "shrine-cloudinary"
368
+ ```
369
+
370
+ ```rb
371
+ require "cloudinary"
372
+ require "shrine/storage/cloudinary"
373
+
374
+ Cloudinary.config(
375
+ cloud_name: "<YOUR_CLOUD_NAME>",
376
+ api_key: "<YOUR_API_KEY>",
377
+ api_secret: "<YOUR_API_SECRET>",
378
+ )
379
+
380
+ Shrine.storages = {
381
+ cache: Shrine::Storage::Cloudinary.new(prefix: "cache"),
382
+ store: Shrine::Storage::Cloudinary.new,
383
+ }
384
+ ```
385
+
386
+ Now when we upload our images to Cloudinary, we can generate URLs with various
387
+ processing parameters:
388
+
389
+ ```rb
390
+ photo.image.url(width: 100, height: 100, crop: :fit)
391
+ #=> "http://res.cloudinary.com/myapp/image/upload/w_100,h_100,c_fit/nature.jpg"
392
+ ```
393
+
394
+ [`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/Base/FileMethods.html
395
+ [image_optim]: https://github.com/toy/image_optim
396
+ [ImageProcessing]: https://github.com/janko-m/image_processing
397
+ [`ImageProcessing::MiniMagick`]: https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md
398
+ [ImageMagick]: https://www.imagemagick.org
399
+ [GraphicsMagick]: http://www.graphicsmagick.org
400
+ [libvips]: http://jcupitt.github.io/libvips/
401
+ [Why is libvips quick]: https://github.com/jcupitt/libvips/wiki/Why-is-libvips-quick
402
+ [ImageOptim.com]: https://imageoptim.com/api
403
+ [Down]: https://github.com/janko-m/down
404
+ [HTTP.rb]: https://github.com/httprb/http
405
+ [streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
406
+ [reprocessing versions]:http://shrinerb.com/rdoc/files/doc/regenerating_versions_md.html
407
+ [Dragonfly]: http://markevans.github.io/dragonfly/
408
+ [imgproxy]: https://github.com/DarthSim/imgproxy
409
+ [imaginary]: https://github.com/h2non/imaginary
410
+ [thumbor]: http://thumbor.org
411
+ [flyimg]: http://flyimg.io
412
+ [Cloudinary]: https://cloudinary.com
413
+ [Dragonfly configuration]: http://markevans.github.io/dragonfly/configuration
414
+ [fog-aws]: https://github.com/fog/fog-aws
415
+ [shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary