shrine 2.4.1 → 2.5.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 +510 -416
- data/doc/carrierwave.md +4 -2
- data/doc/direct_s3.md +21 -2
- data/doc/multiple_files.md +118 -0
- data/doc/paperclip.md +4 -2
- data/doc/testing.md +18 -5
- data/lib/shrine.rb +3 -1
- data/lib/shrine/plugins/add_metadata.rb +50 -12
- data/lib/shrine/plugins/backgrounding.rb +103 -51
- data/lib/shrine/plugins/default_url.rb +32 -10
- data/lib/shrine/plugins/direct_upload.rb +3 -1
- data/lib/shrine/plugins/processing.rb +35 -16
- data/lib/shrine/plugins/rack_file.rb +54 -21
- data/lib/shrine/plugins/remove_invalid.rb +1 -0
- data/lib/shrine/plugins/store_dimensions.rb +14 -5
- data/lib/shrine/plugins/upload_options.rb +5 -0
- data/lib/shrine/plugins/validation_helpers.rb +32 -37
- data/lib/shrine/plugins/versions.rb +69 -47
- data/lib/shrine/storage/file_system.rb +3 -3
- data/lib/shrine/storage/linter.rb +1 -1
- data/lib/shrine/storage/s3.rb +8 -4
- data/lib/shrine/version.rb +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bfafd65b7f7fe8b99b1c5f93ef378bfc3dc7eac
|
4
|
+
data.tar.gz: 698ed63662d4394c346f5ae2946e892de53d694e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fcf2377c360c399342238366a1458f48f078d7b946b52af09b3e9acdbc8761e7b07270700f6a9b09b4195a43536947d6fc145da10c75667f88bd95dd9284edf
|
7
|
+
data.tar.gz: f293b1ba574687eae1edc8abad8fbdfbb2db2a4028470964f612ccf51a9e74a8dd810caf2e8dc6c784b9136fc6503ac168175d955404b923def1fd89eafc1a61
|
data/README.md
CHANGED
@@ -18,6 +18,7 @@ Add Shrine to the Gemfile and write an initializer which sets up the storage and
|
|
18
18
|
loads the ORM plugin:
|
19
19
|
|
20
20
|
```rb
|
21
|
+
# Gemfile
|
21
22
|
gem "shrine"
|
22
23
|
```
|
23
24
|
|
@@ -32,6 +33,7 @@ Shrine.storages = {
|
|
32
33
|
|
33
34
|
Shrine.plugin :sequel # :activerecord
|
34
35
|
Shrine.plugin :cached_attachment_data # for forms
|
36
|
+
Shrine.plugin :rack_file # for non-Rails apps
|
35
37
|
```
|
36
38
|
|
37
39
|
Next decide how you will name the attachment attribute on your model, and run a
|
@@ -47,7 +49,8 @@ end # end
|
|
47
49
|
```
|
48
50
|
|
49
51
|
Now you can create an uploader class for the type of files you want to upload,
|
50
|
-
and add
|
52
|
+
and add a virtual attribute for handling attachments using this uploader to
|
53
|
+
your model:
|
51
54
|
|
52
55
|
```rb
|
53
56
|
class ImageUploader < Shrine
|
@@ -57,15 +60,13 @@ end
|
|
57
60
|
|
58
61
|
```rb
|
59
62
|
class Photo < Sequel::Model # ActiveRecord::Base
|
60
|
-
include ImageUploader[:image]
|
63
|
+
include ImageUploader[:image] # adds an `image` virtual attribute
|
61
64
|
end
|
62
65
|
```
|
63
66
|
|
64
|
-
Let's now add the form fields
|
65
|
-
field for choosing files, and a hidden field for retaining the
|
66
|
-
in case of validation errors and [direct uploads].
|
67
|
-
needs to go *after* the hidden field, so that attaching a new file can always
|
68
|
-
override whatever is in the hidden field.
|
67
|
+
Let's now add the form fields which will use this virtual attribute. We need
|
68
|
+
(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].
|
69
70
|
|
70
71
|
```erb
|
71
72
|
<form action="/photos" method="post" enctype="multipart/form-data">
|
@@ -80,13 +81,14 @@ override whatever is in the hidden field.
|
|
80
81
|
<% end %>
|
81
82
|
```
|
82
83
|
|
83
|
-
Note the
|
84
|
-
|
85
|
-
|
84
|
+
Note that the file field needs to go *after* the hidden field, so that
|
85
|
+
selecting a new file can always override the cached file in the hidden field.
|
86
|
+
Also notice the `enctype="multipart/form-data"` HTML attribute, which is
|
87
|
+
required for submitting files through the form, though the Rails form builder
|
88
|
+
will automatically generate it for you.
|
86
89
|
|
87
90
|
Now in your router/controller the attachment request parameter can be assigned
|
88
|
-
to the model like any other attribute
|
89
|
-
need to load the `rack_file` plugin which handles Rack's uploaded file hash.
|
91
|
+
to the model like any other attribute:
|
90
92
|
|
91
93
|
```rb
|
92
94
|
post "/photos" do
|
@@ -95,65 +97,217 @@ post "/photos" do
|
|
95
97
|
end
|
96
98
|
```
|
97
99
|
|
98
|
-
|
100
|
+
Once a file is uploaded and attached to the record, you can retrieve a URL to
|
101
|
+
the uploaded file and display it:
|
99
102
|
|
100
103
|
```erb
|
101
104
|
<img src="<%= @photo.image_url %>">
|
102
105
|
```
|
103
106
|
|
104
|
-
##
|
107
|
+
## Storage
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
109
|
+
A "storage" in Shrine is an object responsible for managing files on a specific
|
110
|
+
storage service (filesystem, Amazon S3 etc), which implements a generic method
|
111
|
+
interface. Storages are configured directly and registered under a name in
|
112
|
+
`Shrine.storages`, so that they can be later used by uploaders.
|
110
113
|
|
111
114
|
```rb
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
+
# Gemfile
|
116
|
+
gem "aws-sdk", "~> 2.1" # for Amazon S3 storage
|
117
|
+
```
|
118
|
+
```rb
|
119
|
+
require "shrine/storage/s3"
|
120
|
+
|
121
|
+
s3_options = {
|
122
|
+
access_key_id: "abc",
|
123
|
+
secret_access_key: "xyz",
|
124
|
+
region: "my-region",
|
125
|
+
bucket: "my-bucket",
|
126
|
+
}
|
115
127
|
|
116
|
-
|
117
|
-
|
128
|
+
Shrine.storages = {
|
129
|
+
cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
|
130
|
+
store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
|
131
|
+
}
|
118
132
|
```
|
119
133
|
|
120
|
-
The
|
134
|
+
The above example sets up Amazon S3 storage both for temporary and permanent
|
135
|
+
storage, which allows for [direct uploads]. The `:cache` and `:store` names are
|
136
|
+
special only in terms that the attacher will automatically pick them up, but
|
137
|
+
you can also register more than two storages under different names.
|
121
138
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
* `#image_attacher` – returns instance of `Shrine::Attacher` which handles attaching
|
139
|
+
Shrine ships with [FileSystem] and [S3] storage, take a look at their
|
140
|
+
documentation for more details on various features they support. There are also
|
141
|
+
[many more Shrine storages][external storages] shipping as external gems.
|
126
142
|
|
127
|
-
|
128
|
-
|
143
|
+
## Uploader
|
144
|
+
|
145
|
+
Uploaders are subclasses of `Shrine`, and are essentially wrappers around
|
146
|
+
storages. In addition to actually calling the underlying storage when they need
|
147
|
+
to, they also perform many generic tasks which aren't related to a particular
|
148
|
+
storage (like processing, extracting metadata, logging etc).
|
129
149
|
|
130
150
|
```rb
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
151
|
+
class ImageUploader < Shrine
|
152
|
+
# image attachent logic
|
153
|
+
end
|
154
|
+
```
|
155
|
+
```rb
|
156
|
+
uploader = ImageUploader.new(:store)
|
157
|
+
uploader #=> uploader for storage registered under `:store`
|
158
|
+
```
|
159
|
+
|
160
|
+
It's common to create an uploader for each type of file that you want to handle
|
161
|
+
(image, video, audio, document etc), but you can structure them any way that
|
162
|
+
you like.
|
163
|
+
|
164
|
+
### Uploading
|
165
|
+
|
166
|
+
The main method of the uploader is `#upload`, which takes an IO-like object on
|
167
|
+
the input, and returns a representation of the uploaded file on the output.
|
168
|
+
|
169
|
+
```rb
|
170
|
+
uploaded_file = uploader.upload(file)
|
171
|
+
uploaded_file #=> #<Shrine::UploadedFile>
|
172
|
+
```
|
173
|
+
|
174
|
+
Some of the tasks performed by `#upload` include:
|
175
|
+
|
176
|
+
* file processing (if defined)
|
177
|
+
* extracting metadata
|
178
|
+
* generating location
|
179
|
+
* uploading (this is where the storage is called)
|
180
|
+
* closing the file
|
181
|
+
|
182
|
+
### IO abstraction
|
183
|
+
|
184
|
+
Shrine is able to upload any IO-like object that respond to `#read`, `#size`,
|
185
|
+
`#rewind`, `#eof?` and `#close`. This foremost includes all real IO objects
|
186
|
+
like File, Tempfile and StringIO.
|
187
|
+
|
188
|
+
When a file is uploaded to a Rails app, it will be represented by an
|
189
|
+
ActionDispatch::Http::UploadedFile object in the params. This is also an
|
190
|
+
IO-like object accepted by Shrine. In other Rack applications the uploaded file
|
191
|
+
will be represented as a Hash, but it can still be attached when [`rack_file`]
|
192
|
+
plugin is loaded.
|
193
|
+
|
194
|
+
Finally, the `Shrine::UploadedFile` object, returned by uploading, is itself an
|
195
|
+
IO-like object. This makes it incredibly easy to reupload a file from one
|
196
|
+
storage to another, and this is used by the attacher to reupload a file stored
|
197
|
+
on temporary storage to permanent storage.
|
198
|
+
|
199
|
+
### Deleting
|
200
|
+
|
201
|
+
The uploader can also delete uploaded files via `#delete`. Internally this just
|
202
|
+
delegates to the uploaded file, but some plugins bring additional behaviour
|
203
|
+
(e.g. logging).
|
204
|
+
|
205
|
+
```rb
|
206
|
+
uploaded_file = uploader.upload(file)
|
207
|
+
# ...
|
208
|
+
uploader.delete(uploaded_file)
|
209
|
+
```
|
210
|
+
|
211
|
+
## Uploaded file
|
212
|
+
|
213
|
+
The `Shrine::UploadedFile` object represents the file that was uploaded to the
|
214
|
+
storage. It contains the following information:
|
215
|
+
|
216
|
+
* `storage` – identifier of the storage the file was uploaded to
|
217
|
+
* `id` – the location of the file on the storage
|
218
|
+
* `metadata` – file metadata that was extracted during upload
|
219
|
+
|
220
|
+
```rb
|
221
|
+
uploaded_file = uploader.upload(file)
|
222
|
+
|
223
|
+
uploaded_file.id #=> "949sdjg834.jpg"
|
224
|
+
uploaded_file.storage #=> #<Shrine::Storage::FileSystem>
|
225
|
+
uploaded_file.metadata #=> {...}
|
226
|
+
|
227
|
+
# It can be serialized into JSON and saved to a database column
|
228
|
+
uploaded_file.to_json #=> '{"id":"949sdjg834.jpg","storage":"store","metadata":{...}}'
|
229
|
+
```
|
230
|
+
|
231
|
+
It comes with many convenient methods that delegate to the storage:
|
232
|
+
|
233
|
+
```rb
|
234
|
+
uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
|
235
|
+
uploaded_file.download #=> #<Tempfile>
|
236
|
+
uploaded_file.exists? #=> true
|
237
|
+
uploaded_file.open { |io| io.read }
|
238
|
+
uploaded_file.delete
|
136
239
|
```
|
137
240
|
|
138
|
-
|
139
|
-
|
241
|
+
It also implements the IO-like interface that conforms to Shrine's IO
|
242
|
+
abstraction, which allows it to be uploaded to other storages.
|
243
|
+
|
244
|
+
```rb
|
245
|
+
uploaded_file.read # returns content of the uploaded file
|
246
|
+
uploaded_file.eof? # returns true if the whole IO was read
|
247
|
+
uploaded_file.rewind # rewinds the IO
|
248
|
+
uploaded_file.close # closes the IO
|
249
|
+
```
|
250
|
+
|
251
|
+
## Attachment
|
252
|
+
|
253
|
+
Storages, uploaders, and uploaded file objects are the main components for
|
254
|
+
managing files. Since most often you also want to *attach* the uploaded files
|
255
|
+
to database records, Shrine comes with a high-level attachment interface, which
|
256
|
+
uses these components internally.
|
257
|
+
|
258
|
+
Usually you're using an ORM for saving database records, in which case you can
|
259
|
+
load an additional plugin to automatically tie the attached files to record
|
260
|
+
lifecycle. But you can also use Shrine just with plain models.
|
261
|
+
|
262
|
+
```rb
|
263
|
+
Shrine.plugin :sequel # :activerecord
|
264
|
+
```
|
265
|
+
|
266
|
+
```rb
|
267
|
+
class Photo < Sequel::Model # ActiveRecord::Base
|
268
|
+
include ImageUploader[:image] #
|
269
|
+
include ImageUploader.attachment(:image) # these are all equivalent
|
270
|
+
include ImageUploader::Attachment.new(:image) #
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
You can choose whichever of these three syntaxes you prefer. In any case this
|
275
|
+
will create a `Shrine::Attachment` module with attachment methods for the
|
276
|
+
specified attribute, which then get added to your model when you include it:
|
277
|
+
|
278
|
+
* `#image=` – uploads the file to temporary storage and serializes the result into `image_data`
|
279
|
+
* `#image` – returns `Shrine::UploadedFile` instantiated from `image_data`
|
280
|
+
* `#image_url` – calls `url` on the attachment if it's present, otherwise returns nil
|
281
|
+
* `#image_attacher` – returns instance of `Shrine::Attacher` which handles the attaching
|
140
282
|
|
141
283
|
The ORM plugin that we loaded adds appropriate callbacks, so when record is
|
142
|
-
saved the attachment is uploaded to permanent
|
143
|
-
|
284
|
+
saved the attachment is uploaded to permanent storage, and when record is
|
285
|
+
deleted the attachment is deleted as well.
|
144
286
|
|
145
287
|
```rb
|
288
|
+
# no file is attached
|
289
|
+
photo.image #=> nil
|
290
|
+
|
291
|
+
# the assigned file is cached to temporary storage and written to `image_data` column
|
146
292
|
photo.image = File.open("waterfall.jpg")
|
147
|
-
photo.
|
293
|
+
photo.image #=> #<Shrine::UploadedFile @data={...}>
|
294
|
+
photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg"
|
295
|
+
photo.image_data #=> '{"storage":"cache","id":"0sdfllasfi842.jpg","metadata":{...}}'
|
148
296
|
|
297
|
+
# the cached file is promoted to permanent storage and saved to `image_data` column
|
149
298
|
photo.save
|
150
|
-
photo.
|
299
|
+
photo.image #=> #<Shrine::UploadedFile @data={...}>
|
300
|
+
photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg"
|
301
|
+
photo.image_data #=> '{"storage":"store","id":"l02kladf8jlda.jpg","metadata":{...}}'
|
151
302
|
|
303
|
+
# the attached file is deleted with the record
|
152
304
|
photo.destroy
|
153
305
|
photo.image.exists? #=> false
|
154
306
|
```
|
155
307
|
|
156
|
-
|
308
|
+
If there is already a file attached, and the attachment is overriden (either
|
309
|
+
with a new file or no file), the previous attachment will get deleted when the
|
310
|
+
record gets saved.
|
157
311
|
|
158
312
|
```rb
|
159
313
|
photo.update(image: new_file) # changes the attachment and deletes previous
|
@@ -161,138 +315,148 @@ photo.update(image: new_file) # changes the attachment and deletes previous
|
|
161
315
|
photo.update(image: nil) # removes the attachment and deletes previous
|
162
316
|
```
|
163
317
|
|
164
|
-
In
|
165
|
-
|
318
|
+
In addition to assigning raw files, you can also assign a JSON representation
|
319
|
+
of files that are already uploaded to the temporary storage. This allows Shrine
|
320
|
+
to retain cached files in case of validation errors, and handle [direct
|
321
|
+
uploads], via the hidden form field.
|
166
322
|
|
167
323
|
```rb
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
class Movie < Sequel::Model
|
174
|
-
include VideoUploader[:video] # uses "video_data" column
|
175
|
-
end
|
324
|
+
photo.image = '{
|
325
|
+
"storage": "cache",
|
326
|
+
"id": "9260ea09d8effd.jpg",
|
327
|
+
"metadata": { ... }
|
328
|
+
}'
|
176
329
|
```
|
177
330
|
|
178
|
-
|
331
|
+
## Attacher
|
179
332
|
|
180
|
-
The model attachment
|
181
|
-
`Shrine::Attacher` object.
|
182
|
-
to your model, prefer explicitness over callbacks, or use Shrine with custom
|
183
|
-
models, you can use `Shrine::Attacher` directly:
|
333
|
+
The model attachment attributes and callbacks just delegate the behaviour
|
334
|
+
to a `Shrine::Attacher` object.
|
184
335
|
|
185
336
|
```rb
|
186
|
-
attacher = ImageUploader::Attacher.new(photo, :image) #
|
187
|
-
|
188
|
-
attacher.cache #=> #<ImageUploader @storage_key=:cache>
|
189
|
-
attacher.store #=> #<ImageUploader @storage_key=:store>
|
337
|
+
attacher = ImageUploader::Attacher.new(photo, :image) # returned by `photo.image_attacher`
|
190
338
|
|
191
339
|
attacher.assign(file) # equivalent to `photo.image = file`
|
192
340
|
attacher.get # equivalent to `photo.image`
|
193
341
|
attacher.url # equivalent to `photo.image_url`
|
194
|
-
|
195
|
-
attacher.finalize # promotes cached file to store, deletes old attachment (after save callback)
|
196
|
-
attacher.destroy # deletes attachment (after destory callback)
|
197
342
|
```
|
198
343
|
|
199
|
-
|
344
|
+
The attacher is what drives attaching files to models, and it functions
|
345
|
+
independently from models' attachment interface. This means that you can use it
|
346
|
+
as an alternative, in case you prefer not to add additional attributes to the
|
347
|
+
model, or prefer explicitness over callbacks. It's also useful when you need
|
348
|
+
something more advanced which isn't available through the attachment
|
349
|
+
attributes.
|
200
350
|
|
201
|
-
|
351
|
+
Whenever the attacher uploads or deletes files, it sends a `context` hash
|
352
|
+
which includes `:record`, `:name`, and `:action` keys, so that you can perform
|
353
|
+
processing or generate location differently depending on this information. See
|
354
|
+
[Context] section for more details.
|
202
355
|
|
203
|
-
|
204
|
-
achieved with by adding a `multiple` HTML attribute to the file field: `<input
|
205
|
-
type="file" multiple>`.
|
356
|
+
For more information about `Shrine::Attacher`, see [Using Attacher] guide.
|
206
357
|
|
207
|
-
|
208
|
-
can instead attach each file to a separate database record, which is a much
|
209
|
-
more flexible solution.
|
358
|
+
## Plugin system
|
210
359
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
360
|
+
By default Shrine comes with a small core which provides only the essential
|
361
|
+
functionality. All additional features are available via [plugins], which also
|
362
|
+
ship with Shrine. This way you can choose exactly what and how much Shrine does
|
363
|
+
for you, and you load the code only for features that you use.
|
215
364
|
|
216
|
-
|
365
|
+
```rb
|
366
|
+
Shrine.plugin :logging # adds logging
|
367
|
+
```
|
217
368
|
|
218
|
-
|
219
|
-
|
220
|
-
|
369
|
+
Plugins add behaviour by extending Shrine core classes via module inclusion, and
|
370
|
+
many of them also accept configuration options. The plugin system respects
|
371
|
+
inheritance, so you can choose to load a plugin globally or per uploader.
|
221
372
|
|
222
373
|
```rb
|
223
|
-
class
|
224
|
-
#
|
374
|
+
class ImageUploader < Shrine
|
375
|
+
plugin :store_dimensions # extract image dimensions only for this uploader and its descendants
|
225
376
|
end
|
226
377
|
```
|
378
|
+
|
379
|
+
## Metadata
|
380
|
+
|
381
|
+
Shrine automatically extracts available file metadata and saves them to the
|
382
|
+
`Shrine::UploadedFile`. You can access them through the `#metadata` hash or via
|
383
|
+
metadata methods:
|
384
|
+
|
227
385
|
```rb
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
386
|
+
uploaded_file.metadata #=>
|
387
|
+
# {
|
388
|
+
# "filename" => "matrix.mp4",
|
389
|
+
# "mime_type" => "video/mp4",
|
390
|
+
# "size" => 345993,
|
391
|
+
# }
|
233
392
|
|
234
|
-
|
393
|
+
uploaded_file.original_filename #=> "matrix.mp4"
|
394
|
+
uploaded_file.extension #=> "mp4"
|
395
|
+
uploaded_file.mime_type #=> "video/mp4"
|
396
|
+
uploaded_file.size #=> 345993
|
397
|
+
```
|
235
398
|
|
236
|
-
|
237
|
-
* extracts metadata
|
238
|
-
* generates unique location
|
239
|
-
* uploads file(s) (this is where the storage is called)
|
240
|
-
* closes uploaded file(s)
|
399
|
+
### MIME type
|
241
400
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
401
|
+
By default "mime_type" will be inherited from `#content_type` of the uploaded
|
402
|
+
file, which is set from the "Content-Type" request header, but this header is
|
403
|
+
determined by the browser solely based on the file extension. This means that
|
404
|
+
by default Shrine's "mime_type" is **not guaranteed** to hold the actual MIME
|
405
|
+
type of the file.
|
247
406
|
|
248
|
-
|
249
|
-
|
407
|
+
However, if you load the `determine_mime_type` plugin, that will make Shrine
|
408
|
+
always extract the MIME type from **file content** .
|
250
409
|
|
251
410
|
```rb
|
252
|
-
|
253
|
-
uploaded_file.metadata #=> {...}
|
254
|
-
uploaded_file.download #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20151004-74201-1t2jacf.mp4>
|
255
|
-
uploaded_file.exists? #=> true
|
256
|
-
uploaded_file.open { |io| io.read }
|
257
|
-
uploaded_file.delete
|
258
|
-
# ...
|
411
|
+
Shrine.plugin :determine_mime_type
|
259
412
|
```
|
260
|
-
|
261
|
-
This is the same object that is returned when we access the attachment through
|
262
|
-
the record:
|
263
|
-
|
264
413
|
```rb
|
265
|
-
|
414
|
+
File.write("image.png", "<?php ... ?>") # PHP file with a .png extension
|
415
|
+
photo = Photo.create(image: File.open("image.png"))
|
416
|
+
photo.image.mime_type #=> "text/x-php"
|
266
417
|
```
|
267
418
|
|
268
|
-
|
419
|
+
By the default the UNIX [`file`] utility is used, but you can also choose a
|
420
|
+
different analyzer, see plugin's documentation for more details.
|
269
421
|
|
270
|
-
|
271
|
-
and any additional features are available via plugins. This way you can choose
|
272
|
-
exactly what and how much Shrine does for you. See the [website] for a complete
|
273
|
-
list of plugins.
|
422
|
+
### Custom metadata
|
274
423
|
|
275
|
-
|
276
|
-
|
424
|
+
In addition to the built-in metadata, you can also extract and store completely
|
425
|
+
custom metadata with the `add_metadata` plugin. For example, if we're uploading
|
426
|
+
videos, we could store additional video-specific metadata:
|
277
427
|
|
278
428
|
```rb
|
279
|
-
|
429
|
+
require "streamio-ffmpeg"
|
280
430
|
|
281
|
-
class
|
282
|
-
plugin :
|
431
|
+
class VideoUploader < Shrine
|
432
|
+
plugin :add_metadata
|
433
|
+
|
434
|
+
add_metadata do |io, context|
|
435
|
+
movie = FFMPEG::Movie.new(io.path)
|
436
|
+
|
437
|
+
{ "duration" => movie.duration,
|
438
|
+
"bitrate" => movie.bitrate,
|
439
|
+
"resolution" => movie.resolution,
|
440
|
+
"frame_rate" => movie.frame_rate }
|
441
|
+
end
|
283
442
|
end
|
284
443
|
```
|
444
|
+
```rb
|
445
|
+
video.metadata["duration"] #=> 7.5
|
446
|
+
video.metadata["bitrate"] #=> 481
|
447
|
+
video.metadata["resolution"] #=> "640x480"
|
448
|
+
video.metadata["frame_rate"] #=> 16.72
|
449
|
+
```
|
285
450
|
|
286
451
|
## Processing
|
287
452
|
|
288
|
-
|
289
|
-
|
453
|
+
You can have Shrine perform file processing before uploading to storage. It's
|
454
|
+
generally best to process files prior to uploading to permanent storage,
|
455
|
+
because at that point the selected file has been succesfully validated, and
|
456
|
+
this part can be moved into a background job.
|
290
457
|
|
291
|
-
|
292
|
-
|
293
|
-
action), then when the record is saved the cached file is "promoted" to
|
294
|
-
permanent storage ("store" action). We generally want to process on the "store"
|
295
|
-
action, because it happens after file validations and can be backgrounded.
|
458
|
+
This promote phase is called `:store`, and we can use the `processing` plugin
|
459
|
+
to define processing for that phase:
|
296
460
|
|
297
461
|
```rb
|
298
462
|
class ImageUploader < Shrine
|
@@ -304,11 +468,16 @@ class ImageUploader < Shrine
|
|
304
468
|
end
|
305
469
|
```
|
306
470
|
|
307
|
-
|
471
|
+
Now, how do we do the actual processing? Well, Shrine actually doesn't ship
|
308
472
|
with any file processing functionality, because that is a generic problem that
|
309
473
|
belongs in separate libraries. If the type of files you're uploading are
|
310
474
|
images, I created the [image_processing] gem which you can use with Shrine:
|
311
475
|
|
476
|
+
```rb
|
477
|
+
# Gemfile
|
478
|
+
gem "image_processing"
|
479
|
+
gem "mini_magick", ">= 4.3.5"
|
480
|
+
```
|
312
481
|
```rb
|
313
482
|
require "image_processing/mini_magick"
|
314
483
|
|
@@ -317,23 +486,31 @@ class ImageUploader < Shrine
|
|
317
486
|
plugin :processing
|
318
487
|
|
319
488
|
process(:store) do |io, context|
|
320
|
-
resize_to_limit(io.download,
|
489
|
+
resize_to_limit!(io.download, 800, 800)
|
321
490
|
end
|
322
491
|
end
|
323
492
|
```
|
324
493
|
|
325
494
|
Here the `io` is a cached `Shrine::UploadedFile`, so we need to download it to
|
326
|
-
a
|
495
|
+
a file, since file processing tools usually work with files on the filesystem.
|
496
|
+
|
497
|
+
Shrine treats file processing as a functional transformation; you are given the
|
498
|
+
original file, and how you're going to perform processing is entirely up to
|
499
|
+
you, you only need to return the processed files at the end of the block. Then
|
500
|
+
instead of uploading the original file, Shrine will continue to upload the
|
501
|
+
files that the processing block returned.
|
327
502
|
|
328
503
|
### Versions
|
329
504
|
|
330
505
|
Sometimes we want to generate multiple files as the result of processing. If
|
331
506
|
we're uploading images, we might want to store various thumbnails alongside the
|
332
|
-
original image. If we're uploading videos, we might want to save
|
333
|
-
or transcode
|
507
|
+
original image. If we're uploading videos, we might want to save screenshots
|
508
|
+
or transcode the video into different formats.
|
334
509
|
|
335
|
-
To save multiple files, we just need to load the `versions` plugin,
|
336
|
-
|
510
|
+
To be able to save multiple files, we just need to load the `versions` plugin,
|
511
|
+
and then in processing block we can return a Hash of files. It is recommended
|
512
|
+
to also load the `delete_raw` plugin, so that processed files are automatically
|
513
|
+
deleted after uploading.
|
337
514
|
|
338
515
|
```rb
|
339
516
|
require "image_processing/mini_magick"
|
@@ -341,45 +518,60 @@ require "image_processing/mini_magick"
|
|
341
518
|
class ImageUploader < Shrine
|
342
519
|
include ImageProcessing::MiniMagick
|
343
520
|
plugin :processing
|
344
|
-
plugin :versions
|
521
|
+
plugin :versions # enable Shrine to handle a hash of files
|
522
|
+
plugin :delete_raw # delete processed files after uploading
|
345
523
|
|
346
524
|
process(:store) do |io, context|
|
347
|
-
|
348
|
-
|
349
|
-
|
525
|
+
original = io.download
|
526
|
+
|
527
|
+
size_800 = resize_to_limit!(original, 800, 800)
|
528
|
+
size_500 = resize_to_limit(size_800, 500, 500)
|
529
|
+
size_300 = resize_to_limit(size_500, 300, 300)
|
350
530
|
|
351
|
-
{large:
|
531
|
+
{original: io, large: size_800, medium: size_500, small: size_300}
|
352
532
|
end
|
353
533
|
end
|
354
534
|
```
|
355
535
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
everything into a PORO class. It is recommended to load the `delete_raw` plugin
|
360
|
-
so that processed files are automatically deleted after uploading.
|
361
|
-
|
362
|
-
Each version will be saved to the attachment column, and the attachment getter
|
363
|
-
will simply return a Hash of `Shrine::UploadedFile` objects:
|
536
|
+
After these files have been uploaded, their data will all be saved to the
|
537
|
+
`<attachment>_data` column. The attachment getter will then read them as a Hash
|
538
|
+
of `Shrine::UploadedFile` objects.
|
364
539
|
|
365
540
|
```rb
|
366
|
-
photo.
|
541
|
+
photo.image_data #=>
|
542
|
+
# '{
|
543
|
+
# "original": {"id":"9sd84.jpg", "storage":"store", "metadata":{...}},
|
544
|
+
# "large": {"id":"lg043.jpg", "storage":"store", "metadata":{...}},
|
545
|
+
# "medium": {"id":"kd9fk.jpg", "storage":"store", "metadata":{...}},
|
546
|
+
# "small": {"id":"932fl.jpg", "storage":"store", "metadata":{...}}
|
547
|
+
# }'
|
548
|
+
|
549
|
+
photo.image #=>
|
550
|
+
# {
|
551
|
+
# :original => #<Shrine::UploadedFile @data={"id"=>"9sd84.jpg", ...}>,
|
552
|
+
# :large => #<Shrine::UploadedFile @data={"id"=>"lg043.jpg", ...}>,
|
553
|
+
# :medium => #<Shrine::UploadedFile @data={"id"=>"kd9fk.jpg", ...}>,
|
554
|
+
# :small => #<Shrine::UploadedFile @data={"id"=>"932fl.jpg", ...}>,
|
555
|
+
# }
|
556
|
+
|
557
|
+
photo.image[:medium] #=> #<Shrine::UploadedFile>
|
558
|
+
photo.image[:medium].url #=> "/uploads/store/lg043.jpg"
|
559
|
+
photo.image[:medium].size #=> 5825949
|
560
|
+
photo.image[:medium].mime_type #=> "image/jpeg"
|
561
|
+
```
|
367
562
|
|
368
|
-
|
369
|
-
photo.image[:large].width #=> 700
|
370
|
-
photo.image[:medium].width #=> 500
|
371
|
-
photo.image[:small].width #=> 300
|
563
|
+
The `versions` plugin also expands `#<attachment>_url` to accept version names:
|
372
564
|
|
373
|
-
|
565
|
+
```rb
|
374
566
|
photo.image_url(:large) #=> "..."
|
375
567
|
```
|
376
568
|
|
377
569
|
### Custom processing
|
378
570
|
|
379
571
|
Your processing tool doesn't have to be in any way designed for Shrine
|
380
|
-
([image_processing] is a generic library),
|
381
|
-
|
382
|
-
video
|
572
|
+
([image_processing] that we saw earlier is a generic library), the only thing
|
573
|
+
that you need to do is return processed files as some kind of IO objects. Here
|
574
|
+
is an example of transcoding a video using [ffmpeg]:
|
383
575
|
|
384
576
|
```rb
|
385
577
|
require "streamio-ffmpeg"
|
@@ -387,6 +579,7 @@ require "streamio-ffmpeg"
|
|
387
579
|
class VideoUploader < Shrine
|
388
580
|
plugin :processing
|
389
581
|
plugin :versions
|
582
|
+
plugin :delete_raw
|
390
583
|
|
391
584
|
process(:store) do |io, context|
|
392
585
|
mov = io.download
|
@@ -404,286 +597,136 @@ class VideoUploader < Shrine
|
|
404
597
|
end
|
405
598
|
```
|
406
599
|
|
407
|
-
|
408
|
-
|
409
|
-
Whenever a file is uploaded by the attacher, an additional `:action` parameter
|
410
|
-
is added to the context, which holds a symbol name describing what the file was
|
411
|
-
uploaded for. For example, for caching files `action: :cache` will be sent, for
|
412
|
-
promoting `action: :store`, while for backing up attacher sends `action:
|
413
|
-
:backup`.
|
414
|
-
|
415
|
-
The argument to the `process` declaration is the name of that action. When
|
416
|
-
uploading via the uploader, you can add `:action` option with the value of the
|
417
|
-
processing block you want performed before uploading.
|
418
|
-
|
419
|
-
```rb
|
420
|
-
uploader = ImageUploader.new(:store)
|
421
|
-
uploader.upload(file, action: :store) # performs processing defined under ":store"
|
422
|
-
```
|
423
|
-
|
424
|
-
You can also define and call processing for a custom action:
|
425
|
-
|
426
|
-
```rb
|
427
|
-
class ImageUploader < Shrine
|
428
|
-
process(:my_action) { |io, context| special_processing(io) }
|
429
|
-
end
|
430
|
-
```
|
431
|
-
```rb
|
432
|
-
uploader.upload(file, action: :my_action)
|
433
|
-
```
|
600
|
+
## Context
|
434
601
|
|
435
|
-
|
436
|
-
|
602
|
+
The `#upload` (and `#delete`) methods accept a hash of options as the second
|
603
|
+
argument, which is forwarded to all other tasks like processing, extracting
|
604
|
+
metadata and generating location.
|
437
605
|
|
438
606
|
```rb
|
439
|
-
uploader.
|
607
|
+
uploader.upload(file, {foo: "bar"}) # context hash is forwarded to all tasks around upload
|
440
608
|
```
|
441
609
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
to uploaded file, and can contain useful information depending on the situation:
|
610
|
+
Some options are actually recognized by Shrine, like `:location` and
|
611
|
+
`:upload_options`, and some are added by plugins. However, most options are
|
612
|
+
there just to provide you context, for more flexibility in performing tasks and
|
613
|
+
better logging.
|
447
614
|
|
448
|
-
|
449
|
-
|
450
|
-
* `context[:action]` -- identifier for the action being performed (`:cache`, `:store`, `:recache`, `:backup`, ...)
|
451
|
-
* `context[:version]` -- version name of the IO in the argument
|
452
|
-
* ...
|
615
|
+
The attacher automatically includes additional `context` information for each
|
616
|
+
upload and delete:
|
453
617
|
|
454
|
-
|
455
|
-
|
618
|
+
* `context[:record]` – model instance where the file is attached
|
619
|
+
* `context[:name]` – name of the attachment attribute on the model
|
620
|
+
* `context[:action]` – identifier for the action being performed (`:cache`, `:store`, `:recache`, `:backup`, ...)
|
456
621
|
|
457
622
|
```rb
|
458
623
|
class VideoUploader < Shrine
|
459
624
|
process(:store) do |io, context|
|
460
|
-
trim_video(io, 300) if context[:record].
|
625
|
+
trim_video(io, 300) if context[:record].user.free_plan?
|
461
626
|
end
|
462
627
|
end
|
463
628
|
```
|
464
629
|
|
465
|
-
The context is just a hash that is passed to the uploader methods. If you're
|
466
|
-
using the uploader directly, you can pass the context directly:
|
467
|
-
|
468
|
-
```rb
|
469
|
-
uploader.upload(file, {foo: "bar"}) # passing context hash directly
|
470
|
-
```
|
471
|
-
|
472
|
-
## Metadata
|
473
|
-
|
474
|
-
Shrine automatically extracts and stores available file metadata:
|
475
|
-
|
476
|
-
```rb
|
477
|
-
photo = Photo.create(image: image)
|
478
|
-
|
479
|
-
photo.image.metadata #=>
|
480
|
-
# {
|
481
|
-
# "filename" => "nature.jpg",
|
482
|
-
# "mime_type" => "image/jpeg",
|
483
|
-
# "size" => 345993,
|
484
|
-
# }
|
485
|
-
|
486
|
-
photo.image.original_filename #=> "nature.jpg"
|
487
|
-
photo.image.extension #=> "jpg"
|
488
|
-
photo.image.mime_type #=> "image/jpeg"
|
489
|
-
photo.image.size #=> 345993
|
490
|
-
```
|
491
|
-
|
492
|
-
### MIME type
|
493
|
-
|
494
|
-
By default, "mime_type" is inherited from `#content_type` of the uploaded file,
|
495
|
-
which is set from the "Content-Type" request header, which is determined by the
|
496
|
-
browser solely based on the file extension. This means that by default Shrine's
|
497
|
-
"mime_type" is *not* guaranteed to hold the actual MIME type of the file.
|
498
|
-
|
499
|
-
To help with that Shrine provides the `determine_mime_type` plugin, which by
|
500
|
-
default uses the UNIX [file] utility to determine the actual MIME type:
|
501
|
-
|
502
|
-
```rb
|
503
|
-
Shrine.plugin :determine_mime_type
|
504
|
-
```
|
505
|
-
```rb
|
506
|
-
File.write("image.jpg", "<?php ... ?>") # PHP file with a .jpg extension
|
507
|
-
photo = Photo.create(image: File.open("image.jpg"))
|
508
|
-
photo.image.mime_type #=> "text/x-php"
|
509
|
-
```
|
510
|
-
|
511
|
-
### Custom metadata
|
512
|
-
|
513
|
-
You can also extract and store completely custom metadata with the
|
514
|
-
`add_metadata` plugin:
|
515
|
-
|
516
|
-
```rb
|
517
|
-
require "mini_magick"
|
518
|
-
|
519
|
-
class ImageUploader < Shrine
|
520
|
-
plugin :add_metadata
|
521
|
-
|
522
|
-
add_metadata :exif do |io, context|
|
523
|
-
MiniMagick::Image.new(io.path).exif
|
524
|
-
end
|
525
|
-
end
|
526
|
-
```
|
527
|
-
```rb
|
528
|
-
photo.image.metadata["exif"]
|
529
|
-
# or
|
530
|
-
photo.image.exif
|
531
|
-
```
|
532
|
-
|
533
630
|
## Validation
|
534
631
|
|
535
|
-
|
536
|
-
|
632
|
+
Shrine can perform file validations for files assigned to the model. The
|
633
|
+
validations are registered inside a `Attacher.validate` block, and you can load
|
634
|
+
the `validation_helpers` plugin to get convenient file validation methods:
|
537
635
|
|
538
636
|
```rb
|
539
|
-
class
|
637
|
+
class DocumentUploader < Shrine
|
540
638
|
plugin :validation_helpers
|
541
639
|
|
542
640
|
Attacher.validate do
|
543
|
-
validate_max_size
|
544
|
-
validate_mime_type_inclusion ["
|
641
|
+
validate_max_size 5*1024*1024, message: "is too large (max is 5 MB)"
|
642
|
+
validate_mime_type_inclusion ["application/pdf"]
|
545
643
|
end
|
546
644
|
end
|
547
645
|
```
|
548
646
|
|
549
647
|
```rb
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
648
|
+
user = User.new
|
649
|
+
user.cv = File.open("cv.pdf")
|
650
|
+
user.valid? #=> false
|
651
|
+
user.errors.to_hash #=> {cv: ["is too large (max is 5 MB)"]}
|
554
652
|
```
|
555
653
|
|
556
654
|
You can also do custom validations:
|
557
655
|
|
558
656
|
```rb
|
559
|
-
class
|
657
|
+
class DocumentUploader < Shrine
|
560
658
|
Attacher.validate do
|
561
|
-
errors << "
|
659
|
+
errors << "has more than 3 pages" if get.metadata["pages"] > 3
|
562
660
|
end
|
563
661
|
end
|
564
662
|
```
|
565
663
|
|
664
|
+
When file validations fail, Shrine will by default keep the invalid cached file
|
665
|
+
assigned to the model instance. If you want the invalid file to be deassigned,
|
666
|
+
you can load the `remove_invalid` plugin.
|
667
|
+
|
566
668
|
The `Attacher.validate` block is executed in context of a `Shrine::Attacher`
|
567
669
|
instance:
|
568
670
|
|
569
671
|
```rb
|
570
|
-
class
|
672
|
+
class DocumentUploader < Shrine
|
571
673
|
Attacher.validate do
|
572
674
|
self #=> #<Shrine::Attacher>
|
573
675
|
|
574
676
|
get #=> #<Shrine::UploadedFile>
|
575
|
-
record
|
576
|
-
|
677
|
+
record #=> #<User>
|
678
|
+
name #=> :cv
|
577
679
|
end
|
578
680
|
end
|
579
681
|
```
|
580
682
|
|
581
|
-
##
|
582
|
-
|
583
|
-
Before Shrine uploads a file, it generates a random location for it. By
|
584
|
-
default the hierarchy is flat, all files are stored in the root of the storage.
|
585
|
-
If you want that each attachment has its own directory, you can load the
|
586
|
-
`pretty_location` plugin:
|
587
|
-
|
588
|
-
```rb
|
589
|
-
Shrine.plugin :pretty_location
|
590
|
-
```
|
591
|
-
```rb
|
592
|
-
photo = Photo.create(image: File.open("nature.jpg"))
|
593
|
-
photo.image.id #=> "photo/34/image/34krtreds2df.jpg"
|
594
|
-
```
|
683
|
+
## Location
|
595
684
|
|
596
|
-
|
597
|
-
|
685
|
+
Before Shrine uploads a file, it generates a random location for it. By default
|
686
|
+
the hierarchy is flat; all files are stored in the root directory of the
|
687
|
+
storage. You can change how the location is generated by overriding
|
688
|
+
`#generate_location`:
|
598
689
|
|
599
690
|
```rb
|
600
691
|
class ImageUploader < Shrine
|
601
692
|
def generate_location(io, context)
|
602
|
-
if context[:record]
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
693
|
+
type = context[:record].class.name.downcase if context[:record]
|
694
|
+
style = context[:version] == :original ? "originals" : "thumbs" if context[:version]
|
695
|
+
name = super # the default unique identifier
|
696
|
+
|
697
|
+
[type, style, name].compact.join("/")
|
607
698
|
end
|
608
699
|
end
|
609
700
|
```
|
610
|
-
|
611
|
-
Note that there should always be a random component in the location, so that
|
612
|
-
dirty tracking is detected properly; you can use `Shrine#generate_uid`. Inside
|
613
|
-
`#generate_location` you can access the extracted metadata through
|
614
|
-
`context[:metadata]`.
|
615
|
-
|
616
|
-
When using the uploader directly, it's possible to bypass `#generate_location`
|
617
|
-
by passing a `:location`:
|
618
|
-
|
619
|
-
```rb
|
620
|
-
uploader = MyUploader.new(:store)
|
621
|
-
file = File.open("nature.jpg")
|
622
|
-
uploader.upload(file, location: "some/specific/location.jpg")
|
623
|
-
```
|
624
|
-
|
625
|
-
## Storage
|
626
|
-
|
627
|
-
"Storages" are objects which know how to manage files on a particular service.
|
628
|
-
Other than [FileSystem], Shrine also ships with Amazon [S3] storage:
|
629
|
-
|
630
|
-
```rb
|
631
|
-
gem "aws-sdk", "~> 2.1"
|
632
|
-
```
|
633
|
-
```rb
|
634
|
-
require "shrine/storage/s3"
|
635
|
-
|
636
|
-
Shrine.storages[:store] = Shrine::Storage::S3.new(
|
637
|
-
access_key_id: "<ACCESS_KEY_ID>", # "xyz"
|
638
|
-
secret_access_key: "<SECRET_ACCESS_KEY>", # "abc"
|
639
|
-
region: "<REGION>", # "eu-west-1"
|
640
|
-
bucket: "<BUCKET>", # "my-bucket"
|
641
|
-
)
|
642
|
-
```
|
643
|
-
|
644
|
-
```rb
|
645
|
-
photo = Photo.new(image: File.open("image.png"))
|
646
|
-
photo.image_url #=> "/uploads/cache/j4k343ui12ls9.png"
|
647
|
-
photo.save
|
648
|
-
photo.image_url #=> "https://my-bucket.s3.amazonaws.com/0943sf8gfk13.png"
|
649
701
|
```
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
website.
|
658
|
-
|
659
|
-
### Upload options
|
660
|
-
|
661
|
-
Many storages accept additional upload options, which you can pass via the
|
662
|
-
`upload_options` plugin, or manually when uploading:
|
663
|
-
|
664
|
-
```rb
|
665
|
-
uploader = MyUploader.new(:store)
|
666
|
-
uploader.upload(file, upload_options: {acl: "private"})
|
702
|
+
uploads/
|
703
|
+
photos/
|
704
|
+
originals/
|
705
|
+
la98lda74j3g.jpg
|
706
|
+
thumbs/
|
707
|
+
95kd8kafg80a.jpg
|
708
|
+
ka8agiaf9gk4.jpg
|
667
709
|
```
|
668
710
|
|
669
|
-
|
711
|
+
Note that there should always be a random component in the location, so that
|
712
|
+
any ORM dirty tracking is detected properly. Inside `#generate_location` you
|
713
|
+
can also access the extracted metadata through `context[:metadata]`.
|
670
714
|
|
671
|
-
|
672
|
-
|
673
|
-
can put something like this in your Rake task:
|
715
|
+
When uploading single files, it's possible to bypass `#generate_location` via
|
716
|
+
the uploader, by specifying `:location`:
|
674
717
|
|
675
718
|
```rb
|
676
|
-
|
677
|
-
file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
|
719
|
+
uploader.upload(file, location: "some/specific/location.mp4")
|
678
720
|
```
|
679
721
|
|
680
722
|
## Direct uploads
|
681
723
|
|
682
|
-
Shrine comes with a `direct_upload` plugin
|
683
|
-
or an external service like Amazon S3. It
|
684
|
-
can mount in your app:
|
724
|
+
Shrine comes with a `direct_upload` plugin that can be used for client-side
|
725
|
+
asynchronous uploads to your app or an external service like Amazon S3. It
|
726
|
+
provides a [Roda] app which you can mount in your app:
|
685
727
|
|
686
728
|
```rb
|
729
|
+
# Gemfile
|
687
730
|
gem "roda"
|
688
731
|
```
|
689
732
|
```rb
|
@@ -695,25 +738,26 @@ Rails.application.routes.draw do
|
|
695
738
|
end
|
696
739
|
```
|
697
740
|
|
698
|
-
|
741
|
+
The above setup will provide the following endpoints:
|
699
742
|
|
700
743
|
* `POST /images/cache/upload` - for direct uploads to your app
|
701
744
|
* `GET /images/cache/presign` - for direct uploads to external service (e.g. Amazon S3)
|
702
745
|
|
703
|
-
|
704
|
-
the
|
705
|
-
[jQuery-File-Upload], [Dropzone] or
|
746
|
+
Now when the user selects a file, the client can immediately start uploading
|
747
|
+
the file asynchronously using one of these endpoints. For JavaScript you can
|
748
|
+
use generic file upload libraries like [jQuery-File-Upload], [Dropzone] or
|
749
|
+
[FineUploader].
|
706
750
|
|
707
|
-
See the [direct_upload] plugin documentation and [Direct Uploads to S3]
|
708
|
-
for more details, as well as the [Roda][roda_demo] and
|
709
|
-
demo apps which implement multiple uploads directly to S3.
|
751
|
+
See the [direct_upload] plugin documentation and [Direct Uploads to S3][direct uploads]
|
752
|
+
guide for more details, as well as the [Roda][roda_demo] and
|
753
|
+
[Rails][rails_demo] demo apps which implement multiple uploads directly to S3.
|
710
754
|
|
711
755
|
## Backgrounding
|
712
756
|
|
713
|
-
Shrine is the first file
|
757
|
+
Shrine is the first file attachment library designed for backgrounding support.
|
714
758
|
Moving phases of managing file attachments to background jobs is essential for
|
715
759
|
scaling and good user experience, and Shrine provides a `backgrounding` plugin
|
716
|
-
which makes it
|
760
|
+
which makes it easy to plug in your favourite backgrounding library:
|
717
761
|
|
718
762
|
```rb
|
719
763
|
Shrine.plugin :backgrounding
|
@@ -738,23 +782,71 @@ end
|
|
738
782
|
```
|
739
783
|
|
740
784
|
The above puts all promoting (uploading cached file to permanent storage) and
|
741
|
-
deleting of files into
|
742
|
-
you can use [any other backgrounding library][backgrounding libraries].
|
785
|
+
deleting of files into background jobs using Sidekiq. Obviously instead of
|
786
|
+
Sidekiq you can use [any other backgrounding library][backgrounding libraries].
|
743
787
|
|
744
|
-
The main advantages of Shrine's backgrounding support over other file
|
788
|
+
The main advantages of Shrine's backgrounding support over other file attachment
|
745
789
|
libraries are:
|
746
790
|
|
747
|
-
* **User experience** –
|
791
|
+
* **User experience** – Before starting the background job, Shrine will save the
|
748
792
|
record with the cached attachment so that it can be immediately shown to the
|
749
793
|
user. With other file upload libraries users cannot see the file until the
|
750
794
|
background job has finished.
|
751
|
-
* **Simplicity** – Instead of
|
752
|
-
to
|
753
|
-
required.
|
754
|
-
* **Generality** –
|
795
|
+
* **Simplicity** – Instead of shipping with workers for you, Shrine allows you
|
796
|
+
to write your own workers and plug them in very easily. And no extra
|
797
|
+
columns are required.
|
798
|
+
* **Generality** – This setup will automatically be used for all uploaders,
|
755
799
|
types of files and models.
|
756
|
-
* **Safety** – All of Shrine's
|
757
|
-
into account, and concurrent requests are handled well.
|
800
|
+
* **Safety** – All of Shrine's features have been designed to take delayed
|
801
|
+
storing into account, and concurrent requests are handled as well.
|
802
|
+
|
803
|
+
## Clearing cache
|
804
|
+
|
805
|
+
From time to time you'll want to clean your temporary storage from old files.
|
806
|
+
Amazon S3 provides [a built-in solution][S3 lifecycle], and for FileSystem you
|
807
|
+
can run something like this periodically:
|
808
|
+
|
809
|
+
```rb
|
810
|
+
file_system = Shrine.storages[:cache]
|
811
|
+
file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
|
812
|
+
```
|
813
|
+
|
814
|
+
## Logging
|
815
|
+
|
816
|
+
Shrine ships with the `logging` which automatically logs processing, uploading,
|
817
|
+
and deleting of files. This can be very helpful for debugging and performance
|
818
|
+
monitoring.
|
819
|
+
|
820
|
+
```rb
|
821
|
+
Shrine.plugin :logging
|
822
|
+
```
|
823
|
+
```
|
824
|
+
2015-10-09T20:06:06.676Z #25602: STORE[cache] ImageUploader[:avatar] User[29543] 1 file (0.1s)
|
825
|
+
2015-10-09T20:06:06.854Z #25602: PROCESS[store]: ImageUploader[:avatar] User[29543] 1-3 files (0.22s)
|
826
|
+
2015-10-09T20:06:07.133Z #25602: DELETE[destroyed]: ImageUploader[:avatar] User[29543] 3 files (0.07s)
|
827
|
+
```
|
828
|
+
|
829
|
+
## Settings
|
830
|
+
|
831
|
+
Each uploader can store generic settings in the `opts` hash, which can be
|
832
|
+
accessed in other uploader actions. You can store there anything that you find
|
833
|
+
convenient.
|
834
|
+
|
835
|
+
```rb
|
836
|
+
Shrine.opts[:type] = "file"
|
837
|
+
|
838
|
+
class DocumentUploader < Shrine; end
|
839
|
+
class ImageUploader < Shrine
|
840
|
+
opts[:type] = "image"
|
841
|
+
end
|
842
|
+
|
843
|
+
DocumentUploader.opts[:type] #=> "file"
|
844
|
+
ImageUploader.opts[:type] #=> "image"
|
845
|
+
```
|
846
|
+
|
847
|
+
Because `opts` is cloned in subclasses, overriding settings works with
|
848
|
+
inheritance. The `opts` hash is used internally by plugins to store
|
849
|
+
configuration.
|
758
850
|
|
759
851
|
## On-the-fly processing
|
760
852
|
|
@@ -780,10 +872,10 @@ be retried, while the previous chunks remain uploaded.
|
|
780
872
|
[Tus][tus] is an open protocol for resumable file uploads, which enables the
|
781
873
|
client and the server to achieve reliable file uploads, even on unstable
|
782
874
|
networks, with the possibility to resume the upload even after the browser is
|
783
|
-
closed or the device shut down. You can use a client library like
|
875
|
+
closed or the device are shut down. You can use a client library like
|
784
876
|
[tus-js-client] to upload the file to [tus-ruby-server], and attach the
|
785
877
|
uploaded file to a record using [shrine-url]. See [shrine-tus-demo] for an
|
786
|
-
example
|
878
|
+
example of complete implementation.
|
787
879
|
|
788
880
|
Another option might be to do chunked uploads directly to your storage service,
|
789
881
|
if the storage service supports it (e.g. Amazon S3 or Google Cloud Storage).
|
@@ -792,8 +884,8 @@ if the storage service supports it (e.g. Amazon S3 or Google Cloud Storage).
|
|
792
884
|
|
793
885
|
Shrine was heavily inspired by [Refile] and [Roda]. From Refile it borrows the
|
794
886
|
idea of "backends" (here named "storages"), attachment interface, and direct
|
795
|
-
uploads. From Roda it borrows the implementation of an extensible
|
796
|
-
system
|
887
|
+
uploads. From Roda it borrows the implementation of an extensible plugin
|
888
|
+
system.
|
797
889
|
|
798
890
|
## Similar libraries
|
799
891
|
|
@@ -802,45 +894,47 @@ system].
|
|
802
894
|
* Dragonfly
|
803
895
|
* Refile
|
804
896
|
|
897
|
+
## Code of Conduct
|
898
|
+
|
899
|
+
Everyone interacting in the Shrine project’s codebases, issue trackers, and
|
900
|
+
mailing lists is expected to follow the [Shrine code of conduct][CoC].
|
901
|
+
|
805
902
|
## License
|
806
903
|
|
807
904
|
The gem is available as open source under the terms of the [MIT License].
|
808
905
|
|
809
|
-
[image_processing]: https://github.com/janko-m/image_processing
|
810
|
-
[fastimage]: https://github.com/sdsykes/fastimage
|
811
|
-
[file]: http://linux.die.net/man/1/file
|
812
|
-
[image bombs]: https://www.bamsoftware.com/hacks/deflate.html
|
813
|
-
[aws-sdk]: https://github.com/aws/aws-sdk-ruby
|
814
|
-
[jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
|
815
|
-
[Dropzone]: https://github.com/enyo/dropzone
|
816
|
-
[FineUploader]: https://github.com/FineUploader/fine-uploader
|
817
|
-
[Roda]: https://github.com/jeremyevans/roda
|
818
|
-
[Refile]: https://github.com/refile/refile
|
819
|
-
[plugin system]: http://twin.github.io/the-plugin-system-of-sequel-and-roda/
|
820
|
-
[MIT License]: http://opensource.org/licenses/MIT
|
821
|
-
[ships with over 35 plugins]: http://shrinerb.com#plugins
|
822
906
|
[motivation]: https://twin.github.io/better-file-uploads-with-shrine-motivation/
|
823
907
|
[FileSystem]: http://shrinerb.com/rdoc/classes/Shrine/Storage/FileSystem.html
|
824
908
|
[S3]: http://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
|
825
|
-
[
|
826
|
-
[
|
827
|
-
[
|
909
|
+
[direct uploads]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
|
910
|
+
[external storages]: http://shrinerb.com/#external
|
911
|
+
[`rack_file`]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/RackFile.html
|
912
|
+
[Using Attacher]: http://shrinerb.com/rdoc/files/doc/attacher_md.html
|
913
|
+
[plugins]: http://shrinerb.com/#plugins
|
914
|
+
[`file`]: http://linux.die.net/man/1/file
|
915
|
+
[backgrounding]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/Backgrounding.html
|
916
|
+
[Context]: https://github.com/janko-m/shrine#context
|
917
|
+
[image_processing]: https://github.com/janko-m/image_processing
|
828
918
|
[ffmpeg]: https://github.com/streamio/streamio-ffmpeg
|
919
|
+
[jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
|
920
|
+
[Dropzone]: https://github.com/enyo/dropzone
|
921
|
+
[FineUploader]: https://github.com/FineUploader/fine-uploader
|
829
922
|
[direct_upload]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/DirectUpload.html
|
830
923
|
[Cloudinary]: https://github.com/janko-m/shrine-cloudinary
|
831
924
|
[Imgix]: https://github.com/janko-m/shrine-imgix
|
832
925
|
[Uploadcare]: https://github.com/janko-m/shrine-uploadcare
|
833
926
|
[Attache]: https://github.com/choonkeat/attache
|
834
|
-
[
|
835
|
-
[rails_demo]: https://github.com/erikdahlstrand/shrine-rails-example
|
836
|
-
[Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
|
837
|
-
[website]: http://shrinerb.com
|
838
|
-
[backgrounding libraries]: https://github.com/janko-m/shrine/wiki/Backgrounding-libraries
|
927
|
+
[Dragonfly]: http://markevans.github.io/dragonfly/
|
839
928
|
[tus]: http://tus.io
|
840
929
|
[tus-ruby-server]: https://github.com/janko-m/tus-ruby-server
|
841
930
|
[tus-js-client]: https://github.com/tus/tus-js-client
|
842
931
|
[shrine-tus-demo]: https://github.com/janko-m/shrine-tus-demo
|
843
932
|
[shrine-url]: https://github.com/janko-m/shrine-url
|
844
|
-
[
|
845
|
-
[
|
846
|
-
[
|
933
|
+
[Roda]: https://github.com/jeremyevans/roda
|
934
|
+
[Refile]: https://github.com/refile/refile
|
935
|
+
[MIT License]: http://opensource.org/licenses/MIT
|
936
|
+
[CoC]: https://github.com/janko-m/shrine/blob/master/CODE_OF_CONDUCT.md
|
937
|
+
[roda_demo]: https://github.com/janko-m/shrine/tree/master/demo
|
938
|
+
[rails_demo]: https://github.com/erikdahlstrand/shrine-rails-example
|
939
|
+
[backgrounding libraries]: https://github.com/janko-m/shrine/wiki/Backgrounding-libraries
|
940
|
+
[S3 lifecycle]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
|