shrine 2.13.0 → 2.14.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +20 -16
  4. data/doc/creating_storages.md +0 -21
  5. data/doc/design.md +1 -0
  6. data/doc/direct_s3.md +26 -15
  7. data/doc/metadata.md +67 -22
  8. data/doc/multiple_files.md +3 -3
  9. data/doc/processing.md +1 -1
  10. data/doc/retrieving_uploads.md +184 -0
  11. data/lib/shrine.rb +268 -900
  12. data/lib/shrine/attacher.rb +271 -0
  13. data/lib/shrine/attachment.rb +97 -0
  14. data/lib/shrine/plugins.rb +29 -0
  15. data/lib/shrine/plugins/_urlsafe_serialization.rb +182 -0
  16. data/lib/shrine/plugins/activerecord.rb +16 -14
  17. data/lib/shrine/plugins/add_metadata.rb +58 -24
  18. data/lib/shrine/plugins/backgrounding.rb +6 -1
  19. data/lib/shrine/plugins/cached_attachment_data.rb +9 -9
  20. data/lib/shrine/plugins/copy.rb +12 -8
  21. data/lib/shrine/plugins/data_uri.rb +23 -20
  22. data/lib/shrine/plugins/default_url_options.rb +5 -4
  23. data/lib/shrine/plugins/determine_mime_type.rb +24 -23
  24. data/lib/shrine/plugins/download_endpoint.rb +61 -73
  25. data/lib/shrine/plugins/migration_helpers.rb +17 -17
  26. data/lib/shrine/plugins/module_include.rb +9 -8
  27. data/lib/shrine/plugins/presign_endpoint.rb +13 -7
  28. data/lib/shrine/plugins/processing.rb +1 -1
  29. data/lib/shrine/plugins/rack_response.rb +128 -36
  30. data/lib/shrine/plugins/refresh_metadata.rb +20 -5
  31. data/lib/shrine/plugins/remote_url.rb +8 -8
  32. data/lib/shrine/plugins/remove_attachment.rb +9 -9
  33. data/lib/shrine/plugins/sequel.rb +21 -18
  34. data/lib/shrine/plugins/tempfile.rb +68 -0
  35. data/lib/shrine/plugins/upload_endpoint.rb +3 -2
  36. data/lib/shrine/plugins/upload_options.rb +7 -6
  37. data/lib/shrine/plugins/validation_helpers.rb +2 -1
  38. data/lib/shrine/storage/file_system.rb +20 -17
  39. data/lib/shrine/storage/linter.rb +0 -7
  40. data/lib/shrine/storage/s3.rb +159 -50
  41. data/lib/shrine/uploaded_file.rb +258 -0
  42. data/lib/shrine/version.rb +1 -1
  43. data/shrine.gemspec +7 -19
  44. metadata +41 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f51ec7d13c7f830e1cebb7d12c6444aad0b0c2df6c7d57e2cec61da7e8bc8b1
4
- data.tar.gz: 9c75a7232d0e10910582a418646fa850305ef2cc164a6a8bc6e021ec52ac5de7
3
+ metadata.gz: 705f49f8a110b3b50eb00c0fdf17da95dce2fa7782aaadfe20808af178a46405
4
+ data.tar.gz: 41789005e3146dc56a327d7d0c4fe6de12153cb8135789a9a3cf56212fdd1d13
5
5
  SHA512:
6
- metadata.gz: 03c0e1e29b8ba35b64fc6e33ab1a297a7f843726e3e7d21bca57449d1b36ee14c2781c3a2ddc6aa1f5bf36aeff9c7f8e77ee1eec8658cc3177bf701b9e942362
7
- data.tar.gz: 3d2e11839c487fbd1782bc41751718a6d8e601070aa4c2a03f469f79a9b6fd176d2f5da9db37f2d1236ec1c64bb480c580217061f1d67c87eec0a2f86ae46d74
6
+ metadata.gz: 7f9ed520831fb14e199ef8ee9bddeaac47f7cf4151c5eb66ad68620c8c45db6c01d4fc4d62c6ff87a9bed2a771d48c97cc234ba903f1c3bf4005e79bcfa52d33
7
+ data.tar.gz: e70bbbd5f5731778112c4ce700df42b08b207368b5898625dbff8123faae40e383e7429b134d3850c485bfc5cab1e142a6c123f6e1a4adc6bb3d430484377a82
@@ -1,3 +1,73 @@
1
+ ## 2.14.0 (2018-12-27)
2
+
3
+ * Add `tempfile` plugin for easier reusing of the same uploaded file copy on disk (@janko-m)
4
+
5
+ * Don't re-open the uploaded file if it's already open in `refresh_metadata` plugin (@janko-m)
6
+
7
+ * Drop support for MRI 2.1 and 2.2 (@janko-m)
8
+
9
+ * Fix `backgrounding` not working when default storage was changed with `Attachment.new` (@janko-m)
10
+
11
+ * Don't clear existing metadata definitions when loading `add_metadata` plugin (@janko-m)
12
+
13
+ * Don't clear existing processing blocks when loading `processing` plugin (@janko-m)
14
+
15
+ * Deprecate automatic escaping of `:content_disposition` in `Shrine::Storage::S3` (@janko-m)
16
+
17
+ * Use `content_disposition` gem in `Shrine::Storage::S3` and `rack_response` plugin (@janko-m)
18
+
19
+ * Make `FileSystem#clear!` work correctly when the storage directory is a symlink (@janko-m)
20
+
21
+ * Don't abort promotion in `backgrounding` plugin when original metadata was updated (@janko-m)
22
+
23
+ * Don't mutate the `UploadedFile` data hash in `refresh_metadata` plugin (@janko-m)
24
+
25
+ * Deprecate `Storage::S3#download` (@janko-m)
26
+
27
+ * Stop using `Storage#download` in `UploadedFile#download` for peformance (@janko-m)
28
+
29
+ * Remove `#download` from the Shrine storage specification (@janko-m)
30
+
31
+ * Keep `context` argument in `#extract_metadata` optional after loading `add_metadata` plugin (@janko-m)
32
+
33
+ * Include metadata key with `nil` value when `nil` is returned in `add_metadata` block (@janko-m)
34
+
35
+ * Strip query params in upload location when re-uploading from `shrine-url` storage (@jrochkind)
36
+
37
+ * Inline Base plugin into core classes, extract them to separate files (@printercu)
38
+
39
+ * Make `rack_response` plugin work with `Rack::Sendfile` for `FileSystem` storage (@janko-m)
40
+
41
+ * Add `:filename` and `:type` options to `rack_response` plugin (@janko-m)
42
+
43
+ * Add `:host` option to `UploadedFile#download_url` in `download_endpoint` plugin (@janko-m)
44
+
45
+ * Add support for client-side encryption to S3 storage (@janko-m)
46
+
47
+ * Don't look up the attachment class in each new model instance (@printercu)
48
+
49
+ * Allow `Attacher#cached?` and `Attacher#stored?` to take an `UploadedFile` object (@jrochkind)
50
+
51
+ * Allow assigning a filename to the `DataFile` object in `Shrine.data_uri` (@janko-m)
52
+
53
+ * Don't strip media type parameters for the `DataFile` object in `data_uri` plugin (@janko-m)
54
+
55
+ * Add `:content_type` analyzer to `Shrine.mime_type_analyzers` in `determine_mime_type` plugin (@janko-m)
56
+
57
+ * Rename `:default` analyzer to `:content_type` in `determine_mime_type` plugin (@janko-m)
58
+
59
+ * Don't display a warning when `determine_mime_type` plugin is loaded with `:default` analyzer (@janko-m)
60
+
61
+ * Exclude media type parameters when copying `IO#content_type` into `mime_type` metadata (@janko-m)
62
+
63
+ * Remove superfluous `#head_object` S3 API call in `S3#download` (@janko-m)
64
+
65
+ * Make `S3#download` and `S3#open` work with server side encryption options (@janko-m)
66
+
67
+ * Make previously extracted metadata available under `:metadata` in `add_metadata` plugin (@jrochkind)
68
+
69
+ * Use a guard raise cause for `bucket` argument in S3 for an appropriate error message (@ardecvz)
70
+
1
71
  ## 2.13.0 (2018-11-04)
2
72
 
3
73
  * Specify UTF-8 charset in `Content-Type` response header in `presign_endpoint` plugin (@janko-m)
@@ -6,6 +76,8 @@
6
76
 
7
77
  * Force UTF-8 encoding on filenames coming from Rack's multipart request params in `rack_file` plugin (@janko-m)
8
78
 
79
+ * Raise `Shrine::Error` if `file` command returns error in stdout in `determine_mime_type` plugin (@janko-m)
80
+
9
81
  * Allow `:host` in `S3#url` to specify a host URL with an additional path prefix (@janko-m)
10
82
 
11
83
  * Revert adding bucket name to URL path in `S3#url` when `:host` is used with `:force_path_style` (@janko-m)
data/README.md CHANGED
@@ -226,12 +226,12 @@ plugin is loaded.
226
226
  Here are some examples of IO objects that can be uploaded:
227
227
 
228
228
  ```rb
229
- uploader.upload File.open("/path/to/file", "rb") # upload from disk
230
- uploader.upload StringIO.new("file content") # upload from memory
231
- uploader.upload ActionDispatch::Http::UploadedFile.new # upload from Rails controller
232
- uploader.upload Shrine.rack_file({ tempfile: Tempfile.new }) # upload from Rack controller
233
- uploader.upload Rack::Test::UploadedFile.new # upload from rack-test
234
- uploader.upload Down.open("https://example.org/file") # upload from internet
229
+ uploader.upload File.open("/path/to/file", binmode: true) # upload from disk
230
+ uploader.upload StringIO.new("file content") # upload from memory
231
+ uploader.upload ActionDispatch::Http::UploadedFile.new # upload from Rails controller
232
+ uploader.upload Shrine.rack_file({ tempfile: tempfile }) # upload from Rack controller
233
+ uploader.upload Rack::Test::UploadedFile.new # upload from rack-test
234
+ uploader.upload Down.open("https://example.org/file") # upload from internet
235
235
  ```
236
236
 
237
237
  `Shrine::UploadedFile`, the object returned after upload, is itself an IO-like
@@ -285,12 +285,8 @@ uploaded_file.rewind # rewinds the IO
285
285
  uploaded_file.close # closes the IO
286
286
  ```
287
287
 
288
- If you want to retrieve the content of the uploaded file, you can use a
289
- combination of `#open` and `#read`:
290
-
291
- ```rb
292
- uploaded_file.open(&:read) #=> "..." (binary content of the uploaded file)
293
- ```
288
+ For more details on these `Shrine::UploadedFile` methods, see the [Retrieving
289
+ Uploads] guide.
294
290
 
295
291
  ## Attachment
296
292
 
@@ -465,6 +461,13 @@ uploaded_file.mime_type #=> "video/mp4"
465
461
  uploaded_file.size #=> 345993
466
462
  ```
467
463
 
464
+ By default these values are determined from the following attributes on the IO
465
+ object:
466
+
467
+ * `filename` – `io.original_filename` or `io.path`
468
+ * `mime_type` – `io.content_type`
469
+ * `size` – `io.size`
470
+
468
471
  ### MIME type
469
472
 
470
473
  By default `mime_type` will be inherited from `#content_type` attribute of the
@@ -765,7 +768,7 @@ Shrine.plugin :presign_endpoint
765
768
  ```
766
769
  ```rb
767
770
  # config.ru (Rack)
768
- map "/presign" do
771
+ map "/s3/params" do
769
772
  run Shrine.presign_endpoint(:cache)
770
773
  end
771
774
 
@@ -773,11 +776,11 @@ end
773
776
 
774
777
  # config/routes.rb (Rails)
775
778
  Rails.application.routes.draw do
776
- mount Shrine.presign_endpoint(:cache) => "/presign"
779
+ mount Shrine.presign_endpoint(:cache) => "/s3/params"
777
780
  end
778
781
  ```
779
782
 
780
- The above will add a `GET /presign` route to your app. You can now hook Uppy's
783
+ The above will add a `GET /s3/params` route to your app. You can now hook Uppy's
781
784
  [AWS S3][uppy aws s3] plugin to this endpoint and have it upload directly to
782
785
  S3. See [this walkthrough][direct S3 uploads walkthrough] that shows adding
783
786
  direct S3 uploads from scratch, as well as the [Direct Uploads to S3][direct S3
@@ -789,7 +792,7 @@ If you wanted to implement this enpdoint yourself, this is how it could roughly
789
792
  look like for S3 storage in Sinatra:
790
793
 
791
794
  ```rb
792
- get "/presign" do
795
+ get "/s3/params" do
793
796
  storage = Shrine.storages[:cache]
794
797
  location = SecureRandom.hex + File.extname(params["filename"].to_s)
795
798
 
@@ -990,6 +993,7 @@ The gem is available as open source under the terms of the [MIT License].
990
993
  [external storages]: https://shrinerb.com/#external
991
994
  [creating storage]: https://shrinerb.com/rdoc/files/doc/creating_storages_md.html
992
995
  [creating plugin]: https://shrinerb.com/rdoc/files/doc/creating_plugins_md.html
996
+ [Retrieving Uploads]: https://shrinerb.com/rdoc/files/doc/retrieving_uploads_md.html
993
997
  [Using Attacher]: https://shrinerb.com/rdoc/files/doc/attacher_md.html
994
998
  [plugins]: https://shrinerb.com/#plugins
995
999
  [`file`]: http://linux.die.net/man/1/file
@@ -122,27 +122,6 @@ The storage can support additional options to customize how the file will be
122
122
  opened, `Shrine::UploadedFile#open` and `Shrine::UploadedFile#download` will
123
123
  forward any given options to `#open`.
124
124
 
125
- ## Download
126
-
127
- `Shrine::UploadedFile#download` by default uses the `#open` storage method to
128
- stream file content to a Tempfile. However, if you would like to use your own
129
- custom way of downloading to a file, you can define `#download` on the storage
130
- and `Shrine::UploadedFile#download` will automatically call that instead.
131
-
132
- ```rb
133
- class MyStorage
134
- # ...
135
- def download(id, **options)
136
- # download the uploaded file to a Tempfile
137
- end
138
- # ...
139
- end
140
- ```
141
-
142
- The storage can support additional options to customize how the file will be
143
- downloaded, `Shrine::UploadedFile#download` will forward any given options to
144
- `#download`.
145
-
146
125
  ## Url
147
126
 
148
127
  The `#url` storage method is called by `Shrine::UploadedFile#url`, it accepts a
@@ -125,6 +125,7 @@ uploaded_file.size
125
125
  # storage methods
126
126
  uploaded_file.url
127
127
  uploaded_file.exists?
128
+ uploaded_file.open
128
129
  uploaded_file.download
129
130
  uploaded_file.delete
130
131
  # ...
@@ -96,25 +96,26 @@ S3. It's recommended to use [Uppy] for client side uploads.
96
96
  The `presign_endpoint` plugin provides a Rack application that generates these
97
97
  upload parameters, which we can just mount in our application. We'll make our
98
98
  presign endpoint also use the additional `type` and `filename` query parameters
99
- to set `Content-Type` and `Content-Disposition` for the uploaded file, as well
100
- as limit the upload size to 10 MB (see [`Shrine::Storage::S3#presign`] for the
101
- list of available options).
99
+ to set `Content-Type` header, `Content-Disposition` header (using the
100
+ [content_disposition] gem), as well as limit the upload size to 10 MB (see
101
+ [`Shrine::Storage::S3#presign`] for the list of available options).
102
102
 
103
103
  ```rb
104
104
  Shrine.plugin :presign_endpoint, presign_options: -> (request) {
105
+ # Uppy will send the "filename" and "type" query parameters
105
106
  filename = request.params["filename"]
106
107
  type = request.params["type"]
107
108
 
108
109
  {
109
- content_disposition: "inline; filename=\"#{filename}\"", # set download filename
110
- content_type: type, # set content type (required if using DigitalOcean Spaces)
111
- content_length_range: 0..(10*1024*1024), # limit upload size to 10 MB
110
+ content_disposition: ContentDisposition.inline(filename), # set download filename
111
+ content_type: type, # set content type (required if using DigitalOcean Spaces)
112
+ content_length_range: 0..(10*1024*1024), # limit upload size to 10 MB
112
113
  }
113
114
  }
114
115
  ```
115
116
  ```rb
116
117
  # config.ru (Rack)
117
- map "/presign" do
118
+ map "/s3/params" do
118
119
  run Shrine.presign_endpoint(:cache)
119
120
  end
120
121
 
@@ -122,17 +123,17 @@ end
122
123
 
123
124
  # config/routes.rb (Rails)
124
125
  Rails.application.routes.draw do
125
- mount Shrine.presign_endpoint(:cache) => "/presign"
126
+ mount Shrine.presign_endpoint(:cache) => "/s3/params"
126
127
  end
127
128
  ```
128
129
 
129
- The above will create a `GET /presign` route, which internally calls
130
+ The above will create a `GET /s3/params` route, which internally calls
130
131
  [`Shrine::Storage::S3#presign`] to return the HTTP verb (POST) and the S3 URL
131
132
  to which the file should be uploaded, along with the required POST parameters
132
133
  and request headers.
133
134
 
134
135
  ```rb
135
- # GET /presign
136
+ # GET /s3/params
136
137
  {
137
138
  "method": "post",
138
139
  "url": "https://my-bucket.s3-eu-west-1.amazonaws.com",
@@ -148,11 +149,11 @@ and request headers.
148
149
  }
149
150
  ```
150
151
 
151
- Uppy's [AWS S3][uppy aws s3] plugin would then make a request to this endpoint and use these
152
- parameters to upload the file directly to S3. Once the file has been uploaded,
153
- you can generate a JSON representation of the uploaded file on the client side,
154
- and write it to the hidden attachment field (or send it directly in an AJAX
155
- request).
152
+ Uppy's [AWS S3][uppy aws s3] plugin would then make a request to this endpoint
153
+ and use these parameters to upload the file directly to S3. Once the file has
154
+ been uploaded, you can generate a JSON representation of the uploaded file on
155
+ the client side, and write it to the hidden attachment field (or send it
156
+ directly in an AJAX request).
156
157
 
157
158
  ```rb
158
159
  {
@@ -176,6 +177,13 @@ upload walkthrough] for adding dynamic direct S3 uploads from scratch, as well
176
177
  as the [Roda][roda demo] / [Rails][rails demo] demo app for a complete example
177
178
  of multiple direct S3 uploads.
178
179
 
180
+ Also, if you're dealing with larger files, you may want to make the uploads
181
+ resumable by using the [Aws S3 Multipart][uppy aws s3 multipart] Uppy plugin
182
+ instead, with the [uppy-s3_multipart] gem on the backend. Your back-end
183
+ implementation is similar, just using `Shrine.uppy_s3_multipart` in place of
184
+ `Shrine.presign_endpoint`. Instructions can be found in uppy-s3_multipart
185
+ README.
186
+
179
187
  ## Strategy B (static)
180
188
 
181
189
  * Basic user experience
@@ -385,6 +393,8 @@ setup] guide.
385
393
  [Uppy]: https://uppy.io
386
394
  [uppy aws s3]: https://uppy.io/docs/aws-s3/
387
395
  [uppy aws-s3 cors]: https://uppy.io/docs/aws-s3/#S3-Bucket-configuration
396
+ [uppy aws s3 multipart]: https://uppy.io/docs/aws-s3/
397
+ [uppy-s3_multipart]: https://github.com/janko-m/uppy-s3_multipart
388
398
  [Amazon S3 Data Consistency Model]: http://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyMode
389
399
  [CORS guide]: http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html
390
400
  [CORS API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_cors-instance_method
@@ -393,3 +403,4 @@ setup] guide.
393
403
  [Minio]: https://minio.io
394
404
  [minio setup]: https://shrinerb.com/rdoc/files/doc/testing_md.html#label-Minio
395
405
  [metadata direct uploads]: https://github.com/shrinerb/shrine/blob/master/doc/metadata.md#direct-uploads
406
+ [content_disposition]: https://github.com/shrinerb/content_disposition
@@ -26,6 +26,13 @@ uploader.extract_metadata(io) #=>
26
26
  # }
27
27
  ```
28
28
 
29
+ By default these values are determined from the following attributes on the IO
30
+ object:
31
+
32
+ * `filename` – `io.original_filename` or `io.path`
33
+ * `mime_type` – `io.content_type`
34
+ * `size` – `io.size`
35
+
29
36
  Note that you can also manually add or override metadata on upload by passing
30
37
  the `:metadata` option to `Shrine#upload`:
31
38
 
@@ -181,21 +188,15 @@ least partially) retrieving file content from the storage, which could
181
188
  potentially be expensive depending on the storage and the type of metadata
182
189
  being extracted.
183
190
 
184
- If you're just attaching files uploaded directly to local disk, there wouldn't
185
- be any additional performance penalty for extracting metadata. However, if
186
- you're attaching files uploaded directly to a cloud service like S3, retrieving
187
- file content for metadata extraction would require an HTTP download. If you're
188
- using just the `determine_mime_type` plugin, only a small portion of the file
189
- will be downloaded, so the performance impact might not be so big. But in other
190
- cases you might have to download the whole file.
191
-
192
191
  There are two ways of extracting metadata from directly uploaded files. If you
193
192
  want metadata to be automatically extracted on assignment (which is useful if
194
- you want to validate the extracted metadata or have it immediately available),
195
- you can load the `restore_cached_data` plugin:
193
+ you want to validate the extracted metadata or have it immediately available
194
+ for any other reason), you can load the `restore_cached_data` plugin:
196
195
 
197
196
  ```rb
198
- Shrine.plugin :restore_cached_data # automatically extract metadata from cached files on assingment
197
+ class ImageUploader < Shrine
198
+ plugin :restore_cached_data # automatically extract metadata from cached files on assignment
199
+ end
199
200
  ```
200
201
  ```rb
201
202
  photo.image = '{"id":"ks9elsd.jpg","storage":"cache","metadata":{}}' # metadata is extracted
@@ -212,15 +213,13 @@ during background promotion using the `refresh_metadata` plugin (which the
212
213
  `restore_cached_data` plugin uses internally):
213
214
 
214
215
  ```rb
215
- Shrine.plugin :refresh_metadata
216
- ```
217
- ```rb
218
- class MyUploader < Shrine
216
+ class ImageUploader < Shrine
217
+ plugin :refresh_metadata
219
218
  plugin :processing
220
219
 
221
220
  # this will be called in the background if using backgrounding plugin
222
221
  process(:store) do |io, context|
223
- io.refresh_metadata! # extracts metadata and updates `io.metadata`
222
+ io.refresh_metadata!(context) # extracts metadata and updates `io.metadata`
224
223
  io
225
224
  end
226
225
  end
@@ -232,17 +231,24 @@ two approaches. For example, if you're attaching video files, you might want to
232
231
  extract MIME type upfront and video-specific metadata in a background job, which
233
232
  can be done as follows (provided that `backgrounding` plugin is used):
234
233
 
235
- ```rb
236
- Shrine.plugin :restore_cached_data
237
- ```
238
234
  ```rb
239
235
  class MyUploader < Shrine
240
236
  plugin :determine_mime_type # this will be called in the foreground
237
+ plugin :restore_cached_data
238
+ plugin :refresh_metadata
239
+ plugin :add_metadata
241
240
  plugin :processing
242
241
 
243
242
  # this will be called in the background if using backgrounding plugin
244
243
  process(:store) do |io, context|
245
- additional_metadata = io.download do |file|
244
+ io.refresh_metadata!(context)
245
+ io
246
+ end
247
+
248
+ add_metadata do |io, context|
249
+ next unless context[:action] == :store # this will be the case during promotion
250
+
251
+ Shrine.with_file(io) do |file|
246
252
  # example of metadata extraction
247
253
  movie = FFMPEG::Movie.new(file.path) # uses the streamio-ffmpeg gem
248
254
 
@@ -251,10 +257,49 @@ class MyUploader < Shrine
251
257
  "resolution" => movie.resolution,
252
258
  "frame_rate" => movie.frame_rate }
253
259
  end
260
+ end
261
+ end
262
+ ```
254
263
 
255
- io.metadata.merge!(additional_metadata)
264
+ If you want to do both metadata extraction and file processing during
265
+ promotion, you can wrap both in an `UploadedFile#open` block to make
266
+ sure the file content is retrieved from the storage only once.
256
267
 
257
- io
268
+ ```rb
269
+ class MyUploader < Shrine
270
+ plugin :refresh_metadata
271
+ plugin :processing
272
+
273
+ process(:store) do |io, context|
274
+ io.open do |io, context|
275
+ io.refresh_metadata!(context)
276
+
277
+ original = io.download # reuses already open uploaded file
278
+ # ... processing ...
279
+ end
280
+ end
281
+ end
282
+ ```
283
+
284
+ If you're dealing with large files, it's recommended to also use the `tempfile`
285
+ plugin to make sure the same copy of the uploaded file is used for metadata
286
+ extraction (`Shrine.with_file`) and processing (`UploadedFile#tempfile`).
287
+
288
+ ```rb
289
+ Shrine.plugin :tempfile # load it globally so that it overrides `Shrine.with_file`
290
+ ```
291
+ ```rb
292
+ class MyUploader < Shrine
293
+ plugin :refresh_metadata
294
+ plugin :processing
295
+
296
+ process(:store) do |io, context|
297
+ io.open do |io, context|
298
+ io.refresh_metadata!(context)
299
+
300
+ original = io.tempfile # used the cached tempfile
301
+ # ... processing ...
302
+ end
258
303
  end
259
304
  end
260
305
  ```