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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3ed9afc9d88a2781a07ccb93dabbcc8623149d58
4
- data.tar.gz: b4f2759ea268ec45cd2d04221b5715bf27daf678
2
+ SHA256:
3
+ metadata.gz: e062b548ed3da3330604b991144e1fa8bb44c547be9d68f04994056e123c3d7f
4
+ data.tar.gz: e4c52f36474a8a66ed3270d70b91ac4302ef0a1c60fc88381bcdfa037ef8c434
5
5
  SHA512:
6
- metadata.gz: 162a3fa939f7462da506c18abab20900fc5dc464cb59e9d200ad7d585e366255ac88f27235814e90e8ffbc660ce6e023ce49240fca755552133883e2a15adfc6
7
- data.tar.gz: 51d52ca48da06898697bf4a3c2b35ce3c34518ad38a878b5aad9519b4c9755c14e2b90ffee188e6543b62bbf64f64f71bc6a6dbc2c6285d82605f033a826e4ac
6
+ metadata.gz: 3ab3db221bd573ef0d7fcdd3a766a9e93b7b384cadb37668fc6406977875c584caae77eaae01ba8e20902a70e75d2d7218c90f54215db6b47288fe43b7a3857a
7
+ data.tar.gz: 64198d22c129df0cc41e602108fca768d141f4223b806e2272b52e15991e0895b14ae9dbb555e7f3e391840b4c0cbd0dc515d1e8a747d8572c7181af4077bda9
@@ -1,7 +1,31 @@
1
- ## 2.10.1 (2018-04-02)
1
+ ## 2.11.0 (2018-04-28)
2
+
3
+ * Add `Shrine.with_file` for temporarily converting an IO-like object into a file (@janko-m)
4
+
5
+ * Add `:method` value to the `S3#presign` result indicating the HTTP verb that should be used (@janko-m)
6
+
7
+ * Add ability to specify `method: :put` in `S3#presign` to generate data for PUT upload (@janko-m)
8
+
9
+ * Return a `Struct` instead of a `Aws::S3::PresignedPost` object in `S3#presign` (@janko-m)
10
+
11
+ * Deprecate `Storage#presign` returning a custom object in `presign_endpoint` (@janko-m)
12
+
13
+ * Allow `Storage#presign` to return a Hash in `presign_endpoint` (@janko-m)
14
+
15
+ * Add ability to specify upload checksum in `upload_endpoint` plugin (@janko-m)
16
+
17
+ * Don't raise exception in `:mini_magick` and `:ruby_vips` dimensions analyzers when image is invalid (@janko-m)
18
+
19
+ * Don't remove bucket name from S3 URL path with `:host` when `:force_path_style` is set (@janko-m)
2
20
 
3
21
  * Correctly determine MIME type from extension of empty files (@janko-m)
4
22
 
23
+ * Modify `UploadedFile#download` not to reopen the uploaded file if it's already open (@janko-m)
24
+
25
+ * Add `UploadedFile#stream` for streaming content into a writable object (@janko-m)
26
+
27
+ * Deprecate `direct_upload` plugin in favor of `upload_endpoint` and `presign_endpoint` plugins (@janko-m)
28
+
5
29
  ## 2.10.0 (2018-03-28)
6
30
 
7
31
  * Add `:fastimage` analyzer to `determine_mime_type` plugin (@mokolabs)
data/README.md CHANGED
@@ -1,13 +1,22 @@
1
- # Shrine
1
+ # [Shrine]
2
2
 
3
- Shrine is a toolkit for file attachments in Ruby applications.
3
+ Shrine is a toolkit for file attachments in Ruby applications. Some highlights:
4
4
 
5
- If you're not sure why you should care, you're encouraged to read the
6
- [motivation behind creating Shrine][motivation].
5
+ * **Modular design** the [plugin system][plugin system] allows you to load only the functionality you need
6
+ * **Memory friendly** – streaming uploads and downloads make it work great with large files
7
+ * **Cloud storage** – store files on [disk][FileSystem], [AWS S3][S3], [Google Cloud][GCS], [Cloudinary] and others
8
+ * **ORM integrations** – works with [Sequel][sequel plugin], [ActiveRecord][activerecord plugin], [Hanami::Model][hanami plugin] and [Mongoid][mongoid plugin]
9
+ * **Flexible processing** – generate thumbnails with [ImageMagick] or [libvips] using the [ImageProcessing][image_processing] gem
10
+ * **Metadata validation** – [validate files][validation_helpers plugin] based on [extracted metadata][Extracting Metadata]
11
+ * **Direct uploads** – upload asynchronously [to your app][upload_endpoint plugin] or [to the cloud][presign_endpoint plugin] using [Uppy]
12
+ * **Resumable uploads** – make large file uploads [resumable][tus] by pointing [Uppy][uppy tus plugin] to a [resumable endpoint][tus-ruby-server]
13
+ * **Background jobs** – built-in support for [background processing][backgrounding plugin] that supports [any backgrounding library][backgrounding libraries]
14
+
15
+ If you're curious how it compares to other file attachment libraries, see the [Advantages of Shrine].
7
16
 
8
17
  ## Resources
9
18
 
10
- - Documentation: [shrinerb.com](http://shrinerb.com)
19
+ - Documentation: [shrinerb.com](https://shrinerb.com)
11
20
  - Source: [github.com/shrinerb/shrine](https://github.com/shrinerb/shrine)
12
21
  - Bugs: [github.com/shrinerb/shrine/issues](https://github.com/shrinerb/shrine/issues)
13
22
  - Help & Discussion: [groups.google.com/group/ruby-shrine](https://groups.google.com/forum/#!forum/ruby-shrine)
@@ -19,7 +28,7 @@ loads the ORM plugin:
19
28
 
20
29
  ```rb
21
30
  # Gemfile
22
- gem "shrine"
31
+ gem "shrine", "~> 2.0"
23
32
  ```
24
33
 
25
34
  ```rb
@@ -28,11 +37,12 @@ require "shrine/storage/file_system"
28
37
 
29
38
  Shrine.storages = {
30
39
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
31
- store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), # permanent
40
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"), # permanent
32
41
  }
33
42
 
34
43
  Shrine.plugin :sequel # or :activerecord
35
- Shrine.plugin :cached_attachment_data # for forms
44
+ Shrine.plugin :cached_attachment_data # for retaining the cached file across form redisplays
45
+ Shrine.plugin :restore_cached_data # re-extract metadata when attaching a cached file
36
46
  Shrine.plugin :rack_file # for non-Rails apps
37
47
  ```
38
48
 
@@ -66,32 +76,37 @@ end
66
76
 
67
77
  Let's now add the form fields which will use this virtual attribute. We need
68
78
  (1) a file field for choosing files, and (2) a hidden field for retaining the
69
- uploaded file in case of validation errors and [direct uploads].
70
-
71
- ```erb
72
- <form action="/photos" method="post" enctype="multipart/form-data">
73
- <input name="photo[image]" type="hidden" value="<%= @photo.cached_image_data %>">
74
- <input name="photo[image]" type="file">
75
- </form>
76
-
77
- <!-- ActionView::Helpers::FormHelper -->
78
- <%= form_for @photo do |f| %>
79
- <%= f.hidden_field :image, value: @photo.cached_image_data %>
80
- <%= f.file_field :image %>
81
- <% end %>
82
-
83
- <!-- SimpleForm -->
84
- <%= simple_form_for @photo do |f| %>
85
- <%= f.input :image, as: :hidden, input_html: {value: @photo.cached_image_data} %>
86
- <%= f.input :image, as: :file %>
87
- <% end %>
79
+ uploaded file in case of validation errors and for potential [direct
80
+ uploads][direct S3 uploads guide].
81
+
82
+ ```rb
83
+ # with Forme:
84
+ Forme.form(@photo, action: "/photos", method: "post", enctype: "multipart/form-data") do |f|
85
+ f.input :image, type: :hidden, value: @photo.cached_image_data
86
+ f.input :image, type: :file
87
+ f.button "Create"
88
+ end
89
+
90
+ # with Rails form builder:
91
+ form_for @photo do |f|
92
+ f.hidden_field :image, value: @photo.cached_image_data
93
+ f.file_field :image
94
+ f.submit
95
+ end
96
+
97
+ # with Simple Form:
98
+ simple_form_for @photo do |f|
99
+ f.input :image, as: :hidden, input_html: { value: @photo.cached_image_data }
100
+ f.input :image, as: :file
101
+ f.button :submit
102
+ end
88
103
  ```
89
104
 
90
105
  Note that the file field needs to go *after* the hidden field, so that
91
106
  selecting a new file can always override the cached file in the hidden field.
92
107
  Also notice the `enctype="multipart/form-data"` HTML attribute, which is
93
- required for submitting files through the form, though the Rails form builder
94
- will automatically generate it for you.
108
+ required for submitting files through the form; the Rails form builder
109
+ will automatically generate this for you.
95
110
 
96
111
  Now in your router/controller the attachment request parameter can be assigned
97
112
  to the model like any other attribute:
@@ -104,22 +119,22 @@ end
104
119
  ```
105
120
 
106
121
  Once a file is uploaded and attached to the record, you can retrieve a URL to
107
- the uploaded file and display it:
122
+ the uploaded file with `#<attachment>_url` and display it on the page:
108
123
 
109
- ```erb
110
- <img src="<%= @photo.image_url %>">
124
+ ```rb
125
+ image_tag @photo.image_url
111
126
  ```
112
127
 
113
128
  ## Storage
114
129
 
115
130
  A "storage" in Shrine is an object responsible for managing files on a specific
116
- storage service (filesystem, Amazon S3 etc), which implements a generic method
117
- interface. Storages are configured directly and registered under a name in
118
- `Shrine.storages`, so that they can be later used by uploaders.
131
+ storage service (disk, AWS S3, Google Cloud etc), which implements a generic
132
+ method interface. Storages are configured directly and registered under a name
133
+ in `Shrine.storages`, so that they can later be used by uploaders.
119
134
 
120
135
  ```rb
121
136
  # Gemfile
122
- gem "aws-sdk-s3", "~> 1.2" # for Amazon S3 storage
137
+ gem "aws-sdk-s3", "~> 1.2" # for AWS S3 storage
123
138
  ```
124
139
  ```rb
125
140
  require "shrine/storage/s3"
@@ -133,25 +148,26 @@ s3_options = {
133
148
 
134
149
  Shrine.storages = {
135
150
  cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
136
- store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
151
+ store: Shrine::Storage::S3.new(**s3_options),
137
152
  }
138
153
  ```
139
154
 
140
- The above example sets up Amazon S3 storage both for temporary and permanent
141
- storage, which allows for [direct uploads]. The `:cache` and `:store` names are
142
- special only in terms that the attacher will automatically pick them up, but
143
- you can also register more than two storages under different names.
155
+ The above example sets up AWS S3 storage both for temporary and permanent
156
+ storage, which is suitable for [direct uploads][direct S3 uploads guide]. The
157
+ `:cache` and `:store` names are special only in terms that the attacher will
158
+ automatically pick them up, but you can also register more storages under
159
+ different names.
144
160
 
145
161
  Shrine ships with [FileSystem] and [S3] storage, take a look at their
146
- documentation for more details on various features they support. There are also
147
- [many more Shrine storages][external storages] shipping as external gems.
162
+ documentation for more details on various features they support. There are
163
+ [many more Shrine storages][external storages] provided by external gems, and
164
+ you can also [create your own storage][creating storage].
148
165
 
149
166
  ## Uploader
150
167
 
151
168
  Uploaders are subclasses of `Shrine`, and are essentially wrappers around
152
- storages. In addition to actually calling the underlying storage when they need
153
- to, they also perform many generic tasks which aren't related to a particular
154
- storage (like processing, extracting metadata, logging etc).
169
+ storages. They perform common tasks around upload that aren't related to a
170
+ particular storage.
155
171
 
156
172
  ```rb
157
173
  class ImageUploader < Shrine
@@ -164,7 +180,7 @@ uploader #=> uploader for storage registered under `:store`
164
180
  ```
165
181
 
166
182
  It's common to create an uploader for each type of file that you want to handle
167
- (image, video, audio, document etc), but you can structure them any way that
183
+ (image, video, audio, document etc), but really you can organize them in any way
168
184
  you like.
169
185
 
170
186
  ### Uploading
@@ -183,45 +199,45 @@ Some of the tasks performed by `#upload` include:
183
199
  * extracting metadata
184
200
  * generating location
185
201
  * uploading (this is where the storage is called)
186
- * closing the file
202
+ * closing the uploaded file
187
203
 
188
204
  ### IO abstraction
189
205
 
190
- Shrine is able to upload any IO-like object that respond to `#read`, `#size`,
191
- `#rewind`, `#eof?` and `#close`. This foremost includes all real IO objects
206
+ Shrine is able to upload any IO-like object that responds to `#read`,
207
+ `#rewind`, `#eof?` and `#close`. This includes built-in IO and IO-like objects
192
208
  like File, Tempfile and StringIO.
193
209
 
194
210
  When a file is uploaded to a Rails app, it will be represented by an
195
211
  ActionDispatch::Http::UploadedFile object in the params. This is also an
196
212
  IO-like object accepted by Shrine. In other Rack applications the uploaded file
197
- will be represented as a Hash, but it can still be attached when [`rack_file`]
213
+ will be represented as a Hash, but it can still be attached when `rack_file`
198
214
  plugin is loaded.
199
215
 
200
- Finally, the `Shrine::UploadedFile` object, returned by uploading, is itself an
201
- IO-like object. This makes it incredibly easy to reupload a file from one
202
- storage to another, and this is used by the attacher to reupload a file stored
203
- on temporary storage to permanent storage.
204
-
205
- ### Deleting
206
-
207
- The uploader can also delete uploaded files via `#delete`. Internally this just
208
- delegates to the uploaded file, but some plugins bring additional behaviour
209
- (e.g. logging).
216
+ Here are some examples of IO objects that can be uploaded:
210
217
 
211
218
  ```rb
212
- uploaded_file = uploader.upload(file)
213
- # ...
214
- uploader.delete(uploaded_file)
219
+ uploader.upload File.open("/path/to/file", "rb") # upload from disk
220
+ uploader.upload StringIO.new("file content") # upload from memory
221
+ uploader.upload ActionDispatch::Http::UploadedFile.new # upload from Rails controller
222
+ uploader.upload Shrine.rack_file({ tempfile: Tempfile.new }) # upload from Rack controller
223
+ uploader.upload Rack::Test::UploadedFile.new # upload from rack-test
224
+ uploader.upload Down.open("https://example.org/file") # upload from internet
215
225
  ```
216
226
 
227
+ `Shrine::UploadedFile`, the object returned after upload, is itself an IO-like
228
+ object as well. This makes it trivial to reupload a file from one storage to
229
+ another, and this is used by the attacher to reupload a file stored on
230
+ temporary storage to permanent storage.
231
+
217
232
  ## Uploaded file
218
233
 
219
234
  The `Shrine::UploadedFile` object represents the file that was uploaded to the
220
- storage. It contains the following information:
235
+ storage, and it's what's returned from `Shrine#upload` or when retrieving a
236
+ record attachment. It contains the following information:
221
237
 
222
238
  * `storage` – identifier of the storage the file was uploaded to
223
- * `id` – the location of the file on the storage
224
- * `metadata` – file metadata that was extracted during upload
239
+ * `id` – location of the file on the storage
240
+ * `metadata` – file metadata that was extracted before upload
225
241
 
226
242
  ```rb
227
243
  uploaded_file = uploader.upload(file)
@@ -237,11 +253,12 @@ uploaded_file.to_json #=> '{"id":"949sdjg834.jpg","storage":"store","metadata":
237
253
  It comes with many convenient methods that delegate to the storage:
238
254
 
239
255
  ```rb
240
- uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
241
- uploaded_file.open #=> IO object
242
- uploaded_file.download #=> #<File:/var/folders/.../20180302-33119-1h1vjbq.jpg>
243
- uploaded_file.exists? #=> true
244
- uploaded_file.delete # deletes the file from the storage
256
+ uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
257
+ uploaded_file.open # opens the uploaded file
258
+ uploaded_file.download #=> #<File:/var/folders/.../20180302-33119-1h1vjbq.jpg>
259
+ uploaded_file.stream(destination) # streams uploaded content into a writable destination
260
+ uploaded_file.exists? #=> true
261
+ uploaded_file.delete # deletes the file from the storage
245
262
 
246
263
  # open/download the uploaded file for the duration of the block
247
264
  uploaded_file.open { |io| io.read }
@@ -249,7 +266,7 @@ uploaded_file.download { |tempfile| tempfile.read }
249
266
  ```
250
267
 
251
268
  It also implements the IO-like interface that conforms to Shrine's IO
252
- abstraction, which allows it to be uploaded to other storages.
269
+ abstraction, which allows it to be uploaded again to other storages.
253
270
 
254
271
  ```rb
255
272
  uploaded_file.read # returns content of the uploaded file
@@ -258,6 +275,13 @@ uploaded_file.rewind # rewinds the IO
258
275
  uploaded_file.close # closes the IO
259
276
  ```
260
277
 
278
+ If you want to retrieve the content of the uploaded file, you can use a
279
+ combination of `#open` and `#read`:
280
+
281
+ ```rb
282
+ uploaded_file.open(&:read) #=> "..." (binary content of the uploaded file)
283
+ ```
284
+
261
285
  ## Attachment
262
286
 
263
287
  Storages, uploaders, and uploaded file objects are the main components for
@@ -281,7 +305,7 @@ class Photo < Sequel::Model # ActiveRecord::Base
281
305
  end
282
306
  ```
283
307
 
284
- You can choose whichever of these three syntaxes you prefer. In any case this
308
+ You can choose whichever of these three syntaxes you prefer. Either of these
285
309
  will create a `Shrine::Attachment` module with attachment methods for the
286
310
  specified attribute, which then get added to your model when you include it:
287
311
 
@@ -315,9 +339,8 @@ photo.destroy
315
339
  photo.image.exists? #=> false
316
340
  ```
317
341
 
318
- If there is already a file attached, and the attachment is overriden (either
319
- with a new file or no file), the previous attachment will get deleted when the
320
- record gets saved.
342
+ If there is already a file attached and a new file is attached, the previous
343
+ attachment will get deleted when the record gets saved.
321
344
 
322
345
  ```rb
323
346
  photo.update(image: new_file) # changes the attachment and deletes previous
@@ -327,21 +350,17 @@ photo.update(image: nil) # removes the attachment and deletes previous
327
350
 
328
351
  In addition to assigning raw files, you can also assign a JSON representation
329
352
  of files that are already uploaded to the temporary storage. This allows Shrine
330
- to retain cached files in case of validation errors, and handle [direct
331
- uploads], via the hidden form field.
353
+ to retain cached files in case of validation errors and handle [direct
354
+ uploads] via the hidden form field.
332
355
 
333
356
  ```rb
334
- photo.image = '{
335
- "id": "9260ea09d8effd.jpg",
336
- "storage": "cache",
337
- "metadata": { ... }
338
- }'
357
+ photo.image = '{"id":"9260ea09d8effd.jpg","storage":"cache","metadata":{...}}'
339
358
  ```
340
359
 
341
360
  ## Attacher
342
361
 
343
362
  The model attachment attributes and callbacks just delegate the behaviour
344
- to a `Shrine::Attacher` object.
363
+ to ther underlying `Shrine::Attacher` object.
345
364
 
346
365
  ```rb
347
366
  photo.image_attacher #=> #<Shrine::Attacher>
@@ -380,9 +399,9 @@ photo.save # promotes to :other_store storage
380
399
  Whenever the attacher uploads or deletes files, it sends a `context` hash
381
400
  which includes `:record`, `:name`, and `:action` keys, so that you can perform
382
401
  processing or generate location differently depending on this information. See
383
- [Context] section for more details.
402
+ "Context" section for more details.
384
403
 
385
- For more information about `Shrine::Attacher`, see [Using Attacher] guide.
404
+ For more information about `Shrine::Attacher`, see the [Using Attacher] guide.
386
405
 
387
406
  ## Plugin system
388
407
 
@@ -405,9 +424,12 @@ class ImageUploader < Shrine
405
424
  end
406
425
  ```
407
426
 
427
+ If you want to extend Shrine functionality with custom behaviour, you can also
428
+ [create your own plugin][creating plugin].
429
+
408
430
  ## Metadata
409
431
 
410
- Shrine automatically extracts available file metadata and saves them to the
432
+ Shrine automatically extracts some basic file metadata and saves them to the
411
433
  `Shrine::UploadedFile`. You can access them through the `#metadata` hash or via
412
434
  metadata methods:
413
435
 
@@ -427,132 +449,56 @@ uploaded_file.size #=> 345993
427
449
 
428
450
  ### MIME type
429
451
 
430
- By default "mime_type" will be inherited from `#content_type` of the uploaded
431
- file, which is set from the "Content-Type" request header, but this header is
432
- determined by the browser solely based on the file extension. This means that
433
- by default Shrine's "mime_type" is **not guaranteed** to hold the actual MIME
434
- type of the file.
452
+ By default `mime_type` will be inherited from `#content_type` attribute of the
453
+ uploaded file, which is set from the `Content-Type` request header. However,
454
+ this header is determined by the browser solely based on the file extension.
455
+ This means that by default Shrine's `mime_type` is *not guaranteed* to hold
456
+ the actual MIME type of the file.
435
457
 
436
- However, if you load the `determine_mime_type` plugin, that will make Shrine
437
- always extract the MIME type from **file content**.
458
+ To remedy that, you can load the `determine_mime_type` plugin, which will make
459
+ Shrine extract the MIME type from *file content*.
438
460
 
439
461
  ```rb
440
462
  Shrine.plugin :determine_mime_type
441
463
  ```
442
464
  ```rb
443
- File.write("image.png", "<?php ... ?>") # PHP file with a .png extension
444
- photo = Photo.create(image: File.open("image.png"))
465
+ photo = Photo.create(image: StringIO.new("<?php ... ?>"))
445
466
  photo.image.mime_type #=> "text/x-php"
446
467
  ```
447
468
 
448
- By the default the UNIX [`file`] utility is used, but you can also choose a
449
- different analyzer, see plugin's documentation for more details.
469
+ By the default the UNIX [`file`] utility is used to determine the MIME type,
470
+ but you can also choose a different analyzer see the plugin documentation for
471
+ more details.
450
472
 
451
473
  ### Custom metadata
452
474
 
453
- In addition to the built-in metadata, you can also extract and store completely
454
- custom metadata with the `add_metadata` plugin. For example, if we're uploading
455
- videos, we could store additional video-specific metadata:
456
-
457
- ```rb
458
- require "streamio-ffmpeg"
459
-
460
- class VideoUploader < Shrine
461
- plugin :add_metadata
462
-
463
- add_metadata do |io, context|
464
- movie = FFMPEG::Movie.new(io.path)
465
-
466
- { "duration" => movie.duration,
467
- "bitrate" => movie.bitrate,
468
- "resolution" => movie.resolution,
469
- "frame_rate" => movie.frame_rate }
470
- end
471
- end
472
- ```
473
- ```rb
474
- video.metadata["duration"] #=> 7.5
475
- video.metadata["bitrate"] #=> 481
476
- video.metadata["resolution"] #=> "640x480"
477
- video.metadata["frame_rate"] #=> 16.72
478
- ```
475
+ In addition to `size`, `filename`, and `mime_type`, you can also extract image
476
+ dimensions using the `store_dimensions` plugin, as well as any custom metadata
477
+ using the `add_metadata` plugin. Check out the [Extracting Metadata] guide for
478
+ more details.
479
479
 
480
480
  ## Processing
481
481
 
482
- You can have Shrine perform file processing before uploading to storage. It's
483
- generally best to process files prior to uploading to permanent storage,
484
- because at that point the selected file has been succesfully validated, and
485
- this part can be moved into a background job.
482
+ Shrine's `processing` plugin allows you to intercept when the cached file is
483
+ being uploaded to permanent storage, and do any file processing your might want.
486
484
 
487
- This promote phase is called `:store`, and we can use the `processing` plugin
488
- to define processing for that phase:
485
+ If you're uploading images, it's common to want to generate various thumbnails.
486
+ It's recommended to use the **[ImageProcessing][image_processing]** gem for
487
+ this, which provides a convenient API over [ImageMagick] and [libvips]. You
488
+ also need to load the `versions` plugin to be able to save multiple files.
489
489
 
490
- ```rb
491
- class ImageUploader < Shrine
492
- plugin :processing
493
-
494
- process(:store) do |io, context|
495
- # ...
496
- end
497
- end
490
+ ```sh
491
+ $ brew install imagemagick
498
492
  ```
499
-
500
- Now, how do we do the actual processing? Well, Shrine actually doesn't ship
501
- with any file processing functionality, because that is a generic problem that
502
- belongs in separate libraries. If the type of files you're uploading are
503
- images, I created the [image_processing] gem which you can use with Shrine:
504
-
505
493
  ```rb
506
494
  # Gemfile
507
- gem "image_processing", "~> 0.10"
508
- gem "mini_magick", "~> 4.0"
509
- ```
510
- ```rb
511
- require "image_processing/mini_magick"
512
-
513
- class ImageUploader < Shrine
514
- plugin :processing
515
-
516
- process(:store) do |io, context|
517
- original = io.download
518
-
519
- resized = ImageProcessing::MiniMagick
520
- .source(original)
521
- .resize_to_limit!(800, 800)
522
-
523
- original.close!
524
-
525
- resized
526
- end
527
- end
495
+ gem "image_processing", "~> 1.0"
528
496
  ```
529
-
530
- Here the `io` is a cached `Shrine::UploadedFile`, so we need to download it to
531
- a file, since file processing tools usually work with files on the filesystem.
532
-
533
- Shrine treats file processing as a functional transformation; you are given the
534
- original file, and how you're going to perform processing is entirely up to
535
- you, you only need to return the processed files at the end of the block. Then
536
- instead of uploading the original file, Shrine will continue to upload the
537
- files that the processing block returned.
538
-
539
- ### Versions
540
-
541
- Sometimes we want to generate multiple files as the result of processing. If
542
- we're uploading images, we might want to store various thumbnails alongside the
543
- original image. If we're uploading videos, we might want to save screenshots
544
- or transcode the video into different formats.
545
-
546
- To be able to save multiple files, we just need to load the `versions` plugin,
547
- and then in processing block we can return a Hash of files. It is recommended
548
- to also load the `delete_raw` plugin, so that processed files are automatically
549
- deleted after uploading.
550
-
551
497
  ```rb
552
498
  require "image_processing/mini_magick"
553
499
 
554
500
  class ImageUploader < Shrine
555
- plugin :processing
501
+ plugin :processing # allows hooking into promoting
556
502
  plugin :versions # enable Shrine to handle a hash of files
557
503
  plugin :delete_raw # delete processed files after uploading
558
504
 
@@ -601,39 +547,11 @@ photo.image[:medium].mime_type #=> "image/jpeg"
601
547
  The `versions` plugin also expands `#<attachment>_url` to accept version names:
602
548
 
603
549
  ```rb
604
- photo.image_url(:large) #=> "..."
550
+ photo.image_url(:large) #=> "https://..."
605
551
  ```
606
552
 
607
- ### Custom processing
608
-
609
- Your processing tool doesn't have to be in any way designed for Shrine
610
- ([image_processing] that we saw earlier is a generic library), the only thing
611
- that you need to do is return processed files as some kind of IO objects. Here
612
- is an example of transcoding a video using [ffmpeg]:
613
-
614
- ```rb
615
- require "streamio-ffmpeg"
616
-
617
- class VideoUploader < Shrine
618
- plugin :processing
619
- plugin :versions
620
- plugin :delete_raw
621
-
622
- process(:store) do |io, context|
623
- mov = io.download
624
- video = Tempfile.new(["video", ".mp4"], binmode: true)
625
- screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
626
-
627
- movie = FFMPEG::Movie.new(mov.path)
628
- movie.transcode(video.path)
629
- movie.screenshot(screenshot.path)
630
-
631
- mov.delete
632
-
633
- {video: video, screenshot: screenshot}
634
- end
635
- end
636
- ```
553
+ For more details, including examples of how to do custom processing, see the
554
+ [File Processing] guide.
637
555
 
638
556
  ## Context
639
557
 
@@ -642,16 +560,16 @@ argument, which is forwarded to all other tasks like processing, extracting
642
560
  metadata and generating location.
643
561
 
644
562
  ```rb
645
- uploader.upload(file, {foo: "bar"}) # context hash is forwarded to all tasks around upload
563
+ uploader.upload(file, { foo: "bar" }) # context hash is forwarded to all tasks around upload
646
564
  ```
647
565
 
648
566
  Some options are actually recognized by Shrine, like `:location` and
649
- `:upload_options`, and some are added by plugins. However, most options are
650
- there just to provide you context, for more flexibility in performing tasks and
651
- better logging.
567
+ `:upload_options`, some are added by plugins, and the rest are there just to
568
+ provide additional context, for more flexibility in performing tasks and more
569
+ descriptive logging.
652
570
 
653
571
  The attacher automatically includes additional `context` information for each
654
- upload and delete:
572
+ upload and delete operation:
655
573
 
656
574
  * `context[:record]` – model instance where the file is attached
657
575
  * `context[:name]` – name of the attachment attribute on the model
@@ -668,7 +586,7 @@ end
668
586
  ## Validation
669
587
 
670
588
  Shrine can perform file validations for files assigned to the model. The
671
- validations are registered inside a `Attacher.validate` block, and you can load
589
+ validations are defined inside the `Attacher.validate` block, and you can load
672
590
  the `validation_helpers` plugin to get convenient file validation methods:
673
591
 
674
592
  ```rb
@@ -686,62 +604,18 @@ end
686
604
  user = User.new
687
605
  user.cv = File.open("cv.pdf")
688
606
  user.valid? #=> false
689
- user.errors.to_hash #=> {cv: ["is too large (max is 5 MB)"]}
690
- ```
691
-
692
- You can also do custom validations:
693
-
694
- ```rb
695
- class ImageUploader < Shrine
696
- Attacher.validate do
697
- get.download do |tempfile|
698
- errors << "image is corrupted" unless ImageProcessing::MiniMagick.valid_image?(tempfile)
699
- end
700
- end
701
- end
607
+ user.errors.to_hash #=> {:cv=>["is too large (max is 5 MB)"]}
702
608
  ```
703
609
 
704
- When file validations fail, Shrine will by default keep the invalid cached file
705
- assigned to the model instance. If you want the invalid file to be deassigned,
706
- you can load the `remove_invalid` plugin.
707
-
708
- The `Attacher.validate` block is executed in context of a `Shrine::Attacher`
709
- instance:
710
-
711
- ```rb
712
- class DocumentUploader < Shrine
713
- Attacher.validate do
714
- self #=> #<Shrine::Attacher>
715
-
716
- get #=> #<Shrine::UploadedFile>
717
- record #=> #<User>
718
- name #=> :cv
719
- end
720
- end
721
- ```
722
-
723
- Validations are inherited from superclasses, but you need to call them manually
724
- when defining more validations:
725
-
726
- ```ruby
727
- class ApplicationUploader < Shrine
728
- Attacher.validate { validate_max_size 5.megabytes }
729
- end
730
-
731
- class ImageUploader < ApplicationUploader
732
- Attacher.validate do
733
- super() # empty braces are required
734
- validate_mime_type_inclusion %w[image/jpeg image/jpg image/png]
735
- end
736
- end
737
- ```
610
+ See the [File Validation] guide and `validation_helpers` plugin documentation
611
+ for more details.
738
612
 
739
613
  ## Location
740
614
 
741
615
  Before Shrine uploads a file, it generates a random location for it. By default
742
616
  the hierarchy is flat; all files are stored in the root directory of the
743
- storage. You can change how the location is generated by overriding
744
- `#generate_location`:
617
+ storage. The `pretty_location` plugin provides a nice default hierarchy, but
618
+ you can also override `#generate_location` with a custom implementation:
745
619
 
746
620
  ```rb
747
621
  class ImageUploader < Shrine
@@ -765,7 +639,7 @@ uploads/
765
639
  ```
766
640
 
767
641
  Note that there should always be a random component in the location, so that
768
- any ORM dirty tracking is detected properly. Inside `#generate_location` you
642
+ the ORM dirty tracking is detected properly. Inside `#generate_location` you
769
643
  can also access the extracted metadata through `context[:metadata]`.
770
644
 
771
645
  When uploading single files, it's possible to bypass `#generate_location` via
@@ -777,20 +651,17 @@ uploader.upload(file, location: "some/specific/location.mp4")
777
651
 
778
652
  ## Direct uploads
779
653
 
780
- While having files uploaded on form submit is simplest to implement, it doesn't
781
- provide the best user experience, because the user doesn't know how long they
782
- need to wait for the file to get uploaded.
654
+ To really improve the user experience, it's recommended to start uploading the
655
+ files asynchronously as soon they're selected. This way the UI is still
656
+ responsive during upload, so the user can fill in other fields while the files
657
+ are being uploaded, and if you display a progress bar they can see when the
658
+ upload will finish.
783
659
 
784
- To improve the user experience, the application can actually start uploading
785
- the file **asynchronously** already when it has been selected, and provide a
786
- progress bar. This way the user can estimate when the upload is going to
787
- finish, and they can continue filling in other fields in the form while the
788
- file is being uploaded.
789
-
790
- Shrine comes with the `upload_endpoint` plugin, which provides a Rack endpoint
791
- that accepts file uploads and forwards them to specified storage. We want to
792
- set it up to upload to *temporary* storage, because we're replacing the caching
793
- step in the default synchronous workflow.
660
+ The asynchronous uploads will have to go to a separate endpoint than the one
661
+ where the form is submitted. You can use Shrine's `upload_endpoint` plugin to
662
+ create a Rack app that accepts file uploads and forwards them to the specified
663
+ storage. We want to set it up to upload to *temporary* storage (`:cache`),
664
+ because we're replacing the caching step from the default synchronous workflow.
794
665
 
795
666
  ```rb
796
667
  Shrine.plugin :upload_endpoint
@@ -809,25 +680,37 @@ Rails.application.routes.draw do
809
680
  end
810
681
  ```
811
682
 
812
- The above created a `POST /images/upload` endpoint. You can now use [Uppy] to
813
- upload files asynchronously to the `/images/upload` endpoint the moment they
814
- are selected. Once the file has been uploaded, the endpoint will return JSON
815
- data of the uploaded file, which the client can then write to a hidden
816
- attachment field, to be submitted instead of the raw file.
817
-
818
- Many popular storage services can accept file uploads directly from the client
819
- ([Amazon S3], [Google Cloud Storage], [Microsoft Azure Storage] etc), which
820
- means you can avoid uploading files through your app. If you're using one of
821
- these storage services, you can use the `presign_endpoint` plugin to generate
822
- URL, fields, and headers that can be used to upload files directly to the
823
- storage service. The only difference from the `upload_endpoint` workflow is
824
- that the client has the extra step of fetching the request information before
825
- uploading the file.
826
-
827
- See the [upload_endpoint] and [presign_endpoint] plugin documentations and
828
- [Direct Uploads to S3][direct uploads] guide for more details, as well as the
829
- [Roda][roda_demo] and [Rails][rails_demo] demo apps which implement multiple
830
- uploads directly to S3.
683
+ The above will add a `POST /images/upload` route to your app. You can now
684
+ use the **[Uppy]** JavaScript library to upload files to this endpoint as soon
685
+ they're selected, and write the result to the hidden field. The JavaScript code
686
+ for this will depend on your application, see [this walkthrough][direct uploads
687
+ walkthrough] that adds direct uploads from scratch.
688
+
689
+ You can also upload files directly to the cloud (AWS S3, Google Cloud etc),
690
+ using Shrine's `presign_endpoint` plugin. See [this walkthrough][direct S3
691
+ uploads walkthrough] that adds direct S3 uploads from scratch using Uppy, as
692
+ well as the [Direct Uploads to S3][direct S3 uploads guide] guide that provides
693
+ some useful tips. Also check out the [Roda][roda demo] or [Rails][rails demo]
694
+ demo app which implements multiple uploads directly to S3.
695
+
696
+ ### Resumable uploads
697
+
698
+ When your app is dealing with large uploads (e.g. videos), keep in mind that it
699
+ can be challening for your users to upload these large files to your app,
700
+ depending on their internet connection. If the connection breaks at any point
701
+ during uploading, the upload needs to be restarted from the beginning.
702
+
703
+ Luckily, there is a solution for this. **[Tus.io][tus]** is an open protocol
704
+ for resumable file uploads, which enables the client and the server to achieve
705
+ reliable file uploads even on unstable connections, by enabling the upload to
706
+ be resumed in case of interruptions, even after the browser was closed or the
707
+ device was shut down.
708
+
709
+ On the client side you can use [Uppy][uppy tus plugin] with [tus-js-client],
710
+ have it upload files to a [tus-ruby-server], and finally attach the uploaded
711
+ files with the help of [shrine-tus]. See [this walkthrough][resumable uploads
712
+ walkthrough] that adds resumable uploads from scratch, as well as the [Roda
713
+ demo][resumable demo] for a complete example.
831
714
 
832
715
  ## Backgrounding
833
716
 
@@ -858,24 +741,10 @@ class DeleteJob
858
741
  end
859
742
  ```
860
743
 
861
- The above puts all promoting (uploading cached file to permanent storage) and
862
- deleting of files into background jobs using Sidekiq. Obviously instead of
863
- Sidekiq you can use [any other backgrounding library][backgrounding libraries].
864
-
865
- The main advantages of Shrine's backgrounding support over other file attachment
866
- libraries are:
867
-
868
- * **User experience** – Before starting the background job, Shrine will save the
869
- record with the cached attachment so that it can be immediately shown to the
870
- user. With other file upload libraries users cannot see the file until the
871
- background job has finished.
872
- * **Simplicity** – Instead of shipping with workers for you, Shrine allows you
873
- to write your own workers and plug them in very easily. And no extra
874
- columns are required.
875
- * **Generality** – This setup will automatically be used for all uploaders,
876
- types of files and models.
877
- * **Safety** – All of Shrine's features have been designed to take delayed
878
- storing into account, and concurrent requests are handled as well.
744
+ The above puts promoting (uploading cached file to permanent storage) and
745
+ deleting of files for all uploaders into background jobs using Sidekiq.
746
+ Obviously instead of Sidekiq you can use [any other backgrounding
747
+ library][backgrounding libraries].
879
748
 
880
749
  ## Clearing cache
881
750
 
@@ -883,7 +752,7 @@ Shrine doesn't automatically delete files uploaded to temporary storage, instead
883
752
  you should set up a separate recurring task that will automatically delete old
884
753
  cached files.
885
754
 
886
- Most of Shrine storage objects come with a `#clear!` method, which you can call
755
+ Most of Shrine storage classes come with a `#clear!` method, which you can call
887
756
  in a recurring script. For FileSystem and S3 storage it would look like this:
888
757
 
889
758
  ```rb
@@ -897,9 +766,9 @@ s3 = Shrine.storages[:cache]
897
766
  s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 } # delete files older than 1 week
898
767
  ```
899
768
 
900
- Note that for S3 you can also configure bucket lifecycle rules to do this for
901
- you. This can be done either from the [AWS Console][S3 lifecycle console] or
902
- via an [API call][S3 lifecycle API]:
769
+ Note that for AWS S3 you can also configure bucket lifecycle rules to do this
770
+ for you. This can be done either from the [AWS Console][S3 lifecycle console]
771
+ or via an [API call][S3 lifecycle API]:
903
772
 
904
773
  ```rb
905
774
  require "aws-sdk-s3"
@@ -960,38 +829,6 @@ Because `opts` is cloned in subclasses, overriding settings works with
960
829
  inheritance. The `opts` hash is used internally by plugins to store
961
830
  configuration.
962
831
 
963
- ## On-the-fly processing
964
-
965
- Shrine allows you to define processing that will be performed on upload.
966
- However, what if you want to have processing performed on-the-fly when the URL
967
- is requested? Unlike Refile or Dragonfly, Shrine doesn't come with an image
968
- server built in; instead it expects you to integrate any of the existing
969
- generic image servers.
970
-
971
- Shrine has integrations for many commercial on-the-fly processing services,
972
- including [Cloudinary], [Imgix] and [Uploadcare].
973
-
974
- If you don't want to use a commercial service, [Dragonfly] is a great
975
- open-source image server. See [this blog post][processing post] on how you can
976
- integrate Dragonfly with Shrine.
977
-
978
- ## Chunked & Resumable uploads
979
-
980
- When you're accepting large file uploads, you normally want to split it into
981
- multiple chunks. This way if an upload fails, it is just for one chunk and can
982
- be retried, while the previous chunks remain uploaded.
983
-
984
- [Tus][tus] is an open protocol for resumable file uploads, which enables the
985
- client and the server to achieve reliable file uploads, even on unstable
986
- networks, with the possibility to resume the upload even after the browser is
987
- closed or the device are shut down. You can use a client library like
988
- [tus-js-client] to upload the file to [tus-ruby-server], and attach the
989
- uploaded file to a record using [shrine-url]. See [shrine-tus-demo] for an
990
- example of complete implementation.
991
-
992
- Another option might be to do chunked uploads directly to your storage service,
993
- if the storage service supports it (e.g. Amazon S3 or Google Cloud Storage).
994
-
995
832
  ## Inspiration
996
833
 
997
834
  Shrine was heavily inspired by [Refile] and [Roda]. From Refile it borrows the
@@ -1005,6 +842,7 @@ system.
1005
842
  * CarrierWave
1006
843
  * Dragonfly
1007
844
  * Refile
845
+ * Active Storage
1008
846
 
1009
847
  ## Code of Conduct
1010
848
 
@@ -1015,41 +853,51 @@ mailing lists is expected to follow the [Shrine code of conduct][CoC].
1015
853
 
1016
854
  The gem is available as open source under the terms of the [MIT License].
1017
855
 
1018
- [motivation]: https://twin.github.io/better-file-uploads-with-shrine-motivation/
1019
- [FileSystem]: http://shrinerb.com/rdoc/classes/Shrine/Storage/FileSystem.html
1020
- [S3]: http://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
1021
- [direct uploads]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
1022
- [external storages]: http://shrinerb.com/#external
1023
- [`rack_file`]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/RackFile.html
1024
- [Using Attacher]: http://shrinerb.com/rdoc/files/doc/attacher_md.html
1025
- [plugins]: http://shrinerb.com/#plugins
1026
- [`file`]: http://linux.die.net/man/1/file
1027
- [backgrounding]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/Backgrounding.html
1028
- [Context]: https://github.com/shrinerb/shrine#context
856
+ [Shrine]: https://shrinerb.com
857
+ [plugin system]: https://twin.github.io/the-plugin-system-of-sequel-and-roda/
858
+ [FileSystem]: https://shrinerb.com/rdoc/classes/Shrine/Storage/FileSystem.html
859
+ [S3]: https://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
860
+ [GCS]: https://github.com/renchap/shrine-google_cloud_storage
861
+ [Cloudinary]: https://github.com/shrinerb/shrine-cloudinary
862
+ [Transloadit]: https://github.com/shrinerb/shrine-transloadit
863
+ [activerecord plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/Activerecord.html
864
+ [sequel plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/Sequel.html
865
+ [hanami plugin]: https://github.com/katafrakt/hanami-shrine
866
+ [mongoid plugin]: https://github.com/shrinerb/shrine-mongoid
1029
867
  [image_processing]: https://github.com/janko-m/image_processing
1030
- [ffmpeg]: https://github.com/streamio/streamio-ffmpeg
868
+ [ImageMagick]: https://www.imagemagick.org/script/index.php
869
+ [libvips]: http://jcupitt.github.io/libvips/
870
+ [validation_helpers plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/ValidationHelpers.html
871
+ [upload_endpoint plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/UploadEndpoint.html
872
+ [presign_endpoint plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/PresignEndpoint.html
1031
873
  [Uppy]: https://uppy.io
1032
- [Amazon S3]: https://aws.amazon.com/s3/
1033
- [Google Cloud Storage]: https://cloud.google.com/storage/
1034
- [Microsoft Azure Storage]: https://azure.microsoft.com/en-us/services/storage/
1035
- [upload_endpoint]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/UploadEndpoint.html
1036
- [presign_endpoint]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/PresignEndpoint.html
1037
- [Cloudinary]: https://github.com/shrinerb/shrine-cloudinary
1038
- [Imgix]: https://github.com/shrinerb/shrine-imgix
1039
- [Uploadcare]: https://github.com/shrinerb/shrine-uploadcare
1040
- [Dragonfly]: http://markevans.github.io/dragonfly/
1041
- [tus]: http://tus.io
874
+ [tus]: https://tus.io
875
+ [uppy tus plugin]: https://uppy.io/docs/tus/
1042
876
  [tus-ruby-server]: https://github.com/janko-m/tus-ruby-server
877
+ [backgrounding plugin]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/Backgrounding.html
878
+ [Advantages of Shrine]: https://shrinerb.com/rdoc/files/doc/advantages_md.html
879
+ [external storages]: https://shrinerb.com/#external
880
+ [creating storage]: https://shrinerb.com/rdoc/files/doc/creating_storages_md.html
881
+ [creating plugin]: https://shrinerb.com/rdoc/files/doc/creating_plugins_md.html
882
+ [Using Attacher]: https://shrinerb.com/rdoc/files/doc/attacher_md.html
883
+ [plugins]: https://shrinerb.com/#plugins
884
+ [`file`]: http://linux.die.net/man/1/file
885
+ [Extracting Metadata]: https://shrinerb.com/rdoc/files/doc/metadata_md.html
886
+ [File Processing]: https://shrinerb.com/rdoc/files/doc/processing_md.html
887
+ [File Validation]: https://shrinerb.com/rdoc/files/doc/validation_md.html
888
+ [direct uploads walkthrough]: https://gist.github.com/janko-m/9aea154d72eb85b1fbfa16e1d77946e5#adding-direct-uploads-to-a-roda--sequel-app-with-shrine
889
+ [direct S3 uploads walkthrough]: https://gist.github.com/janko-m/9aea154d72eb85b1fbfa16e1d77946e5#adding-direct-s3-uploads-to-a-roda--sequel-app-with-shrine
890
+ [direct S3 uploads guide]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
891
+ [roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
892
+ [rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
1043
893
  [tus-js-client]: https://github.com/tus/tus-js-client
1044
- [shrine-tus-demo]: https://github.com/shrinerb/shrine-tus-demo
1045
- [shrine-url]: https://github.com/shrinerb/shrine-url
1046
- [Roda]: https://github.com/jeremyevans/roda
1047
- [Refile]: https://github.com/refile/refile
1048
- [MIT License]: http://opensource.org/licenses/MIT
1049
- [CoC]: https://github.com/shrinerb/shrine/blob/master/CODE_OF_CONDUCT.md
1050
- [roda_demo]: https://github.com/shrinerb/shrine/tree/master/demo
1051
- [rails_demo]: https://github.com/erikdahlstrand/shrine-rails-example
894
+ [shrine-tus]: https://github.com/shrinerb/shrine-tus
895
+ [resumable uploads walkthrough]: https://gist.github.com/janko-m/f05188205cb9af75a27ead78d068b5d3#adding-resumable-uploads-to-a-roda--sequel-app-with-shrine
896
+ [resumable demo]: https://github.com/shrinerb/shrine-tus-demo
1052
897
  [backgrounding libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-libraries
1053
898
  [S3 lifecycle Console]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
1054
899
  [S3 lifecycle API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_lifecycle_configuration-instance_method
1055
- [processing post]: https://twin.github.io/better-file-uploads-with-shrine-processing/
900
+ [Roda]: https://github.com/jeremyevans/roda
901
+ [Refile]: https://github.com/refile/refile
902
+ [CoC]: https://github.com/shrinerb/shrine/blob/master/CODE_OF_CONDUCT.md
903
+ [MIT License]: http://opensource.org/licenses/MIT