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
@@ -65,26 +65,27 @@ deleting files, they also represent the uploaded file. Shrine has a separate
65
65
  uploaded_file = ImageUploader.upload(file, :store)
66
66
  uploaded_file #=> #<Shrine::UploadedFile>
67
67
  uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/store/kfds0lg9rer.jpg"
68
- uploaded_file.download #=> #<Tempfile>
68
+ uploaded_file.download #=> #<File:/tmp/path/to/file>
69
69
  ```
70
70
 
71
- ### Processing
71
+ ## Processing
72
72
 
73
73
  In contrast to CarrierWave's class-level DSL, in Shrine processing is defined
74
- and performed on the instance-level. The result of processing can be a single
75
- file or a hash of versions:
74
+ and performed on the instance-level.
76
75
 
77
76
  ```rb
78
77
  class ImageUploader < CarrierWave::Uploader::Base
79
78
  include CarrierWave::MiniMagick
80
79
 
81
- process resize_to_limit: [800, 800]
80
+ version :large do
81
+ process resize_to_limit: [800, 800]
82
+ end
82
83
 
83
84
  version :medium do
84
85
  process resize_to_limit: [500, 500]
85
86
  end
86
87
 
87
- version :small, from_version: :medium do
88
+ version :small do
88
89
  process resize_to_limit: [300, 300]
89
90
  end
90
91
  end
@@ -94,39 +95,27 @@ end
94
95
  require "image_processing/mini_magick"
95
96
 
96
97
  class ImageUploader < Shrine
97
- plugin :processing
98
- plugin :versions
99
-
100
- process(:store) do |io, context|
101
- versions = {}
98
+ plugin :derivatives
102
99
 
103
- io.download do |original|
104
- pipeline = ImageProcessing::MiniMagick.source(original)
105
-
106
- versions[:original] = pipeline.resize_to_limit!(800, 800)
107
- versions[:medium] = pipeline.resize_to_limit!(500, 500)
108
- versions[:small] = pipeline.resize_to_limit!(300, 300)
109
- end
100
+ Attacher.derivatives_processor do |original|
101
+ magick = ImageProcessing::MiniMagick.source(original)
110
102
 
111
- versions # return the hash of processed files
103
+ {
104
+ large: magick.resize_to_limit!(800, 800),
105
+ medium: magick.resize_to_limit!(500, 500),
106
+ small: magick.resize_to_limit!(300, 300),
107
+ }
112
108
  end
113
109
  end
114
110
  ```
115
111
 
116
- This allows you to fully optimize processing, because you can easily specify
117
- which files are processed from which, and even add parallelization.
118
-
119
112
  CarrierWave performs processing before validations, which is a huge security
120
113
  issue, as it allows users to give arbitrary files to your processing tool, even
121
- if you have validations. Shrine performs processing after validations.
114
+ if you have validations. With Shrine you can perform processing after
115
+ validations.
122
116
 
123
- #### Reprocessing versions
124
-
125
- Shrine doesn't have a built-in way of regenerating versions, because that has
126
- to be written and optimized differently depending on whether you're adding or
127
- removing a version, what ORM are you using, how many records there are in the
128
- database etc. The [Reprocessing versions] guide provides some useful tips on
129
- this task.
117
+ Shrine doesn't have a built-in way of regenerating versions, but there is an
118
+ extensive [Managing Derivatives] guide.
130
119
 
131
120
  ### Validations
132
121
 
@@ -154,9 +143,9 @@ class ImageUploader < Shrine
154
143
  plugin :validation_helpers
155
144
 
156
145
  Attacher.validate do
157
- validate_extension_inclusion %w[jpg jpeg gif png]
158
- validate_mime_type_inclusion %w[image/jpeg image/gif image/png]
159
- validate_max_size 10*1024*1024 unless record.admin?
146
+ validate_extension %w[jpg jpeg gif png]
147
+ validate_mime_type %w[image/jpeg image/gif image/png]
148
+ validate_max_size 10*1024*1024
160
149
  end
161
150
  end
162
151
  ```
@@ -184,7 +173,7 @@ end
184
173
  ```
185
174
  ```rb
186
175
  class Photo < ActiveRecord::Base
187
- include ImageUploader::Attachment.new(:avatar)
176
+ include ImageUploader::Attachment(:image)
188
177
  end
189
178
  ```
190
179
 
@@ -196,12 +185,12 @@ uses to save storage, location, and metadata of the uploaded file.
196
185
  ```rb
197
186
  photo.image_data #=>
198
187
  # {
199
- # "storage" => "store",
200
- # "id" => "photo/1/image/0d9o8dk42.png",
201
- # "metadata" => {
202
- # "filename" => "nature.png",
203
- # "size" => 49349138,
204
- # "mime_type" => "image/png"
188
+ # "storage": "store",
189
+ # "id": "photo/1/image/0d9o8dk42.png",
190
+ # "metadata": {
191
+ # "filename": "nature.png",
192
+ # "size": 49349138,
193
+ # "mime_type": "image/png"
205
194
  # }
206
195
  # }
207
196
 
@@ -218,11 +207,11 @@ Unlike CarrierWave, Shrine will store this information for each processed
218
207
  version, making them first-class citizens:
219
208
 
220
209
  ```rb
221
- photo.image[:original] #=> #<Shrine::UploadedFile>
222
- photo.image[:original].width #=> 800
210
+ photo.image #=> #<Shrine::UploadedFile>
211
+ photo.image.width #=> 800
223
212
 
224
- photo.image[:thumb] #=> #<Shrine::UploadedFile>
225
- photo.image[:thumb].width #=> 300
213
+ photo.image(:thumb) #=> #<Shrine::UploadedFile>
214
+ photo.image(:thumb).width #=> 300
226
215
  ```
227
216
 
228
217
  Also, since CarrierWave stores only the filename, it has to recalculate the
@@ -254,6 +243,17 @@ Afterwards we need to make new uploads write to the `image_data` column. This
254
243
  can be done by including the below module to all models that have CarrierWave
255
244
  attachments:
256
245
 
246
+ ```rb
247
+ require "shrine"
248
+
249
+ Shrine.storages = {
250
+ cache: ...,
251
+ store: ...,
252
+ }
253
+
254
+ Shrine.plugin :model
255
+ Shrine.plugin :derivatives
256
+ ```
257
257
  ```rb
258
258
  module CarrierwaveShrineSynchronization
259
259
  def self.included(model)
@@ -266,22 +266,16 @@ module CarrierwaveShrineSynchronization
266
266
 
267
267
  def write_shrine_data(name)
268
268
  uploader = send(name)
269
+ attacher = Shrine::Attacher.form_model(self, name)
269
270
 
270
271
  if read_attribute(name).present?
271
- data = uploader_to_shrine_data(uploader)
272
+ attacher.set shrine_file(uploader)
272
273
 
273
- if uploader.versions.any?
274
- data = {original: data}
275
- uploader.versions.each do |name, version|
276
- data[name] = uploader_to_shrine_data(version)
277
- end
274
+ uploader.versions.each do |name, version|
275
+ attacher.merge_derivatives(name => shrine_file(version))
278
276
  end
279
-
280
- # Remove the `.to_json` if you're using a JSON column, otherwise the JSON
281
- # object will be saved as an escaped string.
282
- write_attribute(:"#{name}_data", data.to_json)
283
277
  else
284
- write_attribute(:"#{name}_data", nil)
278
+ attacher.set nil
285
279
  end
286
280
  end
287
281
 
@@ -289,15 +283,16 @@ module CarrierwaveShrineSynchronization
289
283
 
290
284
  # If you'll be using `:prefix` on your Shrine storage, make sure to
291
285
  # subtract it from the path assigned as `:id`.
292
- def uploader_to_shrine_data(uploader)
293
- filename = read_attribute(uploader.mounted_as)
286
+ def shrine_file(uploader)
287
+ name = uploader.mounted_as
288
+ filename = read_attribute(name)
294
289
  path = uploader.store_path(filename)
295
290
 
296
- {
297
- storage: :store,
298
- id: path,
299
- metadata: { filename: filename }
300
- }
291
+ Shrine.uploaded_file(
292
+ storage: :store,
293
+ id: path,
294
+ metadata: { "filename" => filename },
295
+ )
301
296
  end
302
297
  end
303
298
  ```
@@ -332,8 +327,8 @@ your Shrine uploader:
332
327
  Shrine.plugin :refresh_metadata
333
328
 
334
329
  Photo.find_each do |photo|
335
- attachment = ImageUploader.uploaded_file(photo.image, &:refresh_metadata!)
336
- photo.update(image_data: attachment.to_json)
330
+ photo.image_attacher.refresh_metadata!
331
+ photo.save
337
332
  end
338
333
  ```
339
334
 
@@ -358,26 +353,28 @@ end
358
353
 
359
354
  #### `.process`, `.version`
360
355
 
361
- As explained in the "Processing" section, processing is done by overriding the
362
- `Shrine#process` method.
363
-
364
- #### `.before`, `.after`
365
-
366
- In Shrine you can get callbacks by loading the `hooks` plugin. Unlike
367
- CarrierWave, and much like Sequel, Shrine implements callbacks by overriding
368
- instance methods:
356
+ Processing is defined by using the `derivatives` plugin:
369
357
 
370
358
  ```rb
371
359
  class ImageUploader < Shrine
372
- plugin :hooks
360
+ plugin :derivatives
361
+
362
+ Attacher.derivatives_processor do |original|
363
+ magick = ImageProcessing::MiniMagick.source(image)
373
364
 
374
- def after_upload(io, context)
375
- super
376
- # do something
365
+ {
366
+ large: magick.resize_to_limit!(800, 800),
367
+ medium: magick.resize_to_limit!(500, 500),
368
+ small: magick.resize_to_limit!(300, 300),
369
+ }
377
370
  end
378
371
  end
379
372
  ```
380
373
 
374
+ #### `.before`, `.after`
375
+
376
+ There is no Shrine equivalent for CarrierWave's callbacks.
377
+
381
378
  #### `#store!`, `#cache!`
382
379
 
383
380
  In Shrine you store and cache files by passing the corresponding storage to
@@ -406,8 +403,9 @@ uploaded_file.download #=> #<Tempfile:/path/to/file>
406
403
  In Shrine you call `#url` on uploaded files:
407
404
 
408
405
  ```rb
409
- user.avatar #=> #<Shrine::UploadedFile>
410
- user.avatar.url #=> "/uploads/398454ujedfggf.jpg"
406
+ photo.image #=> #<Shrine::UploadedFile>
407
+ photo.image.url #=> "/uploads/398454ujedfggf.jpg"
408
+ photo.image_url #=> "/uploads/398454ujedfggf.jpg" (shorthand)
411
409
  ```
412
410
 
413
411
  #### `#identifier`
@@ -415,8 +413,8 @@ user.avatar.url #=> "/uploads/398454ujedfggf.jpg"
415
413
  This method corresponds to `#original_filename` on the uploaded file:
416
414
 
417
415
  ```rb
418
- user.avatar #=> #<Shrine::UploadedFile>
419
- user.avatar.original_filename #=> "avatar.jpg"
416
+ photo.image #=> #<Shrine::UploadedFile>
417
+ photo.image.original_filename #=> "avatar.jpg"
420
418
  ```
421
419
 
422
420
  #### `#store_dir`, `#cache_dir`
@@ -426,15 +424,14 @@ storages:
426
424
 
427
425
  ```rb
428
426
  class ImageUploader < Shrine
429
- def generate_location(io, context)
430
- "#{context[:record].class}/#{context[:record].id}/#{io.original_filename}"
427
+ def generate_location(io, record: nil, **)
428
+ "#{record.class}/#{record.id}/#{io.original_filename}"
431
429
  end
432
430
  end
433
431
  ```
434
432
 
435
- The `context` variable holds the additional data, like the attacment name and
436
- the record instance. You might also want to use the `pretty_location` plugin
437
- for automatically generating an organized folder structure.
433
+ You might also want to use the `pretty_location` plugin for automatically
434
+ generating an organized folder structure.
438
435
 
439
436
  #### `#default_url`
440
437
 
@@ -450,12 +447,9 @@ class ImageUploader < Shrine
450
447
  end
451
448
  ```
452
449
 
453
- The `context` variable holds the name of the attachment, record instance and
454
- in some cases the `:version`.
455
-
456
450
  #### `#extension_white_list`, `#extension_black_list`
457
451
 
458
- In Shrine extension whitelisting/blacklisting is a part of validations, and is
452
+ In Shrine, extension whitelisting/blacklisting is a part of validations, and is
459
453
  provided by the `validation_helpers` plugin:
460
454
 
461
455
  ```rb
@@ -471,7 +465,7 @@ end
471
465
 
472
466
  #### `#blacklist_mime_type_pattern`, `#whitelist_mime_type_pattern`, `#content_type_whitelist`, `#content_type_blacklist`
473
467
 
474
- In Shrine MIME type whitelisting/blacklisting is part of validations, and is
468
+ In Shrine, MIME type whitelisting/blacklisting is part of validations, and is
475
469
  provided by the `validation_helpers` plugin, though it doesn't support regexes:
476
470
 
477
471
  ```rb
@@ -490,14 +484,12 @@ end
490
484
  In Shrine file size validations are typically done using the
491
485
  `validation_helpers` plugin:
492
486
 
493
-
494
487
  ```rb
495
488
  class ImageUploader < Shrine
496
489
  plugin :validation_helpers
497
490
 
498
491
  Attacher.validate do
499
- validate_min_size 0
500
- validate_max_size 5*1024*1024 # 5 MB
492
+ validate_size 0..5*1024*1024 # 5 MB
501
493
  end
502
494
  end
503
495
  ```
@@ -506,13 +498,13 @@ end
506
498
 
507
499
  Shrine doesn't have a built-in way of regenerating versions, because that's
508
500
  very individual and depends on what versions you want regenerated, what ORM are
509
- you using, how many records there are in your database etc. The [Regenerating
510
- versions] guide provides some useful tips on this task.
501
+ you using, how many records there are in your database etc. The [Managing
502
+ Derivatives] guide provides some useful tips on this task.
511
503
 
512
504
  ### Models
513
505
 
514
506
  The only thing that Shrine requires from your models is a `<attachment>_data`
515
- column (e.g. if your attachment is "avatar", you need the `avatar_data` column).
507
+ column (e.g. if your attachment is "image", you need the `image_data` column).
516
508
 
517
509
  #### `.mount_uploader`
518
510
 
@@ -523,7 +515,7 @@ Shrine.plugin :sequel
523
515
  ```
524
516
  ```rb
525
517
  class User < Sequel::Model
526
- include ImageUploader::Attachment.new(:avatar)
518
+ include ImageUploader::Attachment(:avatar)
527
519
  end
528
520
  ```
529
521
 
@@ -532,7 +524,7 @@ end
532
524
  The attachment module adds an attachment setter:
533
525
 
534
526
  ```rb
535
- user.avatar = File.open("avatar.jpg")
527
+ photo.image = File.open("avatar.jpg", "rb")
536
528
  ```
537
529
 
538
530
  Note that unlike CarrierWave, you cannot pass in file paths, the input needs to
@@ -544,8 +536,8 @@ CarrierWave returns the uploader, but Shrine returns a `Shrine::UploadedFile`,
544
536
  a representation of the file uploaded to the storage:
545
537
 
546
538
  ```rb
547
- user.avatar #=> #<Shrine::UploadedFile>
548
- user.avatar.methods #=> [:url, :download, :read, :exists?, :delete, ...]
539
+ photo.image #=> #<Shrine::UploadedFile>
540
+ photo.image.methods #=> [:url, :download, :read, :exists?, :delete, ...]
549
541
  ```
550
542
 
551
543
  If attachment is missing, nil is returned.
@@ -556,13 +548,13 @@ This method is simply a shorthand for "if attachment is present, call `#url`
556
548
  on it, otherwise return nil":
557
549
 
558
550
  ```rb
559
- user.avatar_url #=> nil
560
- user.avatar = File.open("avatar.jpg")
561
- user.avatar_url #=> "/uploads/ksdf934rt.jpg"
551
+ photo.image_url #=> nil
552
+ photo.image = File.open("avatar.jpg", "rb")
553
+ photo.image_url #=> "/uploads/ksdf934rt.jpg"
562
554
  ```
563
555
 
564
- The `versions` plugin extends this method to also accept a version name as the
565
- argument (`user.avatar_url(:thumb)`).
556
+ The `derivatives` plugin extends this method to also accept a version name as
557
+ the argument (`photo.image_url(:thumb)`).
566
558
 
567
559
  #### `#<attachment>_cache`
568
560
 
@@ -573,9 +565,9 @@ that you can use for retaining the cached file:
573
565
  Shrine.plugin :cached_attachment_data
574
566
  ```
575
567
  ```rb
576
- form_for @user do |f|
577
- f.hidden_field :avatar, value: @user.cached_avatar_data
578
- f.file_field :avatar
568
+ form_for @photo do |f|
569
+ f.hidden_field :image, value: @photo.cached_image_data
570
+ f.file_field :image
579
571
  end
580
572
  ```
581
573
 
@@ -608,7 +600,7 @@ By default Shrine deletes cached and replaced files, but you can choose to keep
608
600
  those files by loading the `keep_files` plugin:
609
601
 
610
602
  ```rb
611
- Shrine.plugin :keep_files, cached: true, replaced: true
603
+ Shrine.plugin :keep_files
612
604
  ```
613
605
 
614
606
  #### `move_to_cache`, `move_to_store`
@@ -632,8 +624,8 @@ class ImageUploader < Shrine
632
624
  Attacher.validate do
633
625
  # Evaluated inside an instance of Shrine::Attacher.
634
626
  if record.guest?
635
- validate_max_size 2*1024*1024, message: "is too large (max is 2 MB)"
636
- validate_mime_type_inclusion %w[image/jpg image/png image/gif]
627
+ validate_max_size 2*1024*1024, message: "must not be larger than 2 MB"
628
+ validate_mime_type %w[image/jpg image/png image/webp]
637
629
  end
638
630
  end
639
631
  end
@@ -709,12 +701,12 @@ plugin :url_options, store: { expires_in: 600 }
709
701
  Shrine allows you to override the S3 endpoint:
710
702
 
711
703
  ```rb
712
- Shrine::Storage::S3.new(endpoint: "https://s3-accelerate.amazonaws.com", **options)
704
+ Shrine::Storage::S3.new(use_accelerate_endpoint: true, **options)
713
705
  ```
714
706
 
715
707
  [image_processing]: https://github.com/janko/image_processing
716
708
  [demo app]: https://github.com/shrinerb/shrine/tree/master/demo
717
- [Reprocessing versions]: /doc/regenerating_versions.md#readme
709
+ [Managing Derivatives]: /doc/changing_derivatives.md#readme
718
710
  [shrine-fog]: https://github.com/shrinerb/shrine-fog
719
711
  [direct uploads]: /doc/direct_s3.md#readme
720
712
  [`Shrine::Storage::S3`]: /doc/storage/s3.md#readme
@@ -0,0 +1,308 @@
1
+ # Managing Derivatives
2
+
3
+ This guide shows how to add, create, update, and remove [derivatives] for an
4
+ app in production already handling file attachments, with zero downtime.
5
+
6
+ Let's assume we have a `Photo` model with an `image` file attachment. The
7
+ examples will be showing image thumbnails, but the advice applies to any kind
8
+ of derivatives.
9
+
10
+ ```rb
11
+ Shrine.plugin :derivatives
12
+ Shrine.plugin :activerecord
13
+ ```
14
+ ```rb
15
+ class ImageUploader < Shrine
16
+ # ...
17
+ end
18
+ ```
19
+ ```rb
20
+ class Photo < ActiveRecord::Base
21
+ include ImageUploader::Attachment(:image)
22
+ end
23
+ ```
24
+
25
+ ## Contents
26
+
27
+ * [Adding derivatives](#adding-derivatives)
28
+ * [Reprocessing all derivatives](#reprocessing-all-derivatives)
29
+ * [Reprocessing certain derivatives](#reprocessing-certain-derivatives)
30
+ * [Adding new derivatives](#adding-new-derivatives)
31
+ * [Removing derivatives](#removing-derivatives)
32
+ * [Backgrounding](#backgrounding)
33
+
34
+ ## Adding derivatives
35
+
36
+ *Scenario: Your app is currently working only with original files, and you want
37
+ to introduce derivatives.*
38
+
39
+ You'll first want to start creating the derivatives in production, without yet
40
+ generating URLs for them (because existing attachments won't yet have
41
+ derivatives generated). Let's assume you're generating image thumbnails:
42
+
43
+ ```rb
44
+ # Gemfile
45
+ gem "image_processing", "~> 1.8"
46
+ ```
47
+ ```rb
48
+ require "image_processing/mini_magick"
49
+
50
+ class ImageUploader < Shrine
51
+ Attacher.derivatives_processor do |original|
52
+ magick = ImageProcessing::MiniMagick.source(original)
53
+
54
+ # generate the thumbnails you want here
55
+ {
56
+ small: magick.resize_to_limit!(300, 300),
57
+ medium: magick.resize_to_limit!(500, 500),
58
+ large: magick.resize_to_limit!(800, 800),
59
+ }
60
+ end
61
+ end
62
+ ```
63
+ ```rb
64
+ photo = Photo.new(photo_params)
65
+ photo.image_derivatives! # generate derivatives
66
+ photo.save
67
+ ```
68
+
69
+ Once we've deployed this to production, we can run the following script to
70
+ generate derivatives for all existing attachments in production. It fetches the
71
+ records in batches, downloads attachments on permanent storage, creates
72
+ derivatives, and persists the changes.
73
+
74
+ ```rb
75
+ Photo.find_each do |photo|
76
+ attacher = photo.image_attacher
77
+
78
+ next unless attacher.stored?
79
+
80
+ attacher.create_derivatives
81
+
82
+ begin
83
+ attacher.atomic_persist # persist changes if attachment has not changed in the meantime
84
+ rescue Shrine::AttachmentChanged, # attachment has changed
85
+ ActiveRecord::RecordNotFound # record has been deleted
86
+ attacher.delete_derivatives # delete now orphaned derivatives
87
+ end
88
+ end
89
+ ```
90
+
91
+ Now all attachments should have correctly generated derivatives. You can update
92
+ the attachment URLs to use derivatives as needed.
93
+
94
+ ## Reprocessing all derivatives
95
+
96
+ *Scenario: The processing logic has changed for all or most derivatives, and
97
+ now you want to reprocess them for existing attachments.*
98
+
99
+ Let's assume we've made the following change and have deployed it to production:
100
+
101
+ ```diff
102
+ Attacher.derivatives_processor do |original|
103
+ magick = ImageProcessing::MiniMagick.source(original)
104
+ + .saver(quality: 85)
105
+
106
+ {
107
+ small: magick.resize_to_limit!(300, 300),
108
+ medium: magick.resize_to_limit!(500, 500),
109
+ large: magick.resize_to_limit!(800, 800),
110
+ }
111
+ end
112
+ ```
113
+
114
+ We can now run the following script to reprocess derivatives for all existing
115
+ records. It fetches the records in batches, downloads attachments on permanent
116
+ storage, reprocesses new derivatives, persists the changes, and deletes old
117
+ derivatives.
118
+
119
+ ```rb
120
+ Photo.find_each do |photo|
121
+ attacher = photo.image_attacher
122
+
123
+ next unless attacher.stored?
124
+
125
+ old_derivatives = attacher.derivatives
126
+
127
+ attacher.set_derivatives({}) # clear derivatives
128
+ attacher.create_derivatives # reprocess derivatives
129
+
130
+ begin
131
+ attacher.atomic_persist # persist changes if attachment has not changed in the meantime
132
+ attacher.delete_derivatives(old_derivatives) # delete old derivatives
133
+ rescue Shrine::AttachmentChanged, # attachment has changed
134
+ ActiveRecord::RecordNotFound # record has been deleted
135
+ attacher.delete_derivatives # delete now orphaned derivatives
136
+ end
137
+ end
138
+ ```
139
+
140
+ ## Reprocessing certain derivatives
141
+
142
+ *Scenario: The processing logic has changed for specific derivatives, and now
143
+ you want to reprocess them for existing attachments.*
144
+
145
+ Let's assume we've made a following change and have deployed it to production:
146
+
147
+ ```diff
148
+ Attacher.derivatives_processor do |original|
149
+ magick = ImageProcessing::MiniMagick.source(original)
150
+
151
+ {
152
+ small: magick.resize_to_limit!(300, 300),
153
+ - medium: magick.resize_to_limit!(500, 500),
154
+ + medium: magick.resize_to_limit!(600, 600),
155
+ large: magick.resize_to_limit!(800, 800),
156
+ }
157
+ end
158
+ ```
159
+
160
+ We can now run the following script to reprocess the derivative for all
161
+ existing records. It fetches the records in batches, downloads attachments with
162
+ derivatives, reprocesses the specific derivative, persists the change, and
163
+ deletes old derivative.
164
+
165
+ ```rb
166
+ Photo.find_each do |photo|
167
+ attacher = photo.image_attacher
168
+
169
+ next unless attacher.derivatives.key?(:medium)
170
+
171
+ old_medium = attacher.derivatives[:medium]
172
+ new_medium = attacher.file.download do |original|
173
+ ImageProcessing::MiniMagick
174
+ .source(original)
175
+ .resize_to_limit!(600, 600)
176
+ end
177
+
178
+ attacher.add_derivative(:medium, new_medium)
179
+
180
+ begin
181
+ attacher.atomic_persist # persist changes if attachment has not changed in the meantime
182
+ old_medium.delete # delete old derivative
183
+ rescue Shrine::AttachmentChanged, # attachment has changed
184
+ ActiveRecord::RecordNotFound # record has been deleted
185
+ attacher.derivatives[:medium].delete # delete now orphaned derivative
186
+ end
187
+ end
188
+ ```
189
+
190
+ ## Adding new derivatives
191
+
192
+ *Scenario: A new derivative has been added to the processor, and now
193
+ you want to add it to existing attachments.*
194
+
195
+ Let's assume we've made a following change and have deployed it to production:
196
+
197
+ ```diff
198
+ Attacher.derivatives_processor do |original|
199
+ magick = ImageProcessing::MiniMagick.source(original)
200
+
201
+ {
202
+ + square: magick.resize_to_fill!(150, 150),
203
+ small: magick.resize_to_limit!(300, 300),
204
+ medium: magick.resize_to_limit!(600, 600),
205
+ large: magick.resize_to_limit!(800, 800),
206
+ }
207
+ end
208
+ ```
209
+
210
+ We can now run following script to add the new derivative for all existing
211
+ records. It fetches the records in batches, downloads attachments on permanent
212
+ storage, creates the new derivative, and persists the changes.
213
+
214
+ ```rb
215
+ Photo.find_each do |photo|
216
+ attacher = photo.image_attacher
217
+
218
+ next unless attacher.stored?
219
+
220
+ square = attacher.file.download do |original|
221
+ ImageProcessor::MiniMagick
222
+ .source(original)
223
+ .resize_to_fill!(150, 150)
224
+ end
225
+
226
+ attacher.add_derivative(:square, square)
227
+
228
+ begin
229
+ attacher.atomic_persist # persist changes if attachment has not changed in the meantime
230
+ rescue Shrine::AttachmentChanged, # attachment has changed
231
+ ActiveRecord::RecordNotFound # record has been deleted
232
+ attacher.derivatives[:square].delete # delete now orphaned derivative
233
+ end
234
+ end
235
+ ```
236
+
237
+ Now all attachments should have the new derivative and you can start generating
238
+ URLs for it.
239
+
240
+ ## Removing derivatives
241
+
242
+ *Scenario: A derivative isn't being used anymore, so we want to delete it for
243
+ existing attachments.*
244
+
245
+ Let's assume we've made the following change and have deployed it to production:
246
+
247
+ ```diff
248
+ Attacher.derivatives_processor do |original|
249
+ magick = ImageProcessing::MiniMagick.source(original)
250
+
251
+ {
252
+ - square: magick.resize_to_fill!(150, 150),
253
+ small: magick.resize_to_limit!(300, 300),
254
+ medium: magick.resize_to_limit!(600, 600),
255
+ large: magick.resize_to_limit!(800, 800),
256
+ }
257
+ end
258
+ ```
259
+
260
+ We can now run following script to remove the unused derivative for all
261
+ existing record. It fetches the records in batches, removes and deletes the
262
+ unused derivative, and persists the changes.
263
+
264
+ ```rb
265
+ Photo.find_each do |photo|
266
+ attacher = photo.image_attacher
267
+
268
+ next unless attacher.derivatives.key?(:square)
269
+
270
+ attacher.remove_derivative(:square, delete: true)
271
+
272
+ begin
273
+ attacher.atomic_persist # persist changes if attachment has not changed in the meantime
274
+ rescue Shrine::AttachmentChanged, # attachment has changed
275
+ ActiveRecord::RecordNotFound # record has been deleted
276
+ end
277
+ end
278
+ ```
279
+
280
+ ## Backgrounding
281
+
282
+ For faster migration, we can also delay any of the operations above into a
283
+ background job:
284
+
285
+ ```rb
286
+ Photo.find_each do |photo|
287
+ attacher = photo.image_attacher
288
+
289
+ next unless attacher.stored?
290
+
291
+ MakeChangeJob.perform_later(
292
+ attacher.class,
293
+ attacher.record,
294
+ attacher.name,
295
+ attacher.file_data,
296
+ )
297
+ end
298
+ ```
299
+ ```rb
300
+ class MakeChangeJob < ActiveJob::Base
301
+ def perform(attacher_class, record, name, file_data)
302
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
303
+ # ... make our change ...
304
+ end
305
+ end
306
+ ```
307
+
308
+ [derivatives]: /doc/plugins/derivatives.md#readme