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.

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/README.md +100 -106
  4. data/doc/advantages.md +90 -88
  5. data/doc/attacher.md +322 -152
  6. data/doc/carrierwave.md +105 -113
  7. data/doc/changing_derivatives.md +308 -0
  8. data/doc/changing_location.md +92 -21
  9. data/doc/changing_storage.md +107 -0
  10. data/doc/creating_plugins.md +1 -1
  11. data/doc/design.md +8 -9
  12. data/doc/direct_s3.md +3 -2
  13. data/doc/metadata.md +97 -78
  14. data/doc/multiple_files.md +3 -3
  15. data/doc/paperclip.md +89 -88
  16. data/doc/plugins/activerecord.md +3 -12
  17. data/doc/plugins/backgrounding.md +126 -100
  18. data/doc/plugins/derivation_endpoint.md +4 -5
  19. data/doc/plugins/derivatives.md +63 -32
  20. data/doc/plugins/download_endpoint.md +54 -1
  21. data/doc/plugins/entity.md +1 -0
  22. data/doc/plugins/form_assign.md +53 -0
  23. data/doc/plugins/mirroring.md +37 -16
  24. data/doc/plugins/multi_cache.md +22 -0
  25. data/doc/plugins/presign_endpoint.md +1 -1
  26. data/doc/plugins/remote_url.md +19 -4
  27. data/doc/plugins/validation.md +83 -0
  28. data/doc/processing.md +149 -133
  29. data/doc/refile.md +68 -63
  30. data/doc/release_notes/3.0.0.md +835 -0
  31. data/doc/securing_uploads.md +56 -36
  32. data/doc/storage/s3.md +2 -2
  33. data/doc/testing.md +104 -120
  34. data/doc/upgrading_to_3.md +538 -0
  35. data/doc/validation.md +48 -87
  36. data/lib/shrine.rb +7 -4
  37. data/lib/shrine/attacher.rb +16 -6
  38. data/lib/shrine/plugins/activerecord.rb +33 -14
  39. data/lib/shrine/plugins/atomic_helpers.rb +1 -1
  40. data/lib/shrine/plugins/backgrounding.rb +23 -89
  41. data/lib/shrine/plugins/data_uri.rb +13 -2
  42. data/lib/shrine/plugins/derivation_endpoint.rb +7 -11
  43. data/lib/shrine/plugins/derivatives.rb +44 -20
  44. data/lib/shrine/plugins/download_endpoint.rb +26 -0
  45. data/lib/shrine/plugins/form_assign.rb +6 -3
  46. data/lib/shrine/plugins/keep_files.rb +2 -2
  47. data/lib/shrine/plugins/mirroring.rb +62 -22
  48. data/lib/shrine/plugins/model.rb +2 -2
  49. data/lib/shrine/plugins/multi_cache.rb +27 -0
  50. data/lib/shrine/plugins/remote_url.rb +25 -10
  51. data/lib/shrine/plugins/remove_invalid.rb +1 -1
  52. data/lib/shrine/plugins/sequel.rb +39 -20
  53. data/lib/shrine/plugins/validation.rb +3 -0
  54. data/lib/shrine/storage/s3.rb +16 -1
  55. data/lib/shrine/uploaded_file.rb +1 -0
  56. data/lib/shrine/version.rb +1 -1
  57. data/shrine.gemspec +1 -1
  58. metadata +12 -7
  59. data/doc/migrating_storage.md +0 -76
  60. data/doc/regenerating_versions.md +0 -143
  61. data/lib/shrine/plugins/attacher_options.rb +0 -55
@@ -39,12 +39,9 @@ Shrine.plugin :hanami # https://github.com/katafrakt/hanami-shrine
39
39
 
40
40
  ## Simplicity
41
41
 
42
- Shrine was designed with simplicity in mind. Where other solutions favour
43
- complex class-level DSLs, Shrine chooses simple instance-level interfaces where
44
- you can write regular Ruby code.
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
- Special care was taken to make integrating new storages and ORMs possible with
63
- minimal amount of code.
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're only loading the code for features that you
69
- use, which means that Shrine will generally load very fast.
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 comes with a complete attachment functionality, but it also exposes many
81
- low level APIs that can be used for building your own customized attachment
82
- flow. For example, if you prefer the `Attachment`/`Blob` architecture Active
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 dependencies,
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 suit your needs.
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 be very different:
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
- * which storage service is most suitable might depend on the filetype (images, documents, audios, videos)
130
- * different filetypes have different metadata to extract which require different tools
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 both uploaders can be applied globally, while
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
- upload" (Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active
155
- Storage). Having only one option is not ideal, because some type of files
156
- it's more suitable to process on-the-fly (image thumbnails, document previews),
157
- while other types of files should be processed in a background job (video
158
- transcoding, raw images)
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 upload and on-the-fly. So, if you're handling image uploads, you
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
- process(:store) do |io|
168
- versions = { original: io }
169
-
170
- io.download do |original|
171
- pipeline = ImageProcessing::MiniMagick.source(original)
172
-
173
- versions[:large] = pipeline.resize_to_limit!(800, 800)
174
- versions[:medium] = pipeline.resize_to_limit!(500, 500)
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
- Even though the ImageProcessing gem was created for Shrine, it's completely
210
- generic and can be used standalone, or in any other file upload library (e.g.
211
- Active Storage uses it now as well). It takes care of many details for you,
212
- such as [auto orienting] the input image and [sharpening] the thumbnails after
213
- they are resized.
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
- original = open("https://upload.wikimedia.org/wikipedia/commons/3/36/Hopetoun_falls.jpg")
242
-
243
- ImageProcessing::MiniMagick.resize_to_fit(800, 800).call(original)
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 use the
257
- [image_optim] gem to perform additional image optimizations:
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
- derivation :thumbnail do |file, width, height|
262
- thumbnail = ImageProcessing::MiniMagick
263
- .source(file)
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
- image_optim = ImageOptim.new
267
- image_optim.optimize_image!(thumbnail.path)
256
+ movie = FFMPEG::Movie.new(original.path)
257
+ movie.transcode(transcoded.path)
258
+ movie.screenshot(screenshot.path)
268
259
 
269
- thumbnail
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 there are already [validation macros][validation_helpers],
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
- validate_mime_type_inclusion %W[application/pdf]
293
+ validate_mime_type %W[application/pdf]
303
294
 
304
295
  # custom validations
305
- if get.metadata["page_count"] > 30
306
- errors << "has too many pages (max is 30)"
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
- ## Security
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
- It's [important][OWASP] to care about security when handling file uploads, and
356
- Shrine bakes in many good practices. For starters, it uses a separate
357
- "temporary" storage for direct uploads, making it easy to periodically clear
358
- uploads that didn't end up being attached and difficult for the attacker to
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
- File processing and upload to permanent storage is done outside of a database
362
- transaction, and only after the file has been successfully validated. The
363
- `determine_mime_type` plugin determines MIME type from the file content (rather
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
- The `remote_url` plugin requires specifying a `:max_size` option, which limits
368
- the maximum allowed size of the remote file. The [Down] gem which the
369
- `remote_url` plugin uses will [terminate the download early][Down max size]
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
@@ -1,283 +1,453 @@
1
1
  # Using Attacher
2
2
 
3
- The most convenient way to use Shrine is through the model, using the interface
4
- provided by Shrine's attachment module. This way you can interact with the
5
- attachment just like with any other column attribute, and adding attachment
6
- fields to the form just works.
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 < Sequel::Model
10
- include ImageUploader::Attachment.new(:image)
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
- However, if you don't want to add additional methods on the model and prefer
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.new(photo, :image) # equivalent to `photo.image_attacher`
21
- attacher.assign(file) # equivalent to `photo.image = file`
22
- attacher.get # equivalent to `photo.image`
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 attacher will use the `<attachment>_data` attribute for storing information
26
- about the attachment.
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.data_attribute #=> :image_data
58
+ attacher = Shrine::Attacher.new
30
59
  ```
31
60
 
32
- ## Initializing
61
+ ## Storage
33
62
 
34
- The attacher object exposes the objects it uses:
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
- attacher.record #=> #<Photo>
38
- attacher.name #=> :image
39
- attacher.cache #=> #<ImageUploader @storage_key=:cache>
40
- attacher.store #=> #<ImageUploader @storage_key=:store>
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
- The attacher will automatically use `:cache` and `:store` storages, but you can
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
- ImageUploader::Attacher.new(photo, :image, cache: :other_cache, store: :other_store)
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
- # OR
86
+ You can also change default attacher options on the `Shrine::Attachment`
87
+ module:
50
88
 
51
- photo.image_attacher(cache: :other_cache, store: :other_store)
52
- photo.image = file # uploads to :other_cache storage
53
- photo.save # promotes to :other_store storage
89
+ ```rb
90
+ class Photo
91
+ include ImageUploader::Attachment(:image, cache: :other_cache, store: :other_store)
92
+ end
54
93
  ```
55
94
 
56
- You can pass the `:cache` and `:store` options via `Attachment.new` too:
95
+ The `Attacher#cache` and `Attacher#store` methods will retrieve corresponding
96
+ uploaders:
57
97
 
58
98
  ```rb
59
- class Photo < Sequel::Model
60
- include ImageUploader::Attachment.new(:image, cache: :other_cache, store: :other_store)
61
- end
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
- Note that it's not necessary to use the temporary storage, see the next section
65
- for more details.
113
+ If given a raw file, it will upload it to temporary storage:
66
114
 
67
- ## Assignment
115
+ ```rb
116
+ attacher.assign(file)
117
+ attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg", @storage_key=:cache>
118
+ ```
68
119
 
69
- The `#assign` method accepts either an IO object to be cached, or an already
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
- # uploads the `io` object to temporary storage, and writes to the data column
75
- attacher.assign(io)
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
- # writes the given cached file to the data column
78
- attacher.assign('{"id":"9260ea09d8effd.jpg","storage":"cache","metadata":{ ... }}')
129
+ ```rb
130
+ attacher.assign("") # no-op
79
131
  ```
80
132
 
81
- When assigning an IO object, any additional options passed to `#assign` will be
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.assign io,
87
- metadata: { "filename" => "myfile.txt" },
88
- location: "custom/location",
89
- upload_options: { acl: "public-read" }
136
+ attacher.file #=> <Shrine::UploadedFile>
137
+ attacher.assign(nil)
138
+ attacher.file #=> nil
90
139
  ```
91
140
 
92
- If you're attaching a cached file and want to override its metadata before
93
- assignment, you can do it like so:
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
- cached_file = Shrine.uploaded_file('{"id":"9260ea09d8effd.jpg","storage":"cache","metadata":{ ... }}')
97
- cached_file.metadata["filename"] = "myfile.txt"
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
- attacher.assign(cached_file.to_json)
154
+ # unsets attached file
155
+ attacher.attach_cached(nil)
100
156
  ```
101
157
 
102
- For security reasons `#assign` doesn't accept files uploaded to permanent
103
- storage, but you can use `#set` to attach any `Shrine::UploadedFile` object.
104
- You can use this to skip temporary storage altogether and upload files directly
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
- uploaded_file = attacher.store!(file) # upload a file directly to permanent storage
109
- attacher.set(uploaded_file) # attach the uploaded file
163
+ attacher.attach(file)
164
+ attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg" @storage=:store>
110
165
  ```
111
166
 
112
- ## Retrieval
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
- The `#get` method reads record's `<attachment>_data` attribute, and constructs
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.get #=> #<Shrine::UploadedFile>
173
+ attacher.attach(file, storage: :other_store)
174
+ attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg" @storage=:other_store>
119
175
  ```
120
176
 
121
- The `#read` method will just return the value of the underlying
122
- `<attachment>_data` attribute.
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.read #=> '{"id":"dsg024lfs.jpg","storage":"cache","metadata":{...}}'
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
- In general you can use `#uploaded_file` to contruct a `Shrine::UploadedFile`
129
- from a JSON string.
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
- attachment_data = '{"id":"dsg024lfs.jpg","storage":"cache","metadata":{...}}'
133
- attacher.uploaded_file(attachment_data) #=> #<Shrine::UploadedFile>
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
- ## URL
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
- The `#url` method returns the URL to the attached file, and returns `nil` if
139
- no file is attached.
200
+ You can also pass additional options for `Shrine#upload`:
140
201
 
141
202
  ```rb
142
- attacher.url # calls `attacher.get.url`
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
- ## State
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 ask the attacher whether the currently attached file is cached or
148
- stored.
214
+ You can check whether a new file has been attached with `Attacher#changed?`:
149
215
 
150
216
  ```rb
151
- attacher.cached?
152
- attacher.stored?
217
+ attacher.changed? #=> true
153
218
  ```
154
219
 
155
- ## Validations
220
+ You can use `Attacher#change` to attach an `UploadedFile` object as is:
156
221
 
157
- Whenever a file is assigned via `#assign` or `#set`, the file validations are
158
- automatically run, and you can access the validation errors through `#errors`:
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
- attacher.assign(large_file)
162
- attacher.errors #=> ["is larger than 10 MB"]
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
- ## Promoting
240
+ ## Finalizing
166
241
 
167
- After the attachment is assigned and you run validations, it should be promoted
168
- to permanent storage after the record is saved. You can use `#finalize` for
169
- that, since that will also automatically delete any previously attached files.
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
- This is normally automatically added to a callback by the ORM plugin when going
177
- through the model. Internally this calls `#promote`, which uploads a given
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
- # uploads cached file to permanent storage and replaces the current one
183
- attacher.promote(cached_file, action: :custom_name)
254
+ attacher.changed? #=> true
255
+ attacher.finalize
256
+ attacher.changed? #=> false
184
257
  ```
185
258
 
186
- The `:action` parameter is optional; it can be used for triggering a certain
187
- processing block, or for additional context during instrumentation.
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
- As a matter of fact, all additional options passed to `#promote` will be
190
- forwarded to `Shrine#upload`. So unless you're generating versions, you can do
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.promote cached_file,
195
- metadata: { "filename" => "myfile.txt" },
196
- location: "custom/location",
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
- Internally `#promote` calls `#swap`, which will update the record with any
201
- uploaded file, but will reload the record to check if the current attachment
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.swap(uploaded_file)
283
+ attacher.promote
206
284
  ```
207
285
 
208
- Both `#promote` and `#swap` are useful for [file migrations].
286
+ Any options passed to `Attacher#promote_cached` or `Attacher#promote` will be
287
+ forwarded to `Shrine#upload`.
209
288
 
210
- ## Backgrounding
289
+ ### Replacing
211
290
 
212
- When the `backgrounding` plugin is loaded, it allows you to promote and delete
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
- Shrine.plugin :backgrounding
217
- Shrine::Attacher.promote { |data| PromoteJob.perform_async(data) }
218
- Shrine::Attacher.delete { |data| DeleteJob.perform_async(data) }
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._promote(cached_file) # calls the registered `Attacher.promote` block
222
- attacher._delete(uploaded_file) # calls the registered `Attacher.delete` block
305
+ attacher.destroy_previous
223
306
  ```
224
307
 
225
- These are automatically used when using Shrine through models.
308
+ ## Retrieving
226
309
 
227
- ## Context
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
- The attacher sends `#context` to each upload/delete call to the uploader. By
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.context #=>
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
- # record: #<Photo...>,
236
- # name: :image,
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
- However, you can change/add additional context to be sent when calling the
241
- uploaders:
383
+ This data can be stored somewhere, and later the attached file can be loaded
384
+ from it:
242
385
 
243
386
  ```rb
244
- attacher.context[:foo] = "bar"
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
- This is useful for example if you have immutable model instances, and you want
248
- to assign a new updated instance. For example both foreground and background
249
- `#promote` requires that the record is persisted (and its `#id` is present).
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
- ## Uploading and deleting
410
+ ## Deleting
252
411
 
253
- Normally you can upload and delete directly by using the uploader.
412
+ The attached file can be deleted via `Attacher#destroy_attached`:
254
413
 
255
414
  ```rb
256
- uploader = ImageUploader.new(:store)
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
- But the attacher also has wrapper methods for uploading and deleting, which
262
- also automatically pass in the attacher `#context` (which includes `:record`
263
- and `:name`):
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.cache!(file) # uploads file to temporary storage
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
- These methods only upload/delete files, they don't write to record's data
274
- column. You can also pass additional options for `Shrine#upload` and
275
- `Shrine#delete`:
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.cache!(file, upload_options: { acl: "public-read" })
279
- attacher.store!(file, location: "custom/location")
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
- [file migrations]: /doc/migrating_storage.md#readme
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