shrine 1.4.2 → 2.0.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 +4 -4
- data/README.md +236 -234
- data/doc/changing_location.md +6 -4
- data/doc/creating_storages.md +4 -4
- data/doc/design.md +223 -0
- data/doc/migrating_storage.md +6 -11
- data/doc/regenerating_versions.md +22 -40
- data/lib/shrine.rb +60 -77
- data/lib/shrine/plugins/activerecord.rb +37 -14
- data/lib/shrine/plugins/background_helpers.rb +1 -0
- data/lib/shrine/plugins/backgrounding.rb +49 -37
- data/lib/shrine/plugins/backup.rb +6 -4
- data/lib/shrine/plugins/cached_attachment_data.rb +5 -5
- data/lib/shrine/plugins/data_uri.rb +9 -9
- data/lib/shrine/plugins/default_storage.rb +4 -4
- data/lib/shrine/plugins/default_url.rb +7 -1
- data/lib/shrine/plugins/default_url_options.rb +1 -1
- data/lib/shrine/plugins/delete_promoted.rb +2 -2
- data/lib/shrine/plugins/delete_raw.rb +4 -4
- data/lib/shrine/plugins/determine_mime_type.rb +50 -43
- data/lib/shrine/plugins/direct_upload.rb +10 -20
- data/lib/shrine/plugins/download_endpoint.rb +16 -13
- data/lib/shrine/plugins/dynamic_storage.rb +4 -12
- data/lib/shrine/plugins/included.rb +6 -19
- data/lib/shrine/plugins/keep_files.rb +4 -4
- data/lib/shrine/plugins/logging.rb +4 -4
- data/lib/shrine/plugins/migration_helpers.rb +37 -34
- data/lib/shrine/plugins/moving.rb +19 -32
- data/lib/shrine/plugins/parallelize.rb +5 -5
- data/lib/shrine/plugins/pretty_location.rb +2 -6
- data/lib/shrine/plugins/remote_url.rb +31 -43
- data/lib/shrine/plugins/remove_attachment.rb +5 -5
- data/lib/shrine/plugins/remove_invalid.rb +1 -1
- data/lib/shrine/plugins/restore_cached_data.rb +4 -10
- data/lib/shrine/plugins/sequel.rb +46 -21
- data/lib/shrine/plugins/store_dimensions.rb +19 -20
- data/lib/shrine/plugins/upload_options.rb +11 -9
- data/lib/shrine/plugins/validation_helpers.rb +3 -3
- data/lib/shrine/plugins/versions.rb +18 -3
- data/lib/shrine/storage/file_system.rb +9 -11
- data/lib/shrine/storage/linter.rb +1 -7
- data/lib/shrine/storage/s3.rb +25 -19
- data/lib/shrine/version.rb +3 -3
- data/shrine.gemspec +13 -3
- metadata +28 -9
- data/lib/shrine/plugins/delete_uploaded.rb +0 -3
- data/lib/shrine/plugins/keep_location.rb +0 -46
- data/lib/shrine/plugins/restore_cached.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25b8acff9f68f0cc11cbadc0eeb39a5bdcf839d1
|
4
|
+
data.tar.gz: 666390819abcf036b20f46ecf04b1a30fef68a28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be25b88ca4629b8a1e052ed442b5530d1661a0f23a4d3d7cb0cb2e6f6538379c1a46a22e921091a3a718755e1f4378bd97a25312bde436134cdd3ac0ba487194
|
7
|
+
data.tar.gz: cf90025e05a3f0790bde9ba0763a66e75c6aba48a1b5586a4a814947693154d1a345ae90c447341f349cf9e3abf29a62e2fe6ab211993fc1685ad38cef479b33
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Shrine
|
2
2
|
|
3
|
-
Shrine is a toolkit for file uploads in Ruby applications.
|
3
|
+
Shrine is a toolkit for handling file uploads in Ruby applications.
|
4
4
|
|
5
5
|
If you're new, you're encouraged to read the [introductory blog post] which
|
6
6
|
explains the motivation behind Shrine.
|
@@ -10,7 +10,7 @@ explains the motivation behind Shrine.
|
|
10
10
|
- Documentation: [shrinerb.com](http://shrinerb.com)
|
11
11
|
- Source: [github.com/janko-m/shrine](https://github.com/janko-m/shrine)
|
12
12
|
- Bugs: [github.com/janko-m/shrine/issues](https://github.com/janko-m/shrine/issues)
|
13
|
-
- Help &
|
13
|
+
- Help & Discussion: [groups.google.com/group/ruby-shrine](https://groups.google.com/forum/#!forum/ruby-shrine)
|
14
14
|
|
15
15
|
## Installation
|
16
16
|
|
@@ -18,11 +18,11 @@ explains the motivation behind Shrine.
|
|
18
18
|
gem "shrine"
|
19
19
|
```
|
20
20
|
|
21
|
-
Shrine has been tested on MRI 2.1, MRI 2.2, MRI 2.3
|
21
|
+
Shrine has been tested on MRI 2.1, MRI 2.2, MRI 2.3 and JRuby.
|
22
22
|
|
23
23
|
## Basics
|
24
24
|
|
25
|
-
Here's
|
25
|
+
Here's an example showing how basic file upload works in Shrine:
|
26
26
|
|
27
27
|
```rb
|
28
28
|
require "shrine"
|
@@ -34,7 +34,6 @@ uploader = Shrine.new(:file_system)
|
|
34
34
|
|
35
35
|
uploaded_file = uploader.upload(File.open("movie.mp4"))
|
36
36
|
uploaded_file #=> #<Shrine::UploadedFile>
|
37
|
-
uploaded_file.url #=> "/uploads/9260ea09d8effd.mp4"
|
38
37
|
uploaded_file.data #=>
|
39
38
|
# {
|
40
39
|
# "storage" => "file_system",
|
@@ -43,27 +42,25 @@ uploaded_file.data #=>
|
|
43
42
|
# }
|
44
43
|
```
|
45
44
|
|
46
|
-
|
47
|
-
simple Ruby classes which perform the actual uploads. We instantiate a `Shrine`
|
48
|
-
with the storage name, and when we call `#upload` Shrine does the following:
|
45
|
+
Let's see what's going on here:
|
49
46
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
* returns a `Shrine::UploadedFile` with relevant data
|
47
|
+
First we registered the storage we want to use under a name. Storages are plain
|
48
|
+
Ruby classes which encapsulate file management on a particular service. We can
|
49
|
+
then instantiate `Shrine` as a wrapper around that storage. A call to `upload`
|
50
|
+
uploads the given file to the underlying storage.
|
55
51
|
|
56
|
-
The argument to `
|
57
|
-
|
58
|
-
|
59
|
-
`#
|
60
|
-
|
52
|
+
The argument to `upload` needs to be an IO-like object. So, `File`, `Tempfile`
|
53
|
+
and `StringIO` are all valid arguments. The object doesn't have to be an actual
|
54
|
+
IO, though, it's enough that it responds to these 5 methods: `#read(*args)`,
|
55
|
+
`#size`, `#eof?`, `#rewind` and `#close`. `ActionDispatch::Http::UploadedFile`
|
56
|
+
is one such object, as well as `Shrine::UploadedFile` itself.
|
61
57
|
|
62
|
-
The
|
63
|
-
|
58
|
+
The result of uploading is a `Shrine::UploadedFile` object, which represents
|
59
|
+
the uploaded file on the storage. It is defined solely by its data hash. We can
|
60
|
+
do a lot with it:
|
64
61
|
|
65
62
|
```rb
|
66
|
-
uploaded_file.url #=> "
|
63
|
+
uploaded_file.url #=> "uploads/938kjsdf932.mp4"
|
67
64
|
uploaded_file.metadata #=> {...}
|
68
65
|
uploaded_file.read #=> "..."
|
69
66
|
uploaded_file.exists? #=> true
|
@@ -72,16 +69,15 @@ uploaded_file.delete
|
|
72
69
|
# ...
|
73
70
|
```
|
74
71
|
|
75
|
-
To read about the metadata that is stored with the uploaded file, see the
|
76
|
-
[metadata](#metadata) section.
|
77
|
-
|
78
72
|
## Attachment
|
79
73
|
|
80
|
-
In web applications
|
81
|
-
treat them as "attachments" to records
|
82
|
-
|
74
|
+
In web applications we usually want work with files on a higher level. We want
|
75
|
+
to treat them as "attachments" to records, by persisting their information to a
|
76
|
+
database column and tying their lifecycle to the record. For this Shrine offers
|
77
|
+
a higher-level attachment interface.
|
83
78
|
|
84
|
-
|
79
|
+
First we need to register temporary and permanent storage which will be used
|
80
|
+
internally:
|
85
81
|
|
86
82
|
```rb
|
87
83
|
require "shrine"
|
@@ -93,88 +89,90 @@ Shrine.storages = {
|
|
93
89
|
}
|
94
90
|
```
|
95
91
|
|
96
|
-
|
97
|
-
|
98
|
-
|
92
|
+
The `:cache` and `:store` are only special in terms that they will be used
|
93
|
+
automatically (but that can be changed with the default_storage plugin). Next,
|
94
|
+
we create an uploader class specific to the type of attachment we want, so that
|
95
|
+
later we can have different uploading logic for different attachment types.
|
99
96
|
|
100
97
|
```rb
|
101
98
|
class ImageUploader < Shrine
|
102
|
-
# your logic for uploading
|
99
|
+
# your logic for uploading images
|
103
100
|
end
|
104
101
|
```
|
105
102
|
|
106
|
-
|
107
|
-
|
103
|
+
Finally, to add an attachment to a model, we generate a named "attachment"
|
104
|
+
module using the uploader and include it:
|
108
105
|
|
109
106
|
```rb
|
110
|
-
class
|
111
|
-
include ImageUploader[:
|
107
|
+
class Photo
|
108
|
+
include ImageUploader[:image] # requires "image_data" attribute
|
112
109
|
end
|
113
110
|
```
|
114
111
|
|
115
|
-
Now our model has gained special methods for attaching
|
112
|
+
Now our model has gained special methods for attaching files:
|
116
113
|
|
117
114
|
```rb
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
115
|
+
photo = Photo.new
|
116
|
+
photo.image = File.open("nature.jpg") # uploads the file to cache
|
117
|
+
photo.image #=> #<Shrine::UploadedFile>
|
118
|
+
photo.image_url #=> "/uploads/cache/9260ea09d8effd.jpg"
|
119
|
+
photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
|
123
120
|
```
|
124
121
|
|
125
|
-
The attachment module has added `#
|
126
|
-
methods to our
|
122
|
+
The attachment module has added `#image`, `#image=` and `#image_url`
|
123
|
+
methods to our `Photo`, using regular module inclusion.
|
127
124
|
|
128
125
|
```rb
|
129
|
-
Shrine[:
|
130
|
-
Shrine[:
|
131
|
-
Shrine[:
|
126
|
+
Shrine[:image] #=> #<Shrine::Attachment(image)>
|
127
|
+
Shrine[:image].is_a?(Module) #=> true
|
128
|
+
Shrine[:image].instance_methods #=> [:image=, :image, :image_url, :image_attacher]
|
132
129
|
|
133
130
|
Shrine[:document] #=> #<Shrine::Attachment(document)>
|
134
131
|
Shrine[:document].instance_methods #=> [:document=, :document, :document_url, :document_attacher]
|
135
132
|
|
136
|
-
#
|
137
|
-
Shrine.attachment(:
|
133
|
+
# Expanded forms
|
134
|
+
Shrine.attachment(:image)
|
138
135
|
Shrine::Attachment.new(:document)
|
139
136
|
```
|
140
137
|
|
141
|
-
* `#
|
142
|
-
* `#
|
143
|
-
* `#
|
138
|
+
* `#image=` – caches the file and saves JSON data into `image_data`
|
139
|
+
* `#image` – returns a `Shrine::UploadedFile` based on data from `image_data`
|
140
|
+
* `#image_url` – calls `image.url` if attachment is present, otherwise returns nil.
|
144
141
|
|
145
|
-
This is how you would typically create the form for a `@
|
142
|
+
This is how you would typically create the form for a `@photo`:
|
146
143
|
|
147
144
|
```erb
|
148
|
-
<form action="/
|
149
|
-
<input name="
|
150
|
-
<input name="
|
145
|
+
<form action="/photos" method="post" enctype="multipart/form-data">
|
146
|
+
<input name="photo[image]" type="hidden" value="<%= @photo.image_data %>">
|
147
|
+
<input name="photo[image]" type="file">
|
151
148
|
</form>
|
152
149
|
```
|
153
150
|
|
154
151
|
The "file" field is for file upload, while the "hidden" field is to make the
|
155
152
|
file persist in case of validation errors, and for direct uploads. This code
|
156
|
-
works because `#
|
153
|
+
works because `#image=` also accepts an already cached file via its JSON
|
157
154
|
representation:
|
158
155
|
|
159
156
|
```rb
|
160
|
-
|
157
|
+
photo.image = '{"id":"9jsdf02kd", "storage":"cache", "metadata": {...}}'
|
161
158
|
```
|
162
159
|
|
163
160
|
### ORM
|
164
161
|
|
165
|
-
|
166
|
-
|
167
|
-
|
162
|
+
Even though you can use Shrine's attachment interface with plain Ruby objects,
|
163
|
+
it's much more common to use it with an ORM. Shrine ships with plugins for
|
164
|
+
Sequel and ActiveRecord ORMs. It uses the `<attachment>_data` column for
|
165
|
+
storing data for uploaded files, so you'll need to add it in a migration.
|
168
166
|
|
169
167
|
```rb
|
170
|
-
add_column :
|
168
|
+
add_column :movies, :video_data, :text # or a JSON column
|
171
169
|
```
|
172
170
|
```rb
|
173
171
|
Shrine.plugin :sequel # or :activerecord
|
174
172
|
```
|
175
173
|
```rb
|
176
|
-
class
|
177
|
-
include
|
174
|
+
class Movie < Sequel::Model
|
175
|
+
include VideoUploader[:video]
|
178
176
|
end
|
179
177
|
```
|
180
178
|
|
@@ -182,77 +180,61 @@ In addition to getters and setters, the ORM plugins add the appropriate
|
|
182
180
|
callbacks:
|
183
181
|
|
184
182
|
```rb
|
185
|
-
|
186
|
-
|
187
|
-
user.save
|
188
|
-
user.avatar.storage_key #=> "store"
|
189
|
-
user.destroy
|
190
|
-
user.avatar.exists? #=> false
|
191
|
-
```
|
192
|
-
|
193
|
-
*NOTE: The record will first be saved with the cached attachment, and afterwards
|
194
|
-
(in an "after commit" hook) updated with the stored attachment. This is done so
|
195
|
-
that processing/storing isn't performed inside a database transaction. If you're
|
196
|
-
doing processing, there will be a bried period of time when the record will exist
|
197
|
-
with an unprocessed attachment, so you may need to account for that.*
|
198
|
-
|
199
|
-
## Direct uploads
|
183
|
+
movie.video = File.open("video.mp4")
|
184
|
+
movie.video_url #=> "/uploads/cache/0sdfllasfi842.mp4"
|
200
185
|
|
201
|
-
|
202
|
-
|
203
|
-
file the moment the user selects it (e.g. using the [jQuery-File-Upload] JS
|
204
|
-
library), which gives a nice experience to the user.
|
186
|
+
movie.save
|
187
|
+
movie.video_url #=> "/uploads/store/l02kladf8jlda.mp4"
|
205
188
|
|
206
|
-
|
207
|
-
|
208
|
-
```
|
209
|
-
```rb
|
210
|
-
Rails.application.routes.draw do
|
211
|
-
mount ImageUploader::UploadEndpoint => "/attachments/images"
|
212
|
-
end
|
213
|
-
```
|
214
|
-
```js
|
215
|
-
$('[type="file"]').fileupload({
|
216
|
-
url: '/attachments/images/cache/upload',
|
217
|
-
paramName: 'file',
|
218
|
-
add: function(e, data) { /* Disable the submit button */ },
|
219
|
-
progress: function(e, data) { /* Add a nice progress bar */ },
|
220
|
-
done: function(e, data) { /* Fill in the hidden field with the result */ }
|
221
|
-
});
|
189
|
+
movie.destroy
|
190
|
+
movie.video.exists? #=> false
|
222
191
|
```
|
223
192
|
|
224
|
-
|
225
|
-
|
226
|
-
|
193
|
+
First the raw file is cached to temporary storage on assignment, then on saving
|
194
|
+
the cached file is uploaded to permanent storage. Destroying the record
|
195
|
+
destroys the attachment.
|
196
|
+
|
197
|
+
*NOTE: The record will first be saved with the cached attachment, and
|
198
|
+
afterwards (in an "after commit" hook) updated with the stored attachment. This
|
199
|
+
is done so that processing/storing isn't performed inside a database
|
200
|
+
transaction. If you're doing processing, there will be a period of time when
|
201
|
+
the record will be saved with an unprocessed attachment, so you may need to
|
202
|
+
account for that.*
|
227
203
|
|
228
204
|
## Processing
|
229
205
|
|
230
206
|
Whenever a file is uploaded, `Shrine#process` is called, and this is where
|
231
207
|
you're expected to define your processing.
|
232
208
|
|
209
|
+
```rb
|
210
|
+
class ImageUploader < Shrine
|
211
|
+
def process(io, context)
|
212
|
+
# ...
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
Shrine's uploaders are stateless; the `#process` method is simply a function
|
218
|
+
which takes an input `io` and returns processed file(s) as output. Since it's
|
219
|
+
called for each upload, attaching the file will call it twice, first when
|
220
|
+
raw file is cached to temporary storage on assignment, then when cached file
|
221
|
+
is uploaded to permanent storage on saving. We usually want to process in the
|
222
|
+
latter phase (after file validations):
|
223
|
+
|
233
224
|
```rb
|
234
225
|
class ImageUploader < Shrine
|
235
226
|
def process(io, context)
|
236
227
|
if context[:phase] == :store
|
237
|
-
#
|
228
|
+
# ...
|
238
229
|
end
|
239
230
|
end
|
240
231
|
end
|
241
232
|
```
|
242
233
|
|
243
|
-
The `io` is the file being uploaded, and `context` we'll leave for later. You
|
244
|
-
may be wondering why we need this conditional. Well, when an attachment is
|
245
|
-
assigned and saved, an "upload" actually happens two times. First the file is
|
246
|
-
"uploaded" to cache on assignment, and then the cached file is reuploaded to
|
247
|
-
store on save. You could theoretically do processing in both phases, depending
|
248
|
-
on your preferences (although it's generally not recommended to process on
|
249
|
-
caching, because it happens before file validations; use the `recache` plugin
|
250
|
-
instead).
|
251
|
-
|
252
234
|
Ok, now how do we do the actual processing? Well, Shrine actually doesn't ship
|
253
235
|
with any file processing functionality, because that is a generic problem that
|
254
|
-
belongs in
|
255
|
-
created the [image_processing] gem which you can use with Shrine:
|
236
|
+
belongs in separate libraries. If the type of files you're uploading are
|
237
|
+
images, I created the [image_processing] gem which you can use with Shrine:
|
256
238
|
|
257
239
|
```rb
|
258
240
|
require "image_processing/mini_magick"
|
@@ -268,20 +250,14 @@ class ImageUploader < Shrine
|
|
268
250
|
end
|
269
251
|
```
|
270
252
|
|
271
|
-
|
272
|
-
|
273
|
-
store. The cached file is an instance of `Shrine::UploadedFile`, but for
|
274
|
-
processing we need to work with actual files, so we first need to download it.
|
275
|
-
|
276
|
-
In general, processing works in a way that if `#process` returns a file, Shrine
|
277
|
-
continues storing that file, otherwise if nil is returned, Shrine continues
|
278
|
-
storing the original file.
|
253
|
+
Since here `io` is a cached `Shrine::UploadedFile`, we need to download it to
|
254
|
+
a file, as image_processing only accepts real files.
|
279
255
|
|
280
256
|
### Versions
|
281
257
|
|
282
258
|
If you're uploading images, often you'll want to store various thumbnails
|
283
|
-
alongside your original image.
|
284
|
-
|
259
|
+
alongside your original image. You can do that by loading the versions plugin,
|
260
|
+
and in `#process` simply returning a Hash of versions:
|
285
261
|
|
286
262
|
```rb
|
287
263
|
require "image_processing/mini_magick"
|
@@ -302,25 +278,24 @@ class ImageUploader < Shrine
|
|
302
278
|
end
|
303
279
|
```
|
304
280
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
after upload.
|
281
|
+
Being able to define processing on instance level provides a lot of flexibility,
|
282
|
+
allowing things like choosing the order or adding parallelization. It is
|
283
|
+
recommended to use the delete_raw plugin for automatically deleting processed
|
284
|
+
files after uploading.
|
310
285
|
|
311
|
-
|
312
|
-
|
286
|
+
The attachment getter will simply return the processed attachment as a Hash of
|
287
|
+
versions:
|
313
288
|
|
314
289
|
```rb
|
315
|
-
|
290
|
+
photo.image.class #=> Hash
|
316
291
|
|
317
292
|
# With the store_dimensions plugin
|
318
|
-
|
319
|
-
|
320
|
-
|
293
|
+
photo.image[:large].width #=> 700
|
294
|
+
photo.image[:medium].width #=> 500
|
295
|
+
photo.image[:small].width #=> 300
|
321
296
|
|
322
297
|
# The plugin expands this method to accept version names.
|
323
|
-
|
298
|
+
photo.image_url(:large) #=> "..."
|
324
299
|
```
|
325
300
|
|
326
301
|
## Context
|
@@ -337,28 +312,28 @@ class ImageUploader < Shrine
|
|
337
312
|
end
|
338
313
|
```
|
339
314
|
```rb
|
340
|
-
|
341
|
-
|
342
|
-
|
315
|
+
photo = Photo.new
|
316
|
+
photo.image = File.open("image.jpg") # "cache"
|
317
|
+
photo.save # "store"
|
343
318
|
```
|
344
319
|
```
|
345
|
-
{:name=>:
|
346
|
-
{:name=>:
|
320
|
+
{:name=>:image, :record=>#<Photo:0x007fe1627f1138>, :phase=>:cache}
|
321
|
+
{:name=>:image, :record=>#<Photo:0x007fe1627f1138>, :phase=>:store}
|
347
322
|
```
|
348
323
|
|
349
|
-
The `:name` is the name of the attachment, in this case "
|
350
|
-
is the model instance, in this case instance of `
|
351
|
-
|
352
|
-
|
324
|
+
The `:name` is the name of the attachment, in this case "image". The `:record`
|
325
|
+
is the model instance, in this case instance of `Photo`. Lastly, the `:phase`
|
326
|
+
is a symbol which indicates the purpose of the upload (by default there are
|
327
|
+
only `:cache` and `:store`, but some plugins add more of them).
|
353
328
|
|
354
|
-
Context is
|
355
|
-
|
356
|
-
|
329
|
+
Context is useful for doing conditional processing and validation, since we
|
330
|
+
have access to the record and attachment name, and it is also used by some
|
331
|
+
plugins internally.
|
357
332
|
|
358
333
|
## Validations
|
359
334
|
|
360
|
-
Validations are registered by calling `
|
361
|
-
|
335
|
+
Validations are registered by calling `Attacher.validate`, and are best done
|
336
|
+
with the validation_helpers plugin:
|
362
337
|
|
363
338
|
```rb
|
364
339
|
class DocumentUploader < Shrine
|
@@ -366,7 +341,7 @@ class DocumentUploader < Shrine
|
|
366
341
|
|
367
342
|
Attacher.validate do
|
368
343
|
# Evaluated inside an instance of Shrine::Attacher.
|
369
|
-
if record.
|
344
|
+
if record.resume?
|
370
345
|
validate_max_size 10*1024*1024, message: "is too large (max is 10 MB)"
|
371
346
|
validate_mime_type_inclusion ["application/pdf"]
|
372
347
|
end
|
@@ -375,72 +350,53 @@ end
|
|
375
350
|
```
|
376
351
|
|
377
352
|
```rb
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
353
|
+
document = Document.new(resume: true)
|
354
|
+
document.file = File.open("resume.pdf")
|
355
|
+
document.valid? #=> false
|
356
|
+
document.errors.to_hash #=> {file: ["is too large (max is 2 MB)"]}
|
382
357
|
```
|
383
358
|
|
384
359
|
## Metadata
|
385
360
|
|
386
|
-
|
361
|
+
Shrine automatically extracts and stores general file metadata:
|
387
362
|
|
388
363
|
```rb
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
# "size" => 345993,
|
397
|
-
# }
|
364
|
+
photo = Photo.create(image: image)
|
365
|
+
photo.image.metadata #=>
|
366
|
+
# {
|
367
|
+
# "filename" => "nature.jpg",
|
368
|
+
# "mime_type" => "image/jpeg",
|
369
|
+
# "size" => 345993,
|
370
|
+
# }
|
398
371
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
end
|
372
|
+
photo.image.original_filename #=> "nature.jpg"
|
373
|
+
photo.image.extension #=> "jpg"
|
374
|
+
photo.image.mime_type #=> "image/jpeg"
|
375
|
+
photo.image.size #=> 345993
|
404
376
|
```
|
405
377
|
|
406
378
|
### MIME type
|
407
379
|
|
408
380
|
By default, "mime_type" is inherited from `#content_type` of the uploaded file,
|
409
|
-
which
|
410
|
-
based on the extension
|
411
|
-
|
412
|
-
file.
|
381
|
+
which is set from the "Content-Type" request header, which is determined by the
|
382
|
+
browser solely based on the file extension. This means that by default Shrine's
|
383
|
+
"mime_type" is *not* guaranteed to hold the actual MIME type of the file.
|
413
384
|
|
414
|
-
To help with that Shrine provides the
|
385
|
+
To help with that Shrine provides the determine_mime_type plugin, which by
|
415
386
|
default uses the UNIX [file] utility to determine the actual MIME type:
|
416
387
|
|
417
388
|
```rb
|
418
389
|
Shrine.plugin :determine_mime_type
|
419
390
|
```
|
420
391
|
```rb
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
### Dimensions
|
426
|
-
|
427
|
-
If you're uploading images and you want to store dimensions, you can use the
|
428
|
-
`store_dimensions` plugin which extracts dimensions using the [fastimage] gem.
|
429
|
-
|
430
|
-
```rb
|
431
|
-
ImageUploader.plugin :store_dimensions
|
432
|
-
```
|
433
|
-
```rb
|
434
|
-
user = User.create(avatar: File.open("image.jpg"))
|
435
|
-
user.avatar.width #=> 400
|
436
|
-
user.avatar.height #=> 500
|
392
|
+
File.write("image.jpg", "<?php ... ?>") # PHP file with a .jpg extension
|
393
|
+
photo = Photo.create(image: File.open("image.jpg"))
|
394
|
+
photo.image.mime_type #=> "text/x-php"
|
437
395
|
```
|
438
396
|
|
439
|
-
The fastimage gem has built-in protection against [image bombs].
|
440
|
-
|
441
397
|
### Custom metadata
|
442
398
|
|
443
|
-
You can also extract and store custom metadata
|
399
|
+
You can also extract and store custom metadata by overriding
|
444
400
|
`Shrine#extract_metadata`:
|
445
401
|
|
446
402
|
```rb
|
@@ -453,49 +409,55 @@ class ImageUploader < Shrine
|
|
453
409
|
end
|
454
410
|
```
|
455
411
|
|
456
|
-
Note that
|
457
|
-
rewind the file afterwards.
|
412
|
+
Note that you should always rewind the `io` after reading from it.
|
458
413
|
|
459
414
|
## Locations
|
460
415
|
|
461
|
-
|
462
|
-
|
416
|
+
Before Shrine uploads a file, it generates a random location for it. By
|
417
|
+
default the hierarchy is flat, all files are stored in the root of the storage.
|
418
|
+
If you want that each attachment has its own directory, you can load the
|
419
|
+
pretty_location plugin:
|
463
420
|
|
464
421
|
```rb
|
465
422
|
Shrine.plugin :pretty_location
|
466
423
|
```
|
467
424
|
```rb
|
468
|
-
|
469
|
-
|
425
|
+
photo = Photo.create(image: File.open("nature.jpg"))
|
426
|
+
photo.image.id #=> "photo/34/image/34krtreds2df.jpg"
|
470
427
|
```
|
471
428
|
|
472
|
-
If you want to generate your own
|
429
|
+
If you want to generate locations on your own, simply override
|
473
430
|
`Shrine#generate_location`:
|
474
431
|
|
475
432
|
```rb
|
476
433
|
class ImageUploader < Shrine
|
477
434
|
def generate_location(io, context)
|
478
|
-
|
435
|
+
if context[:record]
|
436
|
+
"#{context[:record].class}/#{super}"
|
437
|
+
else
|
438
|
+
super
|
439
|
+
end
|
479
440
|
end
|
480
441
|
end
|
481
442
|
```
|
482
443
|
|
483
|
-
Note that there should always be a random component in the location,
|
484
|
-
|
485
|
-
|
444
|
+
Note that there should always be a random component in the location, for dirty
|
445
|
+
tracking to be detected properly (you can use `Shrine#generate_uid`). Inside
|
446
|
+
`#generate_location` you can access the extracted metadata through
|
486
447
|
`context[:metadata]`.
|
487
448
|
|
488
449
|
When using the uploader directly, it's possible to bypass `#generate_location`
|
489
|
-
by passing
|
450
|
+
by passing a `:location`:
|
490
451
|
|
491
452
|
```rb
|
492
|
-
|
493
|
-
|
453
|
+
uploader = Shrine.new(:store)
|
454
|
+
file = File.open("nature.jpg")
|
455
|
+
uploader.upload(file, location: "some/specific/location.jpg")
|
494
456
|
```
|
495
457
|
|
496
458
|
## Storage
|
497
459
|
|
498
|
-
Other than [FileSystem], Shrine also ships with [S3] storage:
|
460
|
+
Other than [FileSystem], Shrine also ships with Amazon [S3] storage:
|
499
461
|
|
500
462
|
```rb
|
501
463
|
gem "aws-sdk", "~> 2.1"
|
@@ -507,42 +469,71 @@ Shrine.storages[:store] = Shrine::Storage::S3.new(
|
|
507
469
|
access_key_id: "<ACCESS_KEY_ID>", # "xyz"
|
508
470
|
secret_access_key: "<SECRET_ACCESS_KEY>", # "abc"
|
509
471
|
region: "<REGION>", # "eu-west-1"
|
510
|
-
bucket: "<BUCKET>", # "my-
|
472
|
+
bucket: "<BUCKET>", # "my-bucket"
|
511
473
|
)
|
512
474
|
```
|
513
475
|
|
514
476
|
```rb
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
477
|
+
movie = Movie.new(video: File.open("video.mp4"))
|
478
|
+
movie.video_url #=> "/uploads/cache/j4k343ui12ls9.jpg"
|
479
|
+
movie.save
|
480
|
+
movie.video_url #=> "https://my-bucket.s3-eu-west-1.amazonaws.com/0943sf8gfk13.mp4"
|
519
481
|
```
|
520
482
|
|
521
|
-
If you're using S3 both for cache and store,
|
522
|
-
|
523
|
-
|
524
|
-
versions are deleted with a single HTTP request.
|
483
|
+
If you're using S3 both for cache and store, uploading a cached file to store
|
484
|
+
will simply do an S3 COPY request instead of downloading and reuploading the
|
485
|
+
file. Also, the versions plugin takes advantage of S3's MULTI DELETE
|
486
|
+
capabilities, so versions are deleted with a single HTTP request.
|
525
487
|
|
526
488
|
See the full documentation for [FileSystem] and [S3] storages. There are also
|
527
489
|
many other Shrine storages available, see the [Plugins & Storages] section.
|
528
490
|
|
529
|
-
|
491
|
+
## Upload options
|
530
492
|
|
531
|
-
|
532
|
-
|
533
|
-
and for FileSystem you can put something like this in your Rake task:
|
493
|
+
Many storages accept additional upload options, which you can pass via the
|
494
|
+
upload_options plugin, or manually when uploading:
|
534
495
|
|
535
496
|
```rb
|
536
|
-
|
537
|
-
|
497
|
+
uploader = Shrine.new(:store)
|
498
|
+
uploader.upload(file, upload_options: {acl: "private"})
|
499
|
+
```
|
500
|
+
|
501
|
+
## Direct uploads
|
502
|
+
|
503
|
+
Shrine comes with a direct_upload plugin which provides a [Roda] endpoint that
|
504
|
+
accepts file uploads. This allows you to asynchronously start caching the file
|
505
|
+
the moment the user selects it via AJAX (e.g. using the [jQuery-File-Upload] JS
|
506
|
+
library).
|
507
|
+
|
508
|
+
```rb
|
509
|
+
Shrine.plugin :direct_upload # Provides a Roda endpoint
|
510
|
+
```
|
511
|
+
```rb
|
512
|
+
Rails.application.routes.draw do
|
513
|
+
mount VideoUploader::UploadEndpoint => "/attachments/videos"
|
514
|
+
end
|
515
|
+
```
|
516
|
+
```js
|
517
|
+
$('[type="file"]').fileupload({
|
518
|
+
url: '/attachments/videos/cache/upload',
|
519
|
+
paramName: 'file',
|
520
|
+
add: function(e, data) { /* Disable the submit button */ },
|
521
|
+
progress: function(e, data) { /* Add a nice progress bar */ },
|
522
|
+
done: function(e, data) { /* Fill in the hidden field with the result */ }
|
523
|
+
});
|
538
524
|
```
|
539
525
|
|
526
|
+
The plugin also provides a route that can be used for doing direct S3 uploads.
|
527
|
+
See the documentation of the plugin for more details, as well as the
|
528
|
+
[Roda](https://github.com/janko-m/shrine-example)/[Rails](https://github.com/erikdahlstrand/shrine-rails-example)
|
529
|
+
example app which demonstrates multiple uploads directly to S3.
|
530
|
+
|
540
531
|
## Background jobs
|
541
532
|
|
542
|
-
Shrine is the first
|
543
|
-
|
544
|
-
and good user experience, and Shrine provides a
|
545
|
-
makes it really easy to plug in your backgrounding library:
|
533
|
+
Shrine is the first file upload library designed for backgrounding support.
|
534
|
+
Moving phases of managing attachments to background jobs is essential for
|
535
|
+
scaling and good user experience, and Shrine provides a backgrounding plugin
|
536
|
+
which makes it really easy to plug in your favourite backgrounding library:
|
546
537
|
|
547
538
|
```rb
|
548
539
|
Shrine.plugin :backgrounding
|
@@ -567,8 +558,8 @@ end
|
|
567
558
|
```
|
568
559
|
|
569
560
|
The above puts all promoting (moving to store) and deleting of files into a
|
570
|
-
background Sidekiq job. Obviously instead of Sidekiq you can
|
571
|
-
|
561
|
+
background Sidekiq job. Obviously instead of Sidekiq you can use any other
|
562
|
+
backgrounding library.
|
572
563
|
|
573
564
|
The main advantages of Shrine's backgrounding support over other file upload
|
574
565
|
libraries are:
|
@@ -583,14 +574,25 @@ libraries are:
|
|
583
574
|
* **Generality** – The above solution will automatically work for all uploaders,
|
584
575
|
types of files and models.
|
585
576
|
* **Safety** – All of Shrine's code has been designed to take delayed storing
|
586
|
-
into account,
|
577
|
+
into account, and concurrent requests are handled well.
|
578
|
+
|
579
|
+
## Clearing cache
|
580
|
+
|
581
|
+
You will want to periodically clean your temporary storage. Amazon S3 provides
|
582
|
+
[a built-in solution](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html),
|
583
|
+
and for FileSystem you can put something like this in your Rake task:
|
584
|
+
|
585
|
+
```rb
|
586
|
+
file_system = Shrine.storages[:cache]
|
587
|
+
file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
|
588
|
+
```
|
587
589
|
|
588
590
|
## Plugins
|
589
591
|
|
590
592
|
Shrine comes with a small core which provides only the essential functionality,
|
591
|
-
and
|
592
|
-
exactly how much Shrine does for you. Shrine itself [ships with over
|
593
|
-
plugins], most of which I
|
593
|
+
and any additional features are available via plugins. This way you can choose
|
594
|
+
exactly what and how much Shrine does for you. Shrine itself [ships with over
|
595
|
+
35 plugins], most of which I didn't cover here.
|
594
596
|
|
595
597
|
The plugin system respects inheritance, so you can choose which plugins will
|
596
598
|
be applied to which uploaders:
|