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 +5 -5
- data/CHANGELOG.md +25 -1
- data/README.md +241 -393
- data/doc/advantages.md +346 -0
- data/doc/attacher.md +1 -1
- data/doc/carrierwave.md +9 -9
- data/doc/creating_storages.md +172 -84
- data/doc/design.md +1 -1
- data/doc/direct_s3.md +98 -85
- data/doc/metadata.md +213 -0
- data/doc/migrating_storage.md +1 -1
- data/doc/multiple_files.md +4 -3
- data/doc/paperclip.md +4 -4
- data/doc/processing.md +415 -0
- data/doc/refile.md +23 -23
- data/doc/testing.md +47 -51
- data/doc/validation.md +148 -0
- data/lib/shrine.rb +45 -4
- data/lib/shrine/plugins/add_metadata.rb +35 -14
- data/lib/shrine/plugins/determine_mime_type.rb +7 -5
- data/lib/shrine/plugins/direct_upload.rb +3 -1
- data/lib/shrine/plugins/infer_extension.rb +1 -1
- data/lib/shrine/plugins/metadata_attributes.rb +2 -2
- data/lib/shrine/plugins/presign_endpoint.rb +27 -17
- data/lib/shrine/plugins/rack_response.rb +4 -4
- data/lib/shrine/plugins/signature.rb +1 -1
- data/lib/shrine/plugins/store_dimensions.rb +10 -18
- data/lib/shrine/plugins/upload_endpoint.rb +22 -0
- data/lib/shrine/plugins/versions.rb +10 -14
- data/lib/shrine/storage/linter.rb +11 -0
- data/lib/shrine/storage/s3.rb +57 -30
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +3 -3
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e062b548ed3da3330604b991144e1fa8bb44c547be9d68f04994056e123c3d7f
|
4
|
+
data.tar.gz: e4c52f36474a8a66ed3270d70b91ac4302ef0a1c60fc88381bcdfa037ef8c434
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ab3db221bd573ef0d7fcdd3a766a9e93b7b384cadb37668fc6406977875c584caae77eaae01ba8e20902a70e75d2d7218c90f54215db6b47288fe43b7a3857a
|
7
|
+
data.tar.gz: 64198d22c129df0cc41e602108fca768d141f4223b806e2272b52e15991e0895b14ae9dbb555e7f3e391840b4c0cbd0dc515d1e8a747d8572c7181af4077bda9
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,31 @@
|
|
1
|
-
## 2.
|
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
|
-
|
6
|
-
|
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](
|
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
|
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
|
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
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
94
|
-
will automatically generate
|
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
|
-
```
|
110
|
-
|
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 (
|
117
|
-
interface. Storages are configured directly and registered under a name
|
118
|
-
`Shrine.storages`, so that they can be
|
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
|
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(
|
151
|
+
store: Shrine::Storage::S3.new(**s3_options),
|
137
152
|
}
|
138
153
|
```
|
139
154
|
|
140
|
-
The above example sets up
|
141
|
-
storage, which
|
142
|
-
special only in terms that the attacher will
|
143
|
-
you can also register more
|
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
|
147
|
-
[many more Shrine storages][external storages]
|
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.
|
153
|
-
|
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
|
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
|
191
|
-
`#rewind`, `#eof?` and `#close`. This
|
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
|
213
|
+
will be represented as a Hash, but it can still be attached when `rack_file`
|
198
214
|
plugin is loaded.
|
199
215
|
|
200
|
-
|
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
|
-
|
213
|
-
#
|
214
|
-
uploader.
|
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
|
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` –
|
224
|
-
* `metadata` – file metadata that was extracted
|
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
|
241
|
-
uploaded_file.open
|
242
|
-
uploaded_file.download
|
243
|
-
uploaded_file.
|
244
|
-
uploaded_file.
|
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.
|
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
|
319
|
-
|
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
|
331
|
-
uploads]
|
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
|
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
|
-
|
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
|
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
|
431
|
-
file, which is set from the
|
432
|
-
determined by the browser solely based on the file extension.
|
433
|
-
by default Shrine's
|
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
|
-
|
437
|
-
|
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
|
-
|
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
|
449
|
-
different analyzer
|
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
|
454
|
-
|
455
|
-
|
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
|
-
|
483
|
-
|
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
|
-
|
488
|
-
to
|
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
|
-
```
|
491
|
-
|
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
|
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
|
-
|
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`,
|
650
|
-
|
651
|
-
|
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
|
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
|
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
|
-
|
705
|
-
|
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.
|
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
|
-
|
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
|
-
|
781
|
-
|
782
|
-
|
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
|
-
|
785
|
-
the
|
786
|
-
|
787
|
-
|
788
|
-
|
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
|
813
|
-
upload files
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
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
|
862
|
-
deleting of files into background jobs using Sidekiq.
|
863
|
-
Sidekiq you can use [any other backgrounding
|
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
|
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
|
901
|
-
you. This can be done either from the [AWS Console][S3 lifecycle console]
|
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
|
-
[
|
1019
|
-
[
|
1020
|
-
[
|
1021
|
-
[
|
1022
|
-
[
|
1023
|
-
[
|
1024
|
-
[
|
1025
|
-
[
|
1026
|
-
[
|
1027
|
-
[
|
1028
|
-
[
|
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
|
-
[
|
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
|
-
[
|
1033
|
-
[
|
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
|
1045
|
-
[
|
1046
|
-
[
|
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
|
-
[
|
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
|