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/multiple_files.md
CHANGED
@@ -104,12 +104,12 @@ In the Photo model, create a Shrine attachment attribute named `image`
|
|
104
104
|
```rb
|
105
105
|
# with Sequel:
|
106
106
|
class Photo < Sequel::Model
|
107
|
-
include ImageUploader::Attachment
|
107
|
+
include ImageUploader::Attachment(:image)
|
108
108
|
end
|
109
109
|
|
110
110
|
# with Active Record:
|
111
111
|
class Photo < ActiveRecord::Base
|
112
|
-
include ImageUploader::Attachment
|
112
|
+
include ImageUploader::Attachment(:image)
|
113
113
|
end
|
114
114
|
```
|
115
115
|
|
@@ -257,7 +257,7 @@ class ImageUploader < Shrine
|
|
257
257
|
|
258
258
|
Attacher.validate do
|
259
259
|
validate_max_size 10*1024*1024
|
260
|
-
|
260
|
+
validate_mime_type %w[image/jpeg image/png image/webp]
|
261
261
|
end
|
262
262
|
end
|
263
263
|
```
|
data/doc/paperclip.md
CHANGED
@@ -65,7 +65,7 @@ class ImageUploader < Shrine
|
|
65
65
|
end
|
66
66
|
|
67
67
|
class Photo < ActiveRecord::Base
|
68
|
-
include ImageUploader::Attachment
|
68
|
+
include ImageUploader::Attachment(:image)
|
69
69
|
end
|
70
70
|
```
|
71
71
|
|
@@ -81,8 +81,9 @@ uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/store/kfds0lg9rer.jpg"
|
|
81
81
|
### Processing
|
82
82
|
|
83
83
|
In contrast to Paperclip's static options, in Shrine you define and perform
|
84
|
-
processing on instance-level.
|
85
|
-
|
84
|
+
processing on instance-level. You also have the descriptive method names
|
85
|
+
provided by the [image_processing] gem.
|
86
|
+
|
86
87
|
|
87
88
|
```rb
|
88
89
|
class Photo < ActiveRecord::Base
|
@@ -99,34 +100,26 @@ end
|
|
99
100
|
require "image_processing/mini_magick"
|
100
101
|
|
101
102
|
class ImageUploader < Shrine
|
102
|
-
plugin :
|
103
|
-
plugin :versions
|
104
|
-
|
105
|
-
process(:store) do |io, context|
|
106
|
-
versions = { original: io } # retain original
|
107
|
-
|
108
|
-
io.download do |original|
|
109
|
-
pipeline = ImageProcessing::MiniMagick.source(original)
|
103
|
+
plugin :derivatives
|
110
104
|
|
111
|
-
|
112
|
-
|
113
|
-
versions[:small] = pipeline.resize_to_limit!(300, 300)
|
114
|
-
end
|
105
|
+
Attacher.derivatives_processor do |original|
|
106
|
+
magick = ImageProcessing::MiniMagick.source(original)
|
115
107
|
|
116
|
-
|
108
|
+
{
|
109
|
+
large: magick.resize_to_limit!(800, 800),
|
110
|
+
medium: magick.resize_to_limit!(500, 500),
|
111
|
+
small: magick.resize_to_limit!(300, 300),
|
112
|
+
}
|
117
113
|
end
|
118
114
|
end
|
119
115
|
```
|
120
116
|
|
121
|
-
This allows you to fully optimize processing, because you can easily specify
|
122
|
-
which files are processed from which, and even add parallelization.
|
123
|
-
|
124
117
|
#### Reprocessing versions
|
125
118
|
|
126
119
|
Shrine doesn't have a built-in way of regenerating versions, because that has
|
127
120
|
to be written and optimized differently depending on whether you're adding or
|
128
121
|
removing a version, what ORM are you using, how many records there are in the
|
129
|
-
database etc. The [
|
122
|
+
database etc. The [Managing Derivatives] guide provides some useful tips on
|
130
123
|
this task.
|
131
124
|
|
132
125
|
### Validations
|
@@ -148,8 +141,8 @@ class ImageUploader < Shrine
|
|
148
141
|
plugin :validation_helpers
|
149
142
|
|
150
143
|
Attacher.validate do
|
151
|
-
|
152
|
-
validate_max_size 10*1024*1024
|
144
|
+
validate_mime_type %w[image/jpeg image/gif image/png]
|
145
|
+
validate_max_size 10*1024*1024
|
153
146
|
end
|
154
147
|
end
|
155
148
|
```
|
@@ -212,7 +205,7 @@ gives your models similar set of methods that Paperclip gives:
|
|
212
205
|
|
213
206
|
```rb
|
214
207
|
class Photo < Sequel::Model
|
215
|
-
include ImageUploader::Attachment
|
208
|
+
include ImageUploader::Attachment(:image)
|
216
209
|
end
|
217
210
|
```
|
218
211
|
|
@@ -243,11 +236,11 @@ Unlike Paperclip, Shrine will store this information for each processed
|
|
243
236
|
version, making them first-class citizens:
|
244
237
|
|
245
238
|
```rb
|
246
|
-
photo.image
|
247
|
-
photo.image
|
239
|
+
photo.image #=> #<Shrine::UploadedFile>
|
240
|
+
photo.image.width #=> 800
|
248
241
|
|
249
|
-
photo.image
|
250
|
-
photo.image
|
242
|
+
photo.image(:thumb) #=> #<Shrine::UploadedFile>
|
243
|
+
photo.image(:thumb).width #=> 300
|
251
244
|
```
|
252
245
|
|
253
246
|
Also, since Paperclip stores only the filename, it has to recalculate the full
|
@@ -256,28 +249,6 @@ to move files to a new location, because changing how the location is generated
|
|
256
249
|
will now cause incorrect URLs to be generated for all existing files. Shrine
|
257
250
|
calculates the whole location only once and saves it to the column.
|
258
251
|
|
259
|
-
### Hooks/Callbacks
|
260
|
-
|
261
|
-
Shrine's `hooks` plugin provides callbacks for Shrine, so to get Paperclip's
|
262
|
-
`(before|after)_post_process`, you can override `#before_process` and
|
263
|
-
`#after_process` methods:
|
264
|
-
|
265
|
-
```rb
|
266
|
-
class ImageUploader < Shrine
|
267
|
-
plugin :hooks
|
268
|
-
|
269
|
-
def before_process(io, context)
|
270
|
-
# ...
|
271
|
-
super
|
272
|
-
end
|
273
|
-
|
274
|
-
def after_process(io, context)
|
275
|
-
super
|
276
|
-
# ...
|
277
|
-
end
|
278
|
-
end
|
279
|
-
```
|
280
|
-
|
281
252
|
## Migrating from Paperclip
|
282
253
|
|
283
254
|
You have an existing app using Paperclip and you want to transfer it to Shrine.
|
@@ -292,6 +263,17 @@ Afterwards we need to make new uploads write to the `image_data` column. This
|
|
292
263
|
can be done by including the below module to all models that have Paperclip
|
293
264
|
attachments:
|
294
265
|
|
266
|
+
```rb
|
267
|
+
require "shrine"
|
268
|
+
|
269
|
+
Shrine.storages = {
|
270
|
+
cache: ...,
|
271
|
+
store: ...,
|
272
|
+
}
|
273
|
+
|
274
|
+
Shrine.plugin :model
|
275
|
+
Shrine.plugin :derivatives
|
276
|
+
```
|
295
277
|
```rb
|
296
278
|
module PaperclipShrineSynchronization
|
297
279
|
def self.included(model)
|
@@ -304,49 +286,53 @@ module PaperclipShrineSynchronization
|
|
304
286
|
|
305
287
|
def write_shrine_data(name)
|
306
288
|
attachment = send(name)
|
289
|
+
attacher = Shrine::Attacher.from_model(self, name)
|
307
290
|
|
308
291
|
if attachment.size.present?
|
309
|
-
|
292
|
+
attacher.set shrine_file(attachment)
|
310
293
|
|
311
|
-
|
312
|
-
|
313
|
-
attachment.styles.each do |name, style|
|
314
|
-
data[name] = style_to_shrine_data(style)
|
315
|
-
end
|
294
|
+
attachment.styles.each do |name, style|
|
295
|
+
attacher.merge_derivatives(name => shrine_file(style))
|
316
296
|
end
|
317
|
-
|
318
|
-
write_attribute(:"#{name}_data", data.to_json)
|
319
297
|
else
|
320
|
-
|
298
|
+
attacher.set nil
|
321
299
|
end
|
322
300
|
end
|
323
301
|
|
324
302
|
private
|
325
303
|
|
304
|
+
def shrine_file(object)
|
305
|
+
if object.is_a?(Paperclip::Attachment)
|
306
|
+
shrine_attachment_file(object)
|
307
|
+
else
|
308
|
+
shrine_style_file(object)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
326
312
|
# If you'll be using a `:prefix` on your Shrine storage, or you're storing
|
327
313
|
# files on the filesystem, make sure to subtract the appropriate part
|
328
314
|
# from the path assigned to `:id`.
|
329
|
-
def
|
330
|
-
|
331
|
-
storage:
|
332
|
-
id:
|
315
|
+
def shrine_attachment_file(attachment)
|
316
|
+
Shrine.uploaded_file(
|
317
|
+
storage: :store,
|
318
|
+
id: attachment.path,
|
333
319
|
metadata: {
|
334
|
-
size
|
335
|
-
filename
|
336
|
-
mime_type
|
320
|
+
"size" => attachment.size,
|
321
|
+
"filename" => attachment.original_filename,
|
322
|
+
"mime_type" => attachment.content_type,
|
337
323
|
}
|
338
|
-
|
324
|
+
)
|
339
325
|
end
|
340
326
|
|
341
327
|
# If you'll be using a `:prefix` on your Shrine storage, or you're storing
|
342
328
|
# files on the filesystem, make sure to subtract the appropriate part
|
343
329
|
# from the path assigned to `:id`.
|
344
330
|
def style_to_shrine_data(style)
|
345
|
-
|
346
|
-
storage:
|
347
|
-
id:
|
348
|
-
metadata: {}
|
349
|
-
|
331
|
+
Shrine.uploaded_file(
|
332
|
+
storage: :store,
|
333
|
+
id: style.attachment.path(style.name),
|
334
|
+
metadata: {},
|
335
|
+
)
|
350
336
|
end
|
351
337
|
end
|
352
338
|
```
|
@@ -383,8 +369,8 @@ metadata defined in your Shrine uploader:
|
|
383
369
|
Shrine.plugin :refresh_metadata
|
384
370
|
|
385
371
|
Photo.find_each do |photo|
|
386
|
-
|
387
|
-
photo.
|
372
|
+
photo.image_attacher.refresh_metadata!
|
373
|
+
photo.save
|
388
374
|
end
|
389
375
|
```
|
390
376
|
|
@@ -396,8 +382,8 @@ As mentioned above, Shrine's equivalent of `has_attached_file` is including
|
|
396
382
|
an attachment module:
|
397
383
|
|
398
384
|
```rb
|
399
|
-
class
|
400
|
-
include ImageUploader::Attachment
|
385
|
+
class Photo < Sequel::Model
|
386
|
+
include ImageUploader::Attachment(:image) # adds `image`, `image=` and `image_url` methods
|
401
387
|
end
|
402
388
|
```
|
403
389
|
|
@@ -420,8 +406,23 @@ You can change that for a specific uploader with the `default_storage` plugin.
|
|
420
406
|
|
421
407
|
#### `:styles`, `:processors`, `:convert_options`
|
422
408
|
|
423
|
-
|
424
|
-
|
409
|
+
Processing is defined by using the `derivatives` plugin:
|
410
|
+
|
411
|
+
```rb
|
412
|
+
class ImageUploader < Shrine
|
413
|
+
plugin :derivatives
|
414
|
+
|
415
|
+
Attacher.derivatives_processor do |original|
|
416
|
+
magick = ImageProcessing::MiniMagick.source(image)
|
417
|
+
|
418
|
+
{
|
419
|
+
large: magick.resize_to_limit!(800, 800),
|
420
|
+
medium: magick.resize_to_limit!(500, 500),
|
421
|
+
small: magick.resize_to_limit!(300, 300),
|
422
|
+
}
|
423
|
+
end
|
424
|
+
end
|
425
|
+
```
|
425
426
|
|
426
427
|
#### `:default_url`
|
427
428
|
|
@@ -443,7 +444,7 @@ Shrine provides a `keep_files` plugin which allows you to keep files that would
|
|
443
444
|
otherwise be deleted:
|
444
445
|
|
445
446
|
```rb
|
446
|
-
Shrine.plugin :keep_files
|
447
|
+
Shrine.plugin :keep_files
|
447
448
|
```
|
448
449
|
|
449
450
|
#### `:path`, `:url`, `:interpolator`, `:url_generator`
|
@@ -460,7 +461,7 @@ Alternatively, if you want to generate locations yourself you can override the
|
|
460
461
|
|
461
462
|
```rb
|
462
463
|
class ImageUploader < Shrine
|
463
|
-
def generate_location(io,
|
464
|
+
def generate_location(io, **options)
|
464
465
|
# ...
|
465
466
|
end
|
466
467
|
end
|
@@ -481,17 +482,16 @@ If you're generating versions in Shrine, the attachment will be a hash of
|
|
481
482
|
uploaded files:
|
482
483
|
|
483
484
|
```rb
|
484
|
-
|
485
|
-
user.avatar #=>
|
485
|
+
photo.image_derivatives #=>
|
486
486
|
# {
|
487
487
|
# small: #<Shrine::UploadedFile>,
|
488
488
|
# medium: #<Shrine::UploadedFile>,
|
489
489
|
# large: #<Shrine::UploadedFile>,
|
490
490
|
# }
|
491
491
|
|
492
|
-
|
492
|
+
photo.image(:small).url #=> "..."
|
493
493
|
# or
|
494
|
-
|
494
|
+
photo.image_url(:small) #=> "..."
|
495
495
|
```
|
496
496
|
|
497
497
|
#### `#path`
|
@@ -500,12 +500,12 @@ Shrine doesn't have this because storages are abstract and this would be
|
|
500
500
|
specific to the filesystem, but the closest is probably `#id`:
|
501
501
|
|
502
502
|
```rb
|
503
|
-
|
503
|
+
photo.image.id #=> "photo/342/image/398543qjfdsf.jpg"
|
504
504
|
```
|
505
505
|
|
506
506
|
#### `#reprocess!`
|
507
507
|
|
508
|
-
Shrine doesn't have an equivalent to this, but the [
|
508
|
+
Shrine doesn't have an equivalent to this, but the [Managing Derivatives]
|
509
509
|
guide provides some useful tips on how to do this.
|
510
510
|
|
511
511
|
### `Paperclip::Storage::S3`
|
@@ -532,7 +532,7 @@ Shrine::Storage::S3.new(
|
|
532
532
|
The object data can be configured via the `:upload_options` hash:
|
533
533
|
|
534
534
|
```rb
|
535
|
-
Shrine::Storage::S3.new(upload_options: {content_disposition: "attachment"}, **options)
|
535
|
+
Shrine::Storage::S3.new(upload_options: { content_disposition: "attachment" }, **options)
|
536
536
|
```
|
537
537
|
|
538
538
|
You can use the `upload_options` plugin to set upload options dynamically.
|
@@ -542,7 +542,7 @@ You can use the `upload_options` plugin to set upload options dynamically.
|
|
542
542
|
The object permissions can be configured with the `:acl` upload option:
|
543
543
|
|
544
544
|
```rb
|
545
|
-
Shrine::Storage::S3.new(upload_options: {acl: "private"}, **options)
|
545
|
+
Shrine::Storage::S3.new(upload_options: { acl: "private" }, **options)
|
546
546
|
```
|
547
547
|
|
548
548
|
You can use the `upload_options` plugin to set upload options dynamically.
|
@@ -581,6 +581,7 @@ The Shrine storage has no replacement for the `:url` Paperclip option, and it
|
|
581
581
|
isn't needed.
|
582
582
|
|
583
583
|
[file]: http://linux.die.net/man/1/file
|
584
|
-
[
|
584
|
+
[Managing Derivatives]: /doc/changing_derivatives.md#readme
|
585
585
|
[direct S3 uploads]: /doc/direct_s3.md#readme
|
586
586
|
[`Shrine::Storage::S3`]: /doc/storage/s3.md#readme
|
587
|
+
[image_processing]: https://github.com/janko/image_processing
|
data/doc/plugins/activerecord.md
CHANGED
@@ -74,18 +74,9 @@ photo.image.exists? #=> false
|
|
74
74
|
|
75
75
|
#### Caveats
|
76
76
|
|
77
|
-
Active Record
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
```rb
|
82
|
-
# This is the default in ActiveRecord 5
|
83
|
-
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
84
|
-
```
|
85
|
-
|
86
|
-
Active Record also currently has a [bug with transaction callbacks], so if
|
87
|
-
you have any "after commit" callbacks, make sure to include Shrine's attachment
|
88
|
-
module *after* they have all been defined.
|
77
|
+
Active Record currently has a [bug with transaction callbacks], so if you have
|
78
|
+
any "after commit" callbacks, make sure to include Shrine's attachment module
|
79
|
+
*after* they have all been defined.
|
89
80
|
|
90
81
|
#### Skipping Callbacks
|
91
82
|
|
@@ -1,150 +1,176 @@
|
|
1
1
|
# Backgrounding
|
2
2
|
|
3
3
|
The [`backgrounding`][backgrounding] plugin enables you to move promoting and
|
4
|
-
deleting of files
|
5
|
-
|
6
|
-
external storage service.
|
7
|
-
|
8
|
-
The plugin provides `Attacher.promote` and `Attacher.delete` methods, which
|
9
|
-
allow you to hook up to promoting and deleting and spawn background jobs, by
|
10
|
-
passing a block.
|
4
|
+
deleting of files into background jobs. This is especially useful if you're
|
5
|
+
processing [derivatives] and storing files to a remote storage service.
|
11
6
|
|
12
7
|
```rb
|
13
|
-
Shrine.plugin :backgrounding
|
14
|
-
|
15
|
-
# makes all uploaders use background jobs
|
16
|
-
Shrine::Attacher.promote { |data| PromoteJob.perform_async(data) }
|
17
|
-
Shrine::Attacher.delete { |data| DeleteJob.perform_async(data) }
|
8
|
+
Shrine.plugin :backgrounding # load the plugin globally
|
18
9
|
```
|
19
10
|
|
20
|
-
|
21
|
-
|
22
|
-
|
11
|
+
The plugin provides `Attacher.promote_block` and `Attacher.destroy_block`
|
12
|
+
methods, which allow you to register blocks that will get executed in place of
|
13
|
+
synchronous promotion and deletion. Inside them you can spawn your background
|
14
|
+
jobs:
|
23
15
|
|
24
16
|
```rb
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
Attacher.delete { |data| DeleteJob.perform_async(data) }
|
29
|
-
end
|
17
|
+
# register backgrounding blocks for all uploaders
|
18
|
+
Shrine::Attacher.promote_block { PromoteJob.perform_later(self.class, record, name, file_data) }
|
19
|
+
Shrine::Attacher.destroy_block { DestroyJob.perform_later(self.class, data) }
|
30
20
|
```
|
31
|
-
|
32
|
-
The yielded `data` variable is a serializable hash containing all context
|
33
|
-
needed for promotion/deletion. Now you just need to declare the job classes,
|
34
|
-
and inside them call `Attacher.promote` or `Attacher.delete`, this time with
|
35
|
-
the received data.
|
36
|
-
|
37
21
|
```rb
|
38
|
-
class PromoteJob
|
39
|
-
|
40
|
-
|
41
|
-
|
22
|
+
class PromoteJob < ActiveJob::Base
|
23
|
+
def perform(attacher_class, record, name, file_data)
|
24
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
25
|
+
attacher.atomic_promote
|
26
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
27
|
+
# attachment has changed or record has been deleted, nothing to do
|
42
28
|
end
|
43
29
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
def perform(data)
|
48
|
-
|
30
|
+
```
|
31
|
+
```rb
|
32
|
+
class DestroyJob < ActiveJob::Base
|
33
|
+
def perform(attacher_class, data)
|
34
|
+
attacher = attacher_class.from_data(data)
|
35
|
+
attacher.destroy
|
49
36
|
end
|
50
37
|
end
|
51
38
|
```
|
52
39
|
|
53
|
-
|
54
|
-
backgrounding
|
40
|
+
If you don't want to apply backgrounding for all uploaders, you can register
|
41
|
+
backgrounding blocks only for a specific uploader:
|
55
42
|
|
56
|
-
|
57
|
-
|
58
|
-
|
43
|
+
```rb
|
44
|
+
class MyUploader < Shrine
|
45
|
+
# register backgrounding blocks only for this uploader
|
46
|
+
Attacher.promote_block { PromoteJob.perform_later(self.class, record, name, file_data) }
|
47
|
+
Attacher.destroy_block { DestroyJob.perform_later(self.class, data) }
|
48
|
+
end
|
49
|
+
```
|
59
50
|
|
60
|
-
|
61
|
-
|
62
|
-
|
51
|
+
Backgrounding will automatically get triggered as part of your attachment flow
|
52
|
+
if you're using `Shrine::Attachment` with a persistence plugin such as
|
53
|
+
`activerecord` or `sequel`:
|
63
54
|
|
64
55
|
```rb
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
56
|
+
photo = Photo.new
|
57
|
+
photo.image = file
|
58
|
+
photo.save # spawns promote job
|
59
|
+
photo.destroy # spawns destroy job
|
70
60
|
```
|
71
61
|
|
72
|
-
|
62
|
+
In terms of `Shrine::Attacher`, the background jobs are spawned on
|
63
|
+
`Attacher#promote_cached` (called on `Attacher#finalize`) and
|
64
|
+
`Attacher#destroy_attached`:
|
73
65
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
66
|
+
```rb
|
67
|
+
attacher.assign(file)
|
68
|
+
attacher.finalize # spawns promote job
|
69
|
+
attacher.destroy_attached # spawns destroy job
|
70
|
+
```
|
79
71
|
|
80
|
-
|
81
|
-
* if record is not found, it finishes
|
82
|
-
* if record is found but attachment has changed, it finishes
|
83
|
-
2. uploads cached file to permanent storage
|
84
|
-
3. reloads the database record
|
85
|
-
* if record is not found, it deletes the promoted files and finishes
|
86
|
-
* if record is found but attachment has changed, it deletes the promoted files and finishes
|
87
|
-
4. updates the record with the promoted files
|
72
|
+
## Promotion
|
88
73
|
|
89
|
-
|
90
|
-
|
91
|
-
additional tasks:
|
74
|
+
While background deletion acts only on file data, background promotion is more
|
75
|
+
complex as it deals with persistence and concurrency safety:
|
92
76
|
|
93
77
|
```rb
|
94
|
-
|
95
|
-
|
96
|
-
attacher.record.update(published: true) if attacher && attacher.record.is_a?(Post)
|
97
|
-
end
|
78
|
+
attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
|
79
|
+
attacher.atomic_promote
|
98
80
|
```
|
99
81
|
|
100
|
-
|
82
|
+
The `Attacher.retrieve` and `Attacher#atomic_promote` methods are provided by
|
83
|
+
the [`atomic_helpers`][atomic_helpers] plugin, which is automatically loaded by
|
84
|
+
your persistence plugin (`activerecord`, `sequel`). They add concurrency safety
|
85
|
+
by verifying that the attachment hasn't changed on the outside before or after
|
86
|
+
promotion.
|
101
87
|
|
102
|
-
|
103
|
-
records; the plugin will use that mode if `Attacher.find_record` is not
|
104
|
-
defined. In that case promotion will have the following behaviour:
|
88
|
+
When we remove the concurrency safety, promotion would look like this:
|
105
89
|
|
106
|
-
|
107
|
-
|
108
|
-
|
90
|
+
```rb
|
91
|
+
attacher = record.send(:"#{name}_attacher")
|
92
|
+
attacher.promote
|
93
|
+
attacher.persist
|
94
|
+
```
|
95
|
+
|
96
|
+
## Backgrounding blocks
|
109
97
|
|
110
|
-
|
111
|
-
|
98
|
+
The blocks registered by `Attacher.promote_block` and `Attacher#destroy_block`
|
99
|
+
are by default evaluated in context of a `Shrine::Attacher` instance. You can
|
100
|
+
also use the explicit version by declaring an attacher argument:
|
101
|
+
|
102
|
+
```rb
|
103
|
+
Shrine::Attacher.promote_block do |attacher|
|
104
|
+
PromoteJob.perform_later(
|
105
|
+
attacher.class,
|
106
|
+
attacher.record,
|
107
|
+
attacher.name,
|
108
|
+
attacher.file_data,
|
109
|
+
)
|
110
|
+
end
|
112
111
|
|
113
|
-
|
112
|
+
Shrine::Attacher.destroy_block do |attacher|
|
113
|
+
DestroyJob.perform_later(
|
114
|
+
attacher.class,
|
115
|
+
attacher.data,
|
116
|
+
)
|
117
|
+
end
|
118
|
+
```
|
114
119
|
|
115
|
-
|
116
|
-
|
117
|
-
internally called by the attacher. `Attacher#promote` and `Attacher#delete!`
|
118
|
-
remain synchronous.
|
120
|
+
You can also register backgrounding blocks on attacher *instances* for more
|
121
|
+
flexibility:
|
119
122
|
|
120
123
|
```rb
|
121
|
-
|
122
|
-
|
123
|
-
attacher.
|
124
|
+
photo.image_attacher.promote_block do |attacher|
|
125
|
+
PromoteJob.perform_later(
|
126
|
+
attacher.class,
|
127
|
+
attacher.record,
|
128
|
+
attacher.name,
|
129
|
+
attacher.file_data,
|
130
|
+
current_user.id, # pass arguments known at the controller level
|
131
|
+
)
|
132
|
+
end
|
124
133
|
|
125
|
-
|
126
|
-
|
127
|
-
attacher.delete!(attachment)
|
134
|
+
photo.image = file
|
135
|
+
photo.save # executes the promote block above
|
128
136
|
```
|
129
137
|
|
130
|
-
##
|
138
|
+
## Other backgrounding libraries
|
131
139
|
|
132
|
-
|
133
|
-
|
134
|
-
jobs for custom tasks.
|
140
|
+
If you're not using Active Job for backgrounding, you might need to retrieve
|
141
|
+
records and resolve constants from job arguments manually:
|
135
142
|
|
136
143
|
```rb
|
137
|
-
|
138
|
-
|
144
|
+
# register backgrounding blocks for all uploaders
|
145
|
+
Shrine::Attacher.promote_block { PromoteJob.perform_async(self.class, record.class, record.id, name, file_data) }
|
146
|
+
Shrine::Attacher.destroy_block { DestroyJob.perform_async(self.class, data) }
|
147
|
+
```
|
148
|
+
```rb
|
149
|
+
class PromoteJob
|
150
|
+
include Sidekiq::Worker
|
151
|
+
|
152
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
153
|
+
attacher_class = Object.const_get(attacher_class)
|
154
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
155
|
+
|
156
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
157
|
+
attacher.atomic_promote
|
158
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class DestroyJob
|
163
|
+
include Sidekiq::Worker
|
139
164
|
|
140
|
-
|
165
|
+
def perform(attacher_class, data)
|
166
|
+
attacher_class = Object.const_get(attacher_class)
|
141
167
|
|
142
|
-
|
143
|
-
|
144
|
-
attacher = Shrine::Attacher.load(data)
|
145
|
-
# ...
|
168
|
+
attacher = attacher_class.from_data(data)
|
169
|
+
attacher.destroy
|
146
170
|
end
|
147
171
|
end
|
148
172
|
```
|
149
173
|
|
150
174
|
[backgrounding]: /lib/shrine/plugins/backgrounding.rb
|
175
|
+
[derivatives]: /doc/plugins/derivatives.md#readme
|
176
|
+
[atomic_helpers]: /doc/plugins/atomic_helpers.md#readme
|