shrine 3.0.0.beta2 → 3.0.0.beta3
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/CHANGELOG.md +45 -1
- data/README.md +100 -106
- data/doc/advantages.md +90 -88
- data/doc/attacher.md +322 -152
- data/doc/carrierwave.md +105 -113
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +92 -21
- data/doc/changing_storage.md +107 -0
- data/doc/creating_plugins.md +1 -1
- data/doc/design.md +8 -9
- data/doc/direct_s3.md +3 -2
- data/doc/metadata.md +97 -78
- data/doc/multiple_files.md +3 -3
- data/doc/paperclip.md +89 -88
- data/doc/plugins/activerecord.md +3 -12
- data/doc/plugins/backgrounding.md +126 -100
- data/doc/plugins/derivation_endpoint.md +4 -5
- data/doc/plugins/derivatives.md +63 -32
- data/doc/plugins/download_endpoint.md +54 -1
- data/doc/plugins/entity.md +1 -0
- data/doc/plugins/form_assign.md +53 -0
- data/doc/plugins/mirroring.md +37 -16
- data/doc/plugins/multi_cache.md +22 -0
- data/doc/plugins/presign_endpoint.md +1 -1
- data/doc/plugins/remote_url.md +19 -4
- data/doc/plugins/validation.md +83 -0
- data/doc/processing.md +149 -133
- data/doc/refile.md +68 -63
- data/doc/release_notes/3.0.0.md +835 -0
- data/doc/securing_uploads.md +56 -36
- data/doc/storage/s3.md +2 -2
- data/doc/testing.md +104 -120
- data/doc/upgrading_to_3.md +538 -0
- data/doc/validation.md +48 -87
- data/lib/shrine.rb +7 -4
- data/lib/shrine/attacher.rb +16 -6
- data/lib/shrine/plugins/activerecord.rb +33 -14
- data/lib/shrine/plugins/atomic_helpers.rb +1 -1
- data/lib/shrine/plugins/backgrounding.rb +23 -89
- data/lib/shrine/plugins/data_uri.rb +13 -2
- data/lib/shrine/plugins/derivation_endpoint.rb +7 -11
- data/lib/shrine/plugins/derivatives.rb +44 -20
- data/lib/shrine/plugins/download_endpoint.rb +26 -0
- data/lib/shrine/plugins/form_assign.rb +6 -3
- data/lib/shrine/plugins/keep_files.rb +2 -2
- data/lib/shrine/plugins/mirroring.rb +62 -22
- data/lib/shrine/plugins/model.rb +2 -2
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/remote_url.rb +25 -10
- data/lib/shrine/plugins/remove_invalid.rb +1 -1
- data/lib/shrine/plugins/sequel.rb +39 -20
- data/lib/shrine/plugins/validation.rb +3 -0
- data/lib/shrine/storage/s3.rb +16 -1
- data/lib/shrine/uploaded_file.rb +1 -0
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +1 -1
- metadata +12 -7
- data/doc/migrating_storage.md +0 -76
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/attacher_options.rb +0 -55
data/doc/advantages.md
CHANGED
@@ -39,12 +39,9 @@ Shrine.plugin :hanami # https://github.com/katafrakt/hanami-shrine
|
|
39
39
|
|
40
40
|
## Simplicity
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
There are no `CarrierWave::Uploader::Base` and `Paperclip::Attachment` [god
|
47
|
-
objects], Shrine has several core classes each with clear responsibilities:
|
42
|
+
Where some popular file attachment libraries have [god objects]
|
43
|
+
(`CarrierWave::Uploader::Base` and `Paperclip::Attachment`), Shrine has several
|
44
|
+
core classes, each with a clear set of responsibilities:
|
48
45
|
|
49
46
|
* Storage classes encapsulate file operations for the underlying service
|
50
47
|
* `Shrine` handles uploads and manages plugins
|
@@ -59,14 +56,16 @@ photo.image.uploader #=> #<Shrine>
|
|
59
56
|
photo.image_attacher #=> #<Shrine::Attacher>
|
60
57
|
```
|
61
58
|
|
62
|
-
|
63
|
-
|
59
|
+
The attachment functionality is decoupled from persistence and storage, which
|
60
|
+
makes it much easier to reason about. Also, special care was taken to make
|
61
|
+
integrating new storages and ORMs possible with minimal amount of code.
|
64
62
|
|
65
63
|
## Modularity
|
66
64
|
|
67
65
|
Shrine uses a [plugin system] that allows you to pick and choose the features
|
68
|
-
that you want. Moreover, you'
|
69
|
-
|
66
|
+
that you want. Moreover, you'll only be loading code for the features you've
|
67
|
+
selected, which means that Shrine will generally much faster than the
|
68
|
+
alternatives.
|
70
69
|
|
71
70
|
```rb
|
72
71
|
Shrine.plugin :instrumentation
|
@@ -77,11 +76,9 @@ require "shrine/plugins/instrumentation"
|
|
77
76
|
Shrine.plugin Shrine::Plugins::Instrumentation
|
78
77
|
```
|
79
78
|
|
80
|
-
Shrine
|
81
|
-
low
|
82
|
-
flow.
|
83
|
-
Storage provides, you can ditch the Shrine's attachment implementation and use
|
84
|
-
uploaders and uploaded files that are decoupled from attachment:
|
79
|
+
Shrine recommends a certain type of attachment flow, but it still offers good
|
80
|
+
low-level abstractions that give you the flexibility to build your own
|
81
|
+
attachment flow.
|
85
82
|
|
86
83
|
```rb
|
87
84
|
uploaded_file = ImageUploader.upload(image, :store) # metadata extraction, upload location generation
|
@@ -101,8 +98,8 @@ uploaded_file.delete
|
|
101
98
|
|
102
99
|
Shrine is very diligent when it comes to dependencies. It has two mandatory
|
103
100
|
dependencies – [Down] and [ContentDisposition] – which are loaded only by
|
104
|
-
components that need them. Some Shrine plugins require additional
|
105
|
-
but you only need to load them if you're using those plugins.
|
101
|
+
components that need them. Some Shrine plugins also require additional
|
102
|
+
dependencies, but you only need to load them if you're using those plugins.
|
106
103
|
|
107
104
|
Moreover, Shrine often gives you the ability choose between multiple
|
108
105
|
alternative dependencies for doing the same task. For example, the
|
@@ -117,33 +114,36 @@ Shrine.plugin :store_dimensions, analyzer: :mini_magick
|
|
117
114
|
```
|
118
115
|
|
119
116
|
This approach gives you control over your dependencies by allowing you to
|
120
|
-
choose the combination that best
|
117
|
+
choose the combination that best suits your needs.
|
121
118
|
|
122
119
|
## Inheritance
|
123
120
|
|
124
121
|
Shrine is designed to handle any types of files. If you're accepting uploads of
|
125
122
|
multiple types of files, such as videos and images, chances are that the logic
|
126
|
-
for handling them will
|
123
|
+
for handling them will differ:
|
127
124
|
|
128
125
|
* small images can be processed on-the-fly, but large files should be processed in a background job
|
129
|
-
*
|
130
|
-
*
|
126
|
+
* you might want to store different files to different storage services (images, documents, audios, videos)
|
127
|
+
* extracting metadata might require different tools depending on the filetype
|
131
128
|
|
132
129
|
With Shrine you can create isolated uploaders for each type of file. Plugins
|
133
|
-
that you want to be applied to
|
130
|
+
that you want to be applied to all uploaders can be applied globally, while
|
134
131
|
other plugins would be loaded only for a specific uploader.
|
135
132
|
|
136
133
|
```rb
|
134
|
+
# loaded for all plugins
|
137
135
|
Shrine.plugin :activerecord
|
138
136
|
Shrine.plugin :instrumentation
|
139
137
|
```
|
140
138
|
```rb
|
141
139
|
class ImageUploader < Shrine
|
140
|
+
# loaded only for ImageUploader
|
142
141
|
plugin :store_dimensions
|
143
142
|
end
|
144
143
|
```
|
145
144
|
```rb
|
146
145
|
class VideoUploader < Shrine
|
146
|
+
# loaded only for VideoUploader
|
147
147
|
plugin :default_storage, store: :vimeo
|
148
148
|
end
|
149
149
|
```
|
@@ -151,31 +151,28 @@ end
|
|
151
151
|
## Processing
|
152
152
|
|
153
153
|
Most file attachment libraries give you the ability to process files either "on
|
154
|
-
|
155
|
-
Storage).
|
156
|
-
|
157
|
-
|
158
|
-
transcoding,
|
154
|
+
attachment" (Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active
|
155
|
+
Storage). However, you should ideally be able to choose both, because both
|
156
|
+
approaches have their pros and cons. For example, on-the-fly processing is only
|
157
|
+
suitable for fast processing (image thumbnails, document previews), longer
|
158
|
+
running processing should be moved into a background job (video transcoding,
|
159
|
+
raw images).
|
159
160
|
|
160
161
|
Shrine is the first file attachment library that has support for both
|
161
|
-
processing on
|
162
|
-
can choose to either generate a set of pre-defined image thumbnails in a
|
162
|
+
processing on attachment and on-the-fly. So, if you're handling image uploads,
|
163
|
+
you can choose to either generate a set of pre-defined image thumbnails in a
|
163
164
|
background job:
|
164
165
|
|
165
166
|
```rb
|
166
167
|
class ImageUploader < Shrine
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
versions[:small] = pipeline.resize_to_limit!(300, 300)
|
176
|
-
end
|
177
|
-
|
178
|
-
versions
|
168
|
+
Attacher.derivatives_processor do |original|
|
169
|
+
magick = ImageProcessing::MiniMagick.source(original)
|
170
|
+
|
171
|
+
{
|
172
|
+
large: magick.resize_to_limit!(800, 800),
|
173
|
+
medium: magick.resize_to_limit!(500, 500),
|
174
|
+
small: magick.resize_to_limit!(300, 300),
|
175
|
+
}
|
179
176
|
end
|
180
177
|
end
|
181
178
|
```
|
@@ -206,11 +203,11 @@ Many file attachment libraries, such as CarrierWave, Paperclip, Dragonfly and
|
|
206
203
|
Refile, implement their own image processing macros. Instead of creating
|
207
204
|
yet another in-house implementation, the **[ImageProcessing]** gem was created.
|
208
205
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
206
|
+
While the ImageProcessing gem was created for Shrine, it's completely generic
|
207
|
+
and can be used standalone or with any other file upload library (e.g. Active
|
208
|
+
Storage 6+ uses it). It takes care of many details for you, such as [auto
|
209
|
+
orienting] the input image and [sharpening] the thumbnails after they are
|
210
|
+
resized.
|
214
211
|
|
215
212
|
```rb
|
216
213
|
require "image_processing"
|
@@ -234,17 +231,11 @@ The `ImageProcessing::Vips` backend implements the same API as
|
|
234
231
|
`ImageProcessing::MiniMagick`, so you can easily swap one for the other.
|
235
232
|
|
236
233
|
```rb
|
237
|
-
require "image_processing/mini_magick"
|
238
234
|
require "image_processing/vips"
|
239
|
-
require "open-uri"
|
240
235
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
#=> 1.0s
|
245
|
-
|
246
|
-
ImageProcessing::Vips.resize_to_fit(800, 800).call(original)
|
247
|
-
#=> 0.2s (5x faster)
|
236
|
+
ImageProcessing::Vips
|
237
|
+
.source(image)
|
238
|
+
.resize_to_limit!(400, 400)
|
248
239
|
```
|
249
240
|
|
250
241
|
### Other processors
|
@@ -253,20 +244,20 @@ Both processing "on upload" and "on-the-fly" work in a way that you define a
|
|
253
244
|
Ruby block, which accepts a source file and is expected to return a processed
|
254
245
|
file. How you're going to do the processing is entirely up to you.
|
255
246
|
|
256
|
-
This allows you to use any tool you want. For example, you could
|
257
|
-
|
247
|
+
This allows you to use any tool you want. For example, you could implement
|
248
|
+
video transcoding:
|
258
249
|
|
259
250
|
```rb
|
260
251
|
class VideoUploader < Shrine
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
.resize_to_limit!(width, height)
|
252
|
+
Attacher.derivatives_processor do |original|
|
253
|
+
transcoded = Tempfile.new ["transcoded", ".mp4"]
|
254
|
+
screenshot = Tempfile.new ["screenshot", ".jpg"]
|
265
255
|
|
266
|
-
|
267
|
-
|
256
|
+
movie = FFMPEG::Movie.new(original.path)
|
257
|
+
movie.transcode(transcoded.path)
|
258
|
+
movie.screenshot(screenshot.path)
|
268
259
|
|
269
|
-
|
260
|
+
{ transcoded: transcoded, screenshot: screenshot }
|
270
261
|
end
|
271
262
|
end
|
272
263
|
```
|
@@ -291,7 +282,7 @@ photo.image.metadata #=>
|
|
291
282
|
# }
|
292
283
|
```
|
293
284
|
|
294
|
-
For common metadata
|
285
|
+
For common metadata you can use the built-in [validators][validation_helpers],
|
295
286
|
but you can also [validate any custom metadata][custom validations].
|
296
287
|
|
297
288
|
```rb
|
@@ -299,11 +290,11 @@ class DocumentUploader < Shrine
|
|
299
290
|
Attacher.validate do
|
300
291
|
# validation macros
|
301
292
|
validate_max_size 10*1024*1024
|
302
|
-
|
293
|
+
validate_mime_type %W[application/pdf]
|
303
294
|
|
304
295
|
# custom validations
|
305
|
-
if
|
306
|
-
errors << "
|
296
|
+
if file["page_count"] > 30
|
297
|
+
errors << "must not have more than 30 pages"
|
307
298
|
end
|
308
299
|
end
|
309
300
|
end
|
@@ -317,6 +308,21 @@ feature in mind from day one. It is supported via the
|
|
317
308
|
[`backgrounding`][backgrounding] plugin and can be used with [any backgrounding
|
318
309
|
library][backgrounding libraries].
|
319
310
|
|
311
|
+
```rb
|
312
|
+
Shrine::Attacher.promote_block do
|
313
|
+
PromoteJob.perform_later(self.class, record, name, file_data)
|
314
|
+
end
|
315
|
+
```
|
316
|
+
```rb
|
317
|
+
class PromoteJob < ActiveJob::Base
|
318
|
+
def perform(attacher_class, record, name, file_data)
|
319
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
320
|
+
attacher.create_derivatives # perform processing
|
321
|
+
attacher.atomic_promote
|
322
|
+
end
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
320
326
|
## Direct Uploads
|
321
327
|
|
322
328
|
Shrine doesn't come with a plug-and-play JavaScript solution for client-side
|
@@ -350,24 +356,25 @@ memory usage.
|
|
350
356
|
Alternatively, you can have [resumable multipart uploads directly to
|
351
357
|
S3][uppy-s3_multipart].
|
352
358
|
|
353
|
-
##
|
359
|
+
## Summary
|
360
|
+
|
361
|
+
Shrine is general purpose, it can integrate with any web framework and any
|
362
|
+
database library. It has core classes with clearly defined responsibilities,
|
363
|
+
which provide both higher and lower level abstractions. The functionality is
|
364
|
+
very modular, you can pick and choose features that you need.
|
354
365
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
flood the main storage.
|
366
|
+
With Shrine you can process both on attachment and on-the-fly, depending on
|
367
|
+
what is more suitable for your requirements. Processing is just a functional
|
368
|
+
transformation, which makes it easier to use the processing tool of your
|
369
|
+
choice. You can also move processing into a background job.
|
360
370
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
than relying on the `Content-Type` request header), preventing exploits like
|
365
|
-
[ImageTragick].
|
371
|
+
Shrine automatically extracts metadata from the main file and any processed
|
372
|
+
files. In addition to built-in metadata you can also extract any custom
|
373
|
+
metadata. Any extracted metadata can be validated on attachment.
|
366
374
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
when it realizes it's too large.
|
375
|
+
Finally, Shrine integrates with Uppy, a full-featured JavaScript file upload
|
376
|
+
library. It allows you to do direct uploads to your app or to S3. For large
|
377
|
+
files you can also make the uploads resumable.
|
371
378
|
|
372
379
|
[Paperclip]: https://github.com/thoughtbot/paperclip
|
373
380
|
[CarrierWave]: https://github.com/carrierwaveuploader/carrierwave
|
@@ -397,7 +404,6 @@ when it realizes it's too large.
|
|
397
404
|
[ruby-vips]: https://github.com/libvips/ruby-vips
|
398
405
|
[god objects]: https://en.wikipedia.org/wiki/God_object
|
399
406
|
[ImageMagick]: https://www.imagemagick.org
|
400
|
-
[refile-mini_magick]: https://github.com/refile/refile-mini_magick
|
401
407
|
[ImageProcessing]: https://github.com/janko/image_processing
|
402
408
|
[auto orienting]: https://www.imagemagick.org/script/command-line-options.php#auto-orient
|
403
409
|
[sharpening]: https://photography.tutsplus.com/tutorials/what-is-image-sharpening--cms-26627
|
@@ -423,10 +429,6 @@ when it realizes it's too large.
|
|
423
429
|
[tus implementations]: https://tus.io/implementations.html
|
424
430
|
[tus-ruby-server]: https://github.com/janko/tus-ruby-server
|
425
431
|
[shrine-tus]: https://github.com/shrinerb/shrine-tus
|
426
|
-
[ImageTragick]: https://imagetragick.com
|
427
432
|
[uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
|
428
|
-
[OWASP]: https://www.owasp.org/index.php/Unrestricted_File_Upload
|
429
|
-
[image_optim]: https://github.com/toy/image_optim
|
430
433
|
[validation_helpers]: /doc/plugins/validation_helpers.md#readme
|
431
434
|
[custom validations]: /doc/validation.md#custom-validations
|
432
|
-
[Down max size]: https://github.com/janko/down#maximum-size
|
data/doc/attacher.md
CHANGED
@@ -1,283 +1,453 @@
|
|
1
1
|
# Using Attacher
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
This guide explains what is `Shrine::Attacher` and how to use it.
|
4
|
+
|
5
|
+
## Contents
|
6
|
+
|
7
|
+
* [Introduction](#introduction)
|
8
|
+
* [Storage](#storage)
|
9
|
+
* [Attaching](#attaching)
|
10
|
+
- [Attaching cached](#attaching-cached)
|
11
|
+
- [Attaching stored](#attaching-stored)
|
12
|
+
- [Uploading](#uploading)
|
13
|
+
- [Changes](#changes)
|
14
|
+
* [Finalizing](#finalizing)
|
15
|
+
- [Promoting](#promoting)
|
16
|
+
- [Replacing](#replacing)
|
17
|
+
* [Retrieving](#retreiving)
|
18
|
+
- [File](#file)
|
19
|
+
- [Attached](#attached)
|
20
|
+
- [URL](#url)
|
21
|
+
- [Data](#data)
|
22
|
+
* [Deleting](#deleting)
|
23
|
+
* [Context](#context)
|
24
|
+
|
25
|
+
## Introduction
|
26
|
+
|
27
|
+
The attachment logic is handled by a `Shrine::Attacher` object. The
|
28
|
+
`Shrine::Attachment` module simply provides a convenience layer around a
|
29
|
+
`Shrine::Attacher` object, which can be accessed via the `#<name>_attacher`
|
30
|
+
attribute.
|
7
31
|
|
8
32
|
```rb
|
9
|
-
class Photo
|
10
|
-
include ImageUploader::Attachment
|
33
|
+
class Photo
|
34
|
+
include ImageUploader::Attachment(:image)
|
11
35
|
end
|
12
36
|
```
|
37
|
+
```rb
|
38
|
+
photo = Photo.new
|
39
|
+
photo.image_attacher #=> #<ImageUploader::Attacher>
|
40
|
+
```
|
13
41
|
|
14
|
-
|
15
|
-
explicitness, or you need more control, you can achieve the same behaviour
|
16
|
-
using the `Shrine::Attacher` object, which is what the attachment interface
|
17
|
-
uses under the hood.
|
42
|
+
We can also instantiate the same `Shrine::Attacher` object directly:
|
18
43
|
|
19
44
|
```rb
|
20
|
-
attacher = ImageUploader::Attacher.
|
21
|
-
attacher.
|
22
|
-
attacher.
|
45
|
+
attacher = ImageUploader::Attacher.from_model(photo, :image)
|
46
|
+
attacher.file # called by `photo.image`
|
47
|
+
attacher.url # called by `photo.image_url`
|
23
48
|
```
|
24
49
|
|
25
|
-
The
|
26
|
-
|
50
|
+
The `model`, `entity`, and `column` plugins provide additional
|
51
|
+
`Shrine::Attacher` methods (such as `Shrine::Attacher.from_model` we see
|
52
|
+
above), but in this guide we'll focus only on the core `Shrine::Attacher`
|
53
|
+
methods.
|
54
|
+
|
55
|
+
So, we'll assume a `Shrine::Attacher` object not backed by any model/entity:
|
27
56
|
|
28
57
|
```rb
|
29
|
-
attacher
|
58
|
+
attacher = Shrine::Attacher.new
|
30
59
|
```
|
31
60
|
|
32
|
-
##
|
61
|
+
## Storage
|
33
62
|
|
34
|
-
|
63
|
+
By default, an `Attacher` will use the `:cache` storage as the **temporary**
|
64
|
+
storage, and the `:store` storage as the **permanent** storage.
|
35
65
|
|
36
66
|
```rb
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
67
|
+
Shrine.storages = {
|
68
|
+
cache: Shrine::Storage::Memory.new,
|
69
|
+
store: Shrine::Storage::Memory.new,
|
70
|
+
}
|
71
|
+
```
|
72
|
+
```rb
|
73
|
+
attacher = Shrine::Attacher.new
|
74
|
+
attacher.cache_key #=> :cache
|
75
|
+
attacher.store_key #=> :store
|
41
76
|
```
|
42
77
|
|
43
|
-
|
44
|
-
also tell it to use different temporary and permanent storage:
|
78
|
+
We can also change the default storage:
|
45
79
|
|
46
80
|
```rb
|
47
|
-
|
81
|
+
attacher = Shrine::Attacher.new(cache: :other_cache, store: :other_store)
|
82
|
+
attacher.cache_key #=> :other_cache
|
83
|
+
attacher.store_key #=> :other_store
|
84
|
+
```
|
48
85
|
|
49
|
-
|
86
|
+
You can also change default attacher options on the `Shrine::Attachment`
|
87
|
+
module:
|
50
88
|
|
51
|
-
|
52
|
-
|
53
|
-
|
89
|
+
```rb
|
90
|
+
class Photo
|
91
|
+
include ImageUploader::Attachment(:image, cache: :other_cache, store: :other_store)
|
92
|
+
end
|
54
93
|
```
|
55
94
|
|
56
|
-
|
95
|
+
The `Attacher#cache` and `Attacher#store` methods will retrieve corresponding
|
96
|
+
uploaders:
|
57
97
|
|
58
98
|
```rb
|
59
|
-
|
60
|
-
|
61
|
-
|
99
|
+
attacher.cache #=> #<MyUploader @storage_key=:cache>
|
100
|
+
attacher.store #=> #<MyUploader @storage_key=:store>
|
101
|
+
```
|
102
|
+
|
103
|
+
## Attaching
|
104
|
+
|
105
|
+
### Attaching cached
|
106
|
+
|
107
|
+
For attaching files submitted via a web form, `Attacher#assign` can be used:
|
108
|
+
|
109
|
+
```rb
|
110
|
+
attacher.assign(file)
|
62
111
|
```
|
63
112
|
|
64
|
-
|
65
|
-
for more details.
|
113
|
+
If given a raw file, it will upload it to temporary storage:
|
66
114
|
|
67
|
-
|
115
|
+
```rb
|
116
|
+
attacher.assign(file)
|
117
|
+
attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg", @storage_key=:cache>
|
118
|
+
```
|
68
119
|
|
69
|
-
|
70
|
-
cached file in form of a JSON string, and assigns the cached result to record's
|
71
|
-
`<attachment>_data` attribute.
|
120
|
+
If given cached file data (JSON or Hash), it will set the cached file:
|
72
121
|
|
73
122
|
```rb
|
74
|
-
|
75
|
-
attacher.
|
123
|
+
attacher.assign('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
|
124
|
+
attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg", @storage_key=:cache>
|
125
|
+
```
|
126
|
+
|
127
|
+
If given an empty string, it will no-op:
|
76
128
|
|
77
|
-
|
78
|
-
attacher.assign(
|
129
|
+
```rb
|
130
|
+
attacher.assign("") # no-op
|
79
131
|
```
|
80
132
|
|
81
|
-
|
82
|
-
forwarded to `Shrine#upload`. This allows you to do things like overriding
|
83
|
-
metadata, setting upload location, or passing upload options:
|
133
|
+
If given `nil`, it will clear the attached file:
|
84
134
|
|
85
135
|
```rb
|
86
|
-
attacher.
|
87
|
-
|
88
|
-
|
89
|
-
upload_options: { acl: "public-read" }
|
136
|
+
attacher.file #=> <Shrine::UploadedFile>
|
137
|
+
attacher.assign(nil)
|
138
|
+
attacher.file #=> nil
|
90
139
|
```
|
91
140
|
|
92
|
-
|
93
|
-
|
141
|
+
This plays nicely with the recommended HTML form fields for the attachment. If
|
142
|
+
you're not using the `hidden` form field (and therefore don't need empty
|
143
|
+
strings to be handled), you can also use `Attacher#attach_cached`:
|
94
144
|
|
95
145
|
```rb
|
96
|
-
|
97
|
-
|
146
|
+
# uploads file to cache
|
147
|
+
attacher.attach_cached(file)
|
148
|
+
|
149
|
+
# sets cached file
|
150
|
+
attacher.attach_cached('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
|
151
|
+
attacher.attach_cached("id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... })
|
152
|
+
attacher.attach_cached(id: "asdf.jpg", storage: "cache", metadata: { ... })
|
98
153
|
|
99
|
-
|
154
|
+
# unsets attached file
|
155
|
+
attacher.attach_cached(nil)
|
100
156
|
```
|
101
157
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
to permanent storage:
|
158
|
+
### Attaching stored
|
159
|
+
|
160
|
+
The `Attacher#attach` method uploads a given file to permanent storage:
|
106
161
|
|
107
162
|
```rb
|
108
|
-
|
109
|
-
attacher.
|
163
|
+
attacher.attach(file)
|
164
|
+
attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg" @storage=:store>
|
110
165
|
```
|
111
166
|
|
112
|
-
|
167
|
+
This method is useful when attaching files from scripts, where validation
|
168
|
+
doesn't need to be performed, and where temporary storage can be skipped.
|
113
169
|
|
114
|
-
|
115
|
-
a `Shrine::UploadedFile` object from it.
|
170
|
+
You can specify a different destination storage with the `:storage` option:
|
116
171
|
|
117
172
|
```rb
|
118
|
-
attacher.
|
173
|
+
attacher.attach(file, storage: :other_store)
|
174
|
+
attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg" @storage=:other_store>
|
119
175
|
```
|
120
176
|
|
121
|
-
|
122
|
-
|
177
|
+
Any additional options passed to `Attacher#attach`, `Attacher#attach_cached`
|
178
|
+
and `Attacher#assign` are forwarded to the uploader:
|
123
179
|
|
124
180
|
```rb
|
125
|
-
attacher.
|
181
|
+
attacher.attach(file, metadata: { "foo" => "bar" }) # adding metadata
|
182
|
+
attacher.attach(file, upload_options: { acl: "private" }) # setting upload options
|
183
|
+
attacher.attach(file, location: "path/to/file") # setting upload location
|
126
184
|
```
|
127
185
|
|
128
|
-
|
129
|
-
|
186
|
+
### Uploading
|
187
|
+
|
188
|
+
If you want to upload a file to without attaching it, you can use
|
189
|
+
`Attacher#upload`:
|
130
190
|
|
131
191
|
```rb
|
132
|
-
|
133
|
-
attacher.
|
192
|
+
attacher.upload(file) #=> #<Shrine::UploadedFile @storage=:store ...>
|
193
|
+
attacher.upload(file, :cache) #=> #<Shrine::UploadedFile @storage=:cache ...>
|
194
|
+
attacher.upload(file, :other_store) #=> #<Shrine::UploadedFile @storage=:other_store ...>
|
134
195
|
```
|
135
196
|
|
136
|
-
|
197
|
+
This is useful if you want to attacher [context](#context) such as `:record`
|
198
|
+
and `:name` to be automatically passed to the uploader.
|
137
199
|
|
138
|
-
|
139
|
-
no file is attached.
|
200
|
+
You can also pass additional options for `Shrine#upload`:
|
140
201
|
|
141
202
|
```rb
|
142
|
-
attacher.
|
203
|
+
attacher.upload(file, metadata: { "foo" => "bar" }) # adding metadata
|
204
|
+
attacher.upload(file, upload_options: { acl: "private" }) # setting upload options
|
205
|
+
attacher.upload(file, location: "path/to/file") # setting upload location
|
143
206
|
```
|
144
207
|
|
145
|
-
|
208
|
+
### Changes
|
209
|
+
|
210
|
+
When a new file is attached, calling [`Attacher#finalize`](#finalization) will
|
211
|
+
perform additional actions such as promotion and deleting any previous file.
|
212
|
+
It will also trigger [validation].
|
146
213
|
|
147
|
-
You can
|
148
|
-
stored.
|
214
|
+
You can check whether a new file has been attached with `Attacher#changed?`:
|
149
215
|
|
150
216
|
```rb
|
151
|
-
attacher.
|
152
|
-
attacher.stored?
|
217
|
+
attacher.changed? #=> true
|
153
218
|
```
|
154
219
|
|
155
|
-
|
220
|
+
You can use `Attacher#change` to attach an `UploadedFile` object as is:
|
156
221
|
|
157
|
-
|
158
|
-
|
222
|
+
```rb
|
223
|
+
uploaded_file #=> #<Shrine::UploadedFile>
|
224
|
+
attacher.change(uploaded_file)
|
225
|
+
attacher.file #=> #<Shrine::UploadedFile> (same object)
|
226
|
+
attacher.changed? #=> true
|
227
|
+
|
228
|
+
```
|
229
|
+
|
230
|
+
If you want to attach a file without triggering dirty tracking or validation,
|
231
|
+
you can use `Attacher#set`:
|
159
232
|
|
160
233
|
```rb
|
161
|
-
|
162
|
-
attacher.
|
234
|
+
uploaded_file #=> #<Shrine::UploadedFile>
|
235
|
+
attacher.set(uploaded_file)
|
236
|
+
attacher.file #=> #<Shrine::UploadedFile> (same object)
|
237
|
+
attacher.changed? #=> false
|
163
238
|
```
|
164
239
|
|
165
|
-
##
|
240
|
+
## Finalizing
|
166
241
|
|
167
|
-
After the
|
168
|
-
|
169
|
-
|
242
|
+
After the file is attached (with `Attacher#assign`, `Attacher#attach_cached`,
|
243
|
+
or `Attacher#attach`), and data has been validated, the attachment can be
|
244
|
+
"finalized":
|
170
245
|
|
171
246
|
```rb
|
172
|
-
# Replaces previous attachment and replaces new
|
173
247
|
attacher.finalize
|
174
248
|
```
|
175
249
|
|
176
|
-
|
177
|
-
|
178
|
-
`Shrine::UploadedFile` to permanent storage, and swaps it with the current
|
179
|
-
attachment, unless a new file was attached in the meanwhile.
|
250
|
+
The `Attacher#finalize` method performs [promoting](#promoting) and
|
251
|
+
[replacing](#replacing). It also clears dirty tracking:
|
180
252
|
|
181
253
|
```rb
|
182
|
-
|
183
|
-
attacher.
|
254
|
+
attacher.changed? #=> true
|
255
|
+
attacher.finalize
|
256
|
+
attacher.changed? #=> false
|
184
257
|
```
|
185
258
|
|
186
|
-
|
187
|
-
|
259
|
+
### Promoting
|
260
|
+
|
261
|
+
`Attacher#finalize` checks if the attached file has been uploaded to temporary
|
262
|
+
storage, and in this case uploads it to permanent storage.
|
263
|
+
|
264
|
+
```rb
|
265
|
+
attacher.attach_cached(io)
|
266
|
+
attacher.finalize # uploads attached file to permanent storage
|
267
|
+
attacher.file #=> #<Shrine::UploadedFile @storage=:store ...>
|
268
|
+
```
|
188
269
|
|
189
|
-
|
190
|
-
|
191
|
-
things like override metadata, set upload location, or pass upload options:
|
270
|
+
Internally it calls `Attacher#promote_cached`, which you can call directly if
|
271
|
+
you want to pass any promote options:
|
192
272
|
|
193
273
|
```rb
|
194
|
-
attacher.
|
195
|
-
|
196
|
-
|
197
|
-
upload_options: { acl: "public-read" }
|
274
|
+
attacher.file #=> #<Shrine::UploadedFile @storage=:cache ...>
|
275
|
+
attacher.promote_cached # uploads attached file to permanent storage if new and cached
|
276
|
+
attacher.file #=> #<Shrine::UploadedFile @storage=:store ...>
|
198
277
|
```
|
199
278
|
|
200
|
-
|
201
|
-
|
202
|
-
hasn't changed (if the `backgrounding` plugin is loaded).
|
279
|
+
You can also call `Attacher#promote` if you want to upload attached file to
|
280
|
+
permanent storage, regardless of whether it's cached or newly attached:
|
203
281
|
|
204
282
|
```rb
|
205
|
-
attacher.
|
283
|
+
attacher.promote
|
206
284
|
```
|
207
285
|
|
208
|
-
|
286
|
+
Any options passed to `Attacher#promote_cached` or `Attacher#promote` will be
|
287
|
+
forwarded to `Shrine#upload`.
|
209
288
|
|
210
|
-
|
289
|
+
### Replacing
|
211
290
|
|
212
|
-
|
213
|
-
files in the background, and the corresponding methods are prefixed with `_`:
|
291
|
+
`Attacher#finalize` also deletes the previous attached file if any:
|
214
292
|
|
215
293
|
```rb
|
216
|
-
|
217
|
-
|
218
|
-
|
294
|
+
previous_file = attacher.file
|
295
|
+
|
296
|
+
attacher.attach(io)
|
297
|
+
attacher.finalize
|
298
|
+
|
299
|
+
previous_file.exists? #=> false
|
219
300
|
```
|
301
|
+
|
302
|
+
Internally it calls `Attacher#destroy_previous` to do this:
|
303
|
+
|
220
304
|
```rb
|
221
|
-
attacher.
|
222
|
-
attacher._delete(uploaded_file) # calls the registered `Attacher.delete` block
|
305
|
+
attacher.destroy_previous
|
223
306
|
```
|
224
307
|
|
225
|
-
|
308
|
+
## Retrieving
|
226
309
|
|
227
|
-
|
310
|
+
### File
|
311
|
+
|
312
|
+
The `Attacher#file` is used to retrieve the attached file:
|
313
|
+
|
314
|
+
```rb
|
315
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
316
|
+
```
|
228
317
|
|
229
|
-
|
230
|
-
default it will hold `:record` and `:name`:
|
318
|
+
If no file is attached, `Attacher#file` returns nil:
|
231
319
|
|
232
320
|
```rb
|
233
|
-
attacher.
|
321
|
+
attacher.file #=> nil
|
322
|
+
```
|
323
|
+
|
324
|
+
If you want to assert a file is attached, you can use `Attacher#file!`:
|
325
|
+
|
326
|
+
```rb
|
327
|
+
attacher.file! #~> Shrine::Error: no file is attached
|
328
|
+
```
|
329
|
+
|
330
|
+
### Attached
|
331
|
+
|
332
|
+
You can also check whether a file is attached with `Attacher#attached?`:
|
333
|
+
|
334
|
+
```rb
|
335
|
+
attacher.attached? # returns whether file is attached
|
336
|
+
```
|
337
|
+
|
338
|
+
If you want to check to which storage a file is uploaded to, you can use
|
339
|
+
`Attacher#cached?` and `Attacher#stored?`:
|
340
|
+
|
341
|
+
```rb
|
342
|
+
attacher.attach(io)
|
343
|
+
attacher.stored? #=> true (checks current file)
|
344
|
+
attacher.stored?(attacher.file) #=> true (checks given file)
|
345
|
+
```
|
346
|
+
```rb
|
347
|
+
attacher.attach_cached(io)
|
348
|
+
attacher.cached? #=> true (checks current file)
|
349
|
+
attacher.cached?(attacher.file) #=> true (checks given file)
|
350
|
+
```
|
351
|
+
|
352
|
+
### URL
|
353
|
+
|
354
|
+
The attached file URL can be retrieved with `Attacher#url`:
|
355
|
+
|
356
|
+
```rb
|
357
|
+
attacher.url #=> "https://example.com/file.jpg"
|
358
|
+
```
|
359
|
+
|
360
|
+
If no file is attached, `Attacher#url` returns `nil`:
|
361
|
+
|
362
|
+
```rb
|
363
|
+
attacher.url #=> nil
|
364
|
+
```
|
365
|
+
|
366
|
+
### Data
|
367
|
+
|
368
|
+
You can retrieve plain attached file data with `Attacher#data`:
|
369
|
+
|
370
|
+
```rb
|
371
|
+
attacher.data #=>
|
234
372
|
# {
|
235
|
-
#
|
236
|
-
#
|
373
|
+
# "id" => "abc123.jpg",
|
374
|
+
# "storage" => "store",
|
375
|
+
# "metadata" => {
|
376
|
+
# "size" => 223984,
|
377
|
+
# "filename" => "nature.jpg",
|
378
|
+
# "mime_type" => "image/jpeg",
|
379
|
+
# }
|
237
380
|
# }
|
238
381
|
```
|
239
382
|
|
240
|
-
|
241
|
-
|
383
|
+
This data can be stored somewhere, and later the attached file can be loaded
|
384
|
+
from it:
|
242
385
|
|
243
386
|
```rb
|
244
|
-
|
387
|
+
# new attacher
|
388
|
+
attacher = Shrine::Attacher.from_data(data)
|
389
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
390
|
+
|
391
|
+
# existing attacher
|
392
|
+
attacher.file #=> nil
|
393
|
+
attacher.load_data(data)
|
394
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
395
|
+
```
|
396
|
+
|
397
|
+
Internally `Attacher#uploaded_file` is used to convert uploaded file data into
|
398
|
+
a `Shrine::UploadedFile` object:
|
399
|
+
|
400
|
+
```rb
|
401
|
+
attacher.uploaded_file("id" => "...", "storage" => "...", "metadata" => { ... }) #=> #<Shrine::UploadedFile>
|
402
|
+
attacher.uploaded_file(id: "...", storage: "...", metadata: { ... }) #=> #<Shrine::UploadedFile>
|
403
|
+
attacher.uploaded_file('{"id":"...","storage":"...","metadata":{...}}') #=> #<Shrine::UploadedFile>
|
245
404
|
```
|
246
405
|
|
247
|
-
|
248
|
-
|
249
|
-
|
406
|
+
You will likely want to use a higher level abstraction for saving and loading
|
407
|
+
this data, see [`column`][column], [`entity`][entity] and [`model`][model]
|
408
|
+
plugins for more details.
|
250
409
|
|
251
|
-
##
|
410
|
+
## Deleting
|
252
411
|
|
253
|
-
|
412
|
+
The attached file can be deleted via `Attacher#destroy_attached`:
|
254
413
|
|
255
414
|
```rb
|
256
|
-
|
257
|
-
uploaded_file = uploader.upload(image) # uploads the file to `:store` storage
|
258
|
-
uploader.delete(uploaded_file) # deletes the uploaded file from `:store`
|
415
|
+
attacher.destroy_attached
|
259
416
|
```
|
260
417
|
|
261
|
-
|
262
|
-
|
263
|
-
|
418
|
+
This will not delete cached files, to not interrupt any potential
|
419
|
+
[backgrounding] that might be in process.
|
420
|
+
|
421
|
+
If you want to delete the attached file regardless of storage it's uploaded to,
|
422
|
+
you can use `Attacher#destroy`:
|
264
423
|
|
265
424
|
```rb
|
266
|
-
attacher.
|
267
|
-
# => #<Shrine::UploadedFile: @data={"storage" => "cache", ...}>
|
268
|
-
attacher.store!(file) # uploads file to permanent storage
|
269
|
-
# => #<Shrine::UploadedFile: @data={"storage" => "store", ...}>
|
270
|
-
attacher.delete!(uploaded_file) # deletes uploaded file from storage
|
425
|
+
attacher.destroy
|
271
426
|
```
|
272
427
|
|
273
|
-
|
274
|
-
|
275
|
-
`
|
428
|
+
## Context
|
429
|
+
|
430
|
+
The `Attacher#context` hash is automatically forwarded to the uploader on
|
431
|
+
`Attacher#upload`. When [`model`][model] or [`entity`][model] plugin is loaded,
|
432
|
+
this will include `:record` and `:name` values:
|
276
433
|
|
277
434
|
```rb
|
278
|
-
attacher.
|
279
|
-
attacher.
|
280
|
-
attacher.delete!(uploaded_file, foo: "bar")
|
435
|
+
attacher = Shrine::Attacher.from_model(photo, :image)
|
436
|
+
attacher.context #=> { record: #<Photo>, name: :image }
|
281
437
|
```
|
282
438
|
|
283
|
-
|
439
|
+
You can add here any other parameters you want to forward to the uploader:
|
440
|
+
|
441
|
+
```rb
|
442
|
+
attacher.context[:foo] = "bar"
|
443
|
+
```
|
444
|
+
|
445
|
+
However, it's generally better practice to pass uploader options directly to
|
446
|
+
`Attacher#assign`, `Attacher#attach`, `Attacher#promote` or any other method
|
447
|
+
that's calling `Attacher#upload`.
|
448
|
+
|
449
|
+
[validation]: /doc/plugins/validation.md#readme
|
450
|
+
[column]: /doc/plugins/column.md#readme
|
451
|
+
[entity]: /doc/plugins/entity.md#readme
|
452
|
+
[model]: /doc/plugins/model.md#readme
|
453
|
+
[backgrounding]: /doc/plugins/backgrounding.md#readme
|