shrine 2.19.4 → 3.0.0.alpha

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -11
  3. data/README.md +9 -3
  4. data/doc/advantages.md +1 -1
  5. data/doc/carrierwave.md +4 -4
  6. data/doc/creating_persistence_plugins.md +172 -0
  7. data/doc/creating_plugins.md +1 -1
  8. data/doc/creating_storages.md +3 -1
  9. data/doc/design.md +2 -2
  10. data/doc/direct_s3.md +0 -22
  11. data/doc/paperclip.md +3 -3
  12. data/doc/plugins/activerecord.md +211 -42
  13. data/doc/plugins/atomic_helpers.md +153 -0
  14. data/doc/plugins/column.md +90 -0
  15. data/doc/plugins/derivation_endpoint.md +54 -62
  16. data/doc/plugins/derivatives.md +752 -0
  17. data/doc/plugins/entity.md +204 -0
  18. data/doc/plugins/infer_extension.md +8 -8
  19. data/doc/plugins/instrumentation.md +33 -13
  20. data/doc/plugins/keep_files.md +5 -15
  21. data/doc/plugins/model.md +157 -0
  22. data/doc/plugins/presign_endpoint.md +2 -1
  23. data/doc/plugins/refresh_metadata.md +44 -7
  24. data/doc/plugins/sequel.md +190 -33
  25. data/doc/plugins/{default_url_options.md → url_options.md} +5 -5
  26. data/doc/processing.md +1 -1
  27. data/doc/release_notes/1.1.0.md +2 -2
  28. data/doc/release_notes/2.15.0.md +1 -1
  29. data/doc/storage/s3.md +2 -2
  30. data/doc/testing.md +1 -1
  31. data/lib/shrine.rb +72 -138
  32. data/lib/shrine/attacher.rb +272 -176
  33. data/lib/shrine/attachment.rb +2 -42
  34. data/lib/shrine/plugins/activerecord.rb +103 -26
  35. data/lib/shrine/plugins/add_metadata.rb +9 -10
  36. data/lib/shrine/plugins/atomic_helpers.rb +111 -0
  37. data/lib/shrine/plugins/attacher_options.rb +55 -0
  38. data/lib/shrine/plugins/backgrounding.rb +147 -115
  39. data/lib/shrine/plugins/cached_attachment_data.rb +6 -9
  40. data/lib/shrine/plugins/column.rb +104 -0
  41. data/lib/shrine/plugins/data_uri.rb +35 -38
  42. data/lib/shrine/plugins/default_storage.rb +18 -12
  43. data/lib/shrine/plugins/default_url.rb +11 -21
  44. data/lib/shrine/plugins/default_url_options.rb +3 -30
  45. data/lib/shrine/plugins/delete_raw.rb +9 -13
  46. data/lib/shrine/plugins/derivation_endpoint.rb +75 -114
  47. data/lib/shrine/plugins/derivatives.rb +576 -0
  48. data/lib/shrine/plugins/determine_mime_type.rb +3 -15
  49. data/lib/shrine/plugins/download_endpoint.rb +83 -131
  50. data/lib/shrine/plugins/dynamic_storage.rb +4 -8
  51. data/lib/shrine/plugins/entity.rb +128 -0
  52. data/lib/shrine/plugins/form_assign.rb +107 -0
  53. data/lib/shrine/plugins/included.rb +4 -3
  54. data/lib/shrine/plugins/infer_extension.rb +10 -17
  55. data/lib/shrine/plugins/instrumentation.rb +45 -25
  56. data/lib/shrine/plugins/keep_files.rb +2 -12
  57. data/lib/shrine/plugins/metadata_attributes.rb +15 -14
  58. data/lib/shrine/plugins/model.rb +137 -0
  59. data/lib/shrine/plugins/module_include.rb +2 -0
  60. data/lib/shrine/plugins/presign_endpoint.rb +1 -15
  61. data/lib/shrine/plugins/pretty_location.rb +5 -5
  62. data/lib/shrine/plugins/processing.rb +21 -6
  63. data/lib/shrine/plugins/rack_file.rb +1 -39
  64. data/lib/shrine/plugins/rack_response.rb +14 -7
  65. data/lib/shrine/plugins/recache.rb +5 -2
  66. data/lib/shrine/plugins/refresh_metadata.rb +12 -8
  67. data/lib/shrine/plugins/remote_url.rb +44 -53
  68. data/lib/shrine/plugins/remove_attachment.rb +7 -2
  69. data/lib/shrine/plugins/remove_invalid.rb +8 -4
  70. data/lib/shrine/plugins/restore_cached_data.rb +12 -4
  71. data/lib/shrine/plugins/sequel.rb +115 -27
  72. data/lib/shrine/plugins/signature.rb +2 -7
  73. data/lib/shrine/plugins/store_dimensions.rb +13 -27
  74. data/lib/shrine/plugins/upload_endpoint.rb +14 -15
  75. data/lib/shrine/plugins/upload_options.rb +9 -8
  76. data/lib/shrine/plugins/url_options.rb +33 -0
  77. data/lib/shrine/plugins/validation.rb +87 -0
  78. data/lib/shrine/plugins/validation_helpers.rb +33 -54
  79. data/lib/shrine/plugins/versions.rb +106 -84
  80. data/lib/shrine/storage/file_system.rb +32 -57
  81. data/lib/shrine/storage/linter.rb +9 -1
  82. data/lib/shrine/storage/memory.rb +42 -0
  83. data/lib/shrine/storage/s3.rb +38 -146
  84. data/lib/shrine/uploaded_file.rb +22 -29
  85. data/lib/shrine/version.rb +4 -4
  86. data/shrine.gemspec +2 -3
  87. metadata +27 -54
  88. data/doc/plugins/backup.md +0 -31
  89. data/doc/plugins/copy.md +0 -24
  90. data/doc/plugins/delete_promoted.md +0 -12
  91. data/doc/plugins/direct_upload.md +0 -172
  92. data/doc/plugins/hooks.md +0 -58
  93. data/doc/plugins/logging.md +0 -42
  94. data/doc/plugins/migration_helpers.md +0 -60
  95. data/doc/plugins/moving.md +0 -19
  96. data/doc/plugins/multi_delete.md +0 -20
  97. data/doc/plugins/parallelize.md +0 -16
  98. data/doc/plugins/parsed_json.md +0 -23
  99. data/lib/shrine/plugins/background_helpers.rb +0 -5
  100. data/lib/shrine/plugins/backup.rb +0 -90
  101. data/lib/shrine/plugins/copy.rb +0 -50
  102. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  103. data/lib/shrine/plugins/direct_upload.rb +0 -217
  104. data/lib/shrine/plugins/hooks.rb +0 -90
  105. data/lib/shrine/plugins/logging.rb +0 -142
  106. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  107. data/lib/shrine/plugins/moving.rb +0 -57
  108. data/lib/shrine/plugins/multi_delete.rb +0 -32
  109. data/lib/shrine/plugins/parallelize.rb +0 -78
  110. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -0,0 +1,90 @@
1
+ # Column
2
+
3
+ The [`column`][column] plugin provides interface for serializing and
4
+ deserializing attachment data in format suitable for persisting in a database
5
+ column (JSON by default).
6
+
7
+ ```rb
8
+ plugin :column
9
+ ```
10
+
11
+ ## Serializing
12
+
13
+ The `Attacher#column_data` method returns attached file data in serialized
14
+ format, ready to be persisted into a database column.
15
+
16
+ ```rb
17
+ attacher.attach(io)
18
+ attacher.column_data #=> '{"id":"...","storage":"...","metadata":{...}}'
19
+ ```
20
+
21
+ If there is no attached file, `nil` is returned.
22
+
23
+ ```rb
24
+ attacher.column_data #=> nil
25
+ ```
26
+
27
+ If you want to retrieve this data as a Hash, use `Attacher#data` instead.
28
+
29
+ ## Deserializing
30
+
31
+ The `Attacher.from_column` method instantiates the attacher from serialized
32
+ attached file data.
33
+
34
+ ```rb
35
+ attacher = Shrine::Attacher.from_column('{"id":"...","storage":"...","metadata":{...}}')
36
+ attacher.file #=> #<Shrine::UploadedFile>
37
+ ```
38
+
39
+ If `nil` is given, it means no attached file.
40
+
41
+ ```rb
42
+ attacher = Shrine::Attacher.from_column(nil)
43
+ attacher.file #=> nil
44
+ ```
45
+
46
+ Any additional options are forwarded to `Attacher#initialize`.
47
+
48
+ ```rb
49
+ attacher = Shrine::Attacher.from_column('{...}', store: :other_store)
50
+ attacher.store_key #=> :other_store
51
+ ```
52
+
53
+ If you want to load attachment data into an existing attacher, use
54
+ `Attacher#load_column`.
55
+
56
+ ```rb
57
+ attacher.file #=> nil
58
+ attacher.load_column('{"id":"...","storage":"...","metadata":{...}}')
59
+ attacher.file #=> #<Shrine::UploadedFile>
60
+ ```
61
+
62
+ If you want to load attachment from a Hash, use `Attacher.from_data` or
63
+ `Attacher#load_data` instead.
64
+
65
+ ## Serializer
66
+
67
+ By default the `JSON` standard library is used as the serializer, but you can
68
+ use your own serializer. The serializer object needs to implement `#dump` and
69
+ `#load` methods.
70
+
71
+ ```rb
72
+ require "oj"
73
+
74
+ plugin :column, serializer: Oj # use custom serializer
75
+ ```
76
+
77
+ If you want to disable serialization, you can set serializer to `nil`.
78
+
79
+ ```rb
80
+ plugin :column, serializer: nil # disable serialization
81
+ ```
82
+
83
+ You can also change the serializer on the attacher level:
84
+
85
+ ```rb
86
+ Shrine::Attacher.new(column_serializer: Oj) # use custom serializer
87
+ Shrine::Attacher.new(column_serializer: nil) # disable serialization
88
+ ```
89
+
90
+ [column]: /lib/shrine/plugins/column.rb
@@ -76,7 +76,7 @@ Now we can generate "derivation" URLs from attached files, which on request
76
76
  will call the derivation block we defined.
77
77
 
78
78
  ```rb
79
- photo.image.derivation_url(:thumbnail, "600", "400")
79
+ photo.image.derivation_url(:thumbnail, 600, 400)
80
80
  #=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
81
81
  ```
82
82
 
@@ -102,10 +102,14 @@ derivation URLs, preventing potential DoS attacks.
102
102
 
103
103
  The derivation endpoint then extracts the source file data, derivation name and
104
104
  arguments from the request URL, and calls the corresponding derivation block,
105
- passing the downloaded source file and derivation arguments.
105
+ passing the downloaded source file and derivation arguments. The derivation
106
+ block is evaluated within the context of a
107
+ [`Shrine::Derivation`](#derivation-api) instance.
106
108
 
107
109
  ```rb
108
110
  derivation :thumbnail do |file, arg1, arg2, ...|
111
+ self #=> #<Shrine::Derivation>
112
+
109
113
  file #=> #<Tempfile:...> (source file downloaded to disk)
110
114
  arg1 #=> "600" (first derivation argument in #derivation_url)
111
115
  arg2 #=> "400" (second derivation argument in #derivation_url)
@@ -494,11 +498,9 @@ sense.
494
498
 
495
499
  ### Deleting derivatives
496
500
 
497
- When you use the `:upload` options and upload the derivatives to storage, there
498
- will come a time when you need to delete the derivatives because the original
499
- file is being replaced, deleted, or some other reason. In this case, you can
500
- delete the derivatives before deleting the record or updating the record with
501
- the new original file.
501
+ When the original attachment is deleted, its uploaded derivatives will not be
502
+ automatically deleted, you will need to do the deletion manually. You can do
503
+ that by calling `Shrine::Derivation#delete` for each derivation you're using:
502
504
 
503
505
  ```rb
504
506
  # photo is the model and image is the file attachment
@@ -538,50 +540,38 @@ uploaded_file.derivation_url(:thumbnail, version: 1)
538
540
 
539
541
  ## Accessing source file
540
542
 
541
- If you want to access the source `UploadedFile` object when deriving, you can
542
- set `:include_uploaded_file` to `true`.
543
+ Inside the derivation block we can access the source `UploadedFile` object via
544
+ `Shrine::Derivation#source`:
543
545
 
544
546
  ```rb
545
- plugin :derivation_endpoint, include_uploaded_file: true
546
- ```
547
-
548
- Now the source `UploadedFile` will be passed as the second argument of the
549
- derivation block:
550
-
551
- ```rb
552
- derivation :thumbnail do |file, uploaded_file, width, height|
553
- uploaded_file #=> #<Shrine::UploadedFile>
554
- uploaded_file.id #=> "9a7d1bfdad24a76f9cfaff137fe1b5c7.jpg"
555
- uploaded_file.storage_key #=> "store"
556
- uploaded_file.metadata #=> {}
547
+ derivation :thumbnail do |file, width, height|
548
+ source #=> #<Shrine::UploadedFile>
549
+ source.id #=> "9a7d1bfdad24a76f9cfaff137fe1b5c7.jpg"
550
+ source.storage_key #=> :store
551
+ source.metadata #=> {}
557
552
 
558
553
  # ...
559
554
  end
560
555
  ```
561
556
 
562
- By default original metadata that were extracted on attachment won't be
563
- available in the derivation block. This is because metadata we want to have
564
- available would need to be serialized into the derivation URL, which would make
565
- it longer. However, you can opt in for the metadata you need with the
566
- `:metadata` option:
557
+ By default, when using the derivation endpoint, original metadata of the source
558
+ file won't be available in the derivation block. This is because any metadata
559
+ we would want to have available would need to be serialized into the derivation
560
+ URL, which would make it longer. Instead, you can opt in for the metadata you
561
+ want to have available:
567
562
 
568
563
  ```rb
569
564
  plugin :derivation_endpoint, metadata: ["filename", "mime_type"]
570
- ```
571
-
572
- Now `filename` and `mime_type` metadata values will be available in the
573
- derivation block:
574
565
 
575
- ```rb
576
- derivation :thumbnail do |file, uploaded_file, width, height|
577
- uploaded_file.metadata #=>
566
+ derivation :thumbnail do |file, width, height|
567
+ source.metadata #=>
578
568
  # {
579
569
  # "filename" => "nature.jpg",
580
570
  # "mime_type" => "image/jpeg"
581
571
  # }
582
572
 
583
- uploaded_file.original_filename #=> "nature.jpg"
584
- uploaded_file.mime_type #=> "image/jpeg"
573
+ source.original_filename #=> "nature.jpg"
574
+ source.mime_type #=> "image/jpeg"
585
575
 
586
576
  # ...
587
577
  end
@@ -602,17 +592,9 @@ plugin :derivation_endpoint, download_options: {
602
592
  }
603
593
  ```
604
594
 
605
- If the source file has been deleted, the error the storage raises when
606
- attempting to download it will be propagated by default. For
607
- `Shrine.derivation_endpoint` and `Shrine.derivation_response` you can have
608
- these errors converted to 404 responses by adding them to `:download_errors`:
609
-
610
- ```rb
611
- plugin :derivation_endpoint, download_errors: [
612
- Errno::ENOENT, # raised by Shrine::Storage::FileSystem
613
- Aws::S3::Errors::NotFound, # raised by Shrine::Storage::S3
614
- ]
615
- ```
595
+ If the source file was not found, `Shrine::Derivation::SourceNotFound`
596
+ exception is raised. In a derivation response this is converted into a `404 Not
597
+ Found` response.
616
598
 
617
599
  ### Skipping download
618
600
 
@@ -621,15 +603,8 @@ disk for you, you can set `:download` to `false`.
621
603
 
622
604
  ```rb
623
605
  plugin :derivation_endpoint, download: false
624
- ```
625
-
626
- In this case the `UploadedFile` object is yielded to the derivation block
627
- instead of the raw file:
628
-
629
- ```rb
630
- derivation :thumbnail do |uploaded_file, width, height|
631
- uploaded_file #=> #<Shrine::UploadedFile>
632
606
 
607
+ derivation :thumbnail do |width, height| # source file is not downloaded
633
608
  # ...
634
609
  end
635
610
  ```
@@ -639,10 +614,10 @@ One use case for this is delegating processing to a 3rd-party service:
639
614
  ```rb
640
615
  require "down/http"
641
616
 
642
- derivation :thumbnail do |uploaded_file, width, height|
617
+ derivation :thumbnail do |width, height|
643
618
  # generate the thumbnail using ImageOptim.com
644
619
  down = Down::Http.new(method: :post)
645
- down.download("https://im2.io/<USERNAME>/#{width}x#{height}/#{uploaded_file.url}")
620
+ down.download("https://im2.io/<USERNAME>/#{width}x#{height}/#{source.url}")
646
621
  end
647
622
  ```
648
623
 
@@ -735,13 +710,20 @@ uploaded_file #=> #<Shrine::UploadedFile>
735
710
  uploaded_file.id #=> "bcfd0d67e4a8ec2dc9a6d7ddcf3825a1/thumbnail-500-500"
736
711
  ```
737
712
 
738
- If not given any arguments, it generates the derivative before uploading it.
713
+ It can also be called without arguments, in which case it will generate a new
714
+ derivative and upload it.
715
+
716
+ ```rb
717
+ derivation.upload # generates derivative and uploads it
718
+ ```
719
+
720
+ Any additional options will be passed to the uploader.
739
721
 
740
722
  ### `#retrieve`
741
723
 
742
- `Derivation#retrieve` method returns the uploaded derivative file. If the file
743
- exists on the storage, it returns an `UploadedFile` object, otherwise `nil` is
744
- returned.
724
+ `Derivation#retrieve` method returns `Shrine::UploadedFile` object pointing to
725
+ the uploaded derivative if it exists. If the uploaded derivative does not
726
+ exist, `nil` is returned.
745
727
 
746
728
  ```rb
747
729
  uploaded_file = derivation.retrieve
@@ -749,6 +731,18 @@ uploaded_file #=> #<Shrine::UploadedFile>
749
731
  uploaded_file.id #=> "bcfd0d67e4a8ec2dc9a6d7ddcf3825a1/thumbnail-500-500"
750
732
  ```
751
733
 
734
+ ### `#opened`
735
+
736
+ `Derivation#opened` method returns opened `Shrine::UploadedFile` object pointing
737
+ to the uploaded derivative if it exists. If the uploaded derivative does not
738
+ exist, `nil` is returned.
739
+
740
+ ```rb
741
+ uploaded_file = derivation.opened
742
+ uploaded_file #=> #<Shrine::UploadedFile>
743
+ uploaded_file.id #=> "bcfd0d67e4a8ec2dc9a6d7ddcf3825a1/thumbnail-500-500"
744
+ ```
745
+
752
746
  ### `#delete`
753
747
 
754
748
  `Derivation#delete` method deletes the uploaded derivative file from the
@@ -774,12 +768,10 @@ derivation.option(:upload_location)
774
768
  | `:cache_control` | Hash of directives for the `Cache-Control` response header | `{ public: true, max_age: 365*24*60*60 }` |
775
769
  | `:disposition` | Whether the browser should attempt to render the derivative (`inline`) or prompt the user to download the file to disk (`attachment`) | `inline` |
776
770
  | `:download` | Whether the source uploaded file should be downloaded to disk when the derivation block is called | `true` |
777
- | `:download_errors` | List of error classes that will be converted to a `404 Not Found` response by the derivation endpoint | `[]` |
778
771
  | `:download_options` | Additional options to pass when downloading the source uploaded file | `{}` |
779
772
  | `:expires_in` | Number of seconds after which the URL will not be available anymore | `nil` |
780
773
  | `:filename` | Filename the browser will assume when the derivative is downloaded to disk | `<name>-<args>-<source id basename>` |
781
774
  | `:host` | URL host to use when generated URLs | `nil` |
782
- | `:include_uploaded_file` | Whether to include the source uploaded file in the derivation block arguments | `false` |
783
775
  | `:metadata` | List of metadata keys the source uploaded file should include in the derivation block | `[]` |
784
776
  | `:prefix` | Path prefix added to the URLs | `nil` |
785
777
  | `:secret_key` | Key used to sign derivation URLs in order to prevent tampering | required |
@@ -0,0 +1,752 @@
1
+ # Derivatives
2
+
3
+ The derivatives plugin allows storing processed files ("derivatives") alongside
4
+ the main attached file. The processed file data will be saved together with the
5
+ main attachment data in the same record attribute.
6
+
7
+ ```rb
8
+ plugin :derivatives
9
+ ```
10
+
11
+ ## Contents
12
+
13
+ * [API overview](#api-overview)
14
+ * [Creating derivatives](#creating-derivatives)
15
+ - [Nesting derivatives](#nesting-derivatives)
16
+ * [Retrieving derivatives](#retrieving-derivatives)
17
+ * [Derivative URL](#derivative-url)
18
+ * [Processing derivatives](#processing-derivatives)
19
+ - [Dynamic processing](#dynamic-processing)
20
+ - [Source file](#source-file)
21
+ * [Adding derivatives](#adding-derivatives)
22
+ * [Uploading derivatives](#uploading-derivatives)
23
+ - [Derivatives storage](#derivatives-storage)
24
+ - [Uploader options](#uploader-options)
25
+ - [File deletion](#file-deletion)
26
+ * [Merging derivatives](#merging-derivatives)
27
+ - [Setting derivatives](#setting-derivatives)
28
+ * [Promoting derivatives](#promoting-derivatives)
29
+ * [Removing derivatives](#removing-derivatives)
30
+ * [Deleting derivatives](#deleting-derivatives)
31
+ * [Miscellaneous](#miscellaneous)
32
+ * [Without original](#without-original)
33
+ * [Iterating derivatives](#iterating-derivatives)
34
+ * [Parsing derivatives](#parsing-derivatives)
35
+ * [Instrumentation](#instrumentation)
36
+
37
+ ## API overview
38
+
39
+ The interface for managing derivatives is implemented on the `Shrine::Attacher`
40
+ class, and it's layered in the following way:
41
+
42
+ * [`Attacher#create_derivatives`](#creating-derivatives) – processes, uploads and merges derivatives
43
+ * [`Attacher#process_derivatives`](#processing-derivatives) – processes derivatives
44
+ * [`Attacher#add_derivatives`](#adding-derivatives) – uploads and merges derivatives
45
+ * [`Attacher#upload_derivatives`](#uploading-derivatives) – uploads derivatives
46
+ * [`Attacher#merge_derivatives`](#merging-derivatives) – merges derivatives
47
+ * [`Attacher#set_derivatives`](#setting-derivatives) – overrides derivatives
48
+
49
+ ## Creating derivatives
50
+
51
+ When you have a file attached, you can generate derivatives from it and save
52
+ them alongside the attached file. The simplest way to do this is to define a
53
+ processor which returns the processed files, and then trigger it with
54
+ `Attacher#create_derivatives` when you want to generate the derivatives.
55
+
56
+ Here is an example of generating image thumbnails:
57
+
58
+ ```rb
59
+ # Gemfile
60
+ gem "image_processing", "~> 1.2"
61
+ ```
62
+ ```rb
63
+ require "image_processing/mini_magick"
64
+
65
+ class ImageUploader < Shrine
66
+ plugin :derivatives
67
+
68
+ Attacher.derivatives_processor :thumbnails do |original|
69
+ processor = ImageProcessing::MiniMagick.source(original)
70
+
71
+ {
72
+ small: processor.resize_to_limit!(300, 300),
73
+ medium: processor.resize_to_limit!(500, 500),
74
+ large: processor.resize_to_limit!(800, 800),
75
+ }
76
+ end
77
+ end
78
+ ```
79
+ ```rb
80
+ class Photo < Model(:image_data)
81
+ include ImageUploader::Attachment(:image)
82
+ end
83
+ ```
84
+ ```rb
85
+ photo.image #=> #<Shrine::UploadedFile @id="original.jpg" @storage_key=:store ...>
86
+ photo.image_derivatives #=> {}
87
+
88
+ photo.image_attacher.create_derivatives(:thumbnails) # calls processor and uploads results
89
+ photo.image_derivatives #=>
90
+ # {
91
+ # small: #<Shrine::UploadedFile @id="small.jpg" @storage_key=:store ...>,
92
+ # medium: #<Shrine::UploadedFile @id="medium.jpg" @storage_key=:store ...>,
93
+ # large: #<Shrine::UploadedFile @id="large.jpg" @storage_key=:store ...>,
94
+ # }
95
+ ```
96
+
97
+ The derivatives data is stored in the `#<name>_data` record attribute alongside
98
+ the main file data:
99
+
100
+ ```rb
101
+ photo.image_data #=>
102
+ # {
103
+ # "id": "original.jpg",
104
+ # "store": "store",
105
+ # "metadata": { ... },
106
+ # "derivatives": {
107
+ # "small": { "id": "small.jpg", "storage": "store", "metadata": { ... } },
108
+ # "medium": { "id": "medium.jpg", "storage": "store", "metadata": { ... } },
109
+ # "large": { "id": "large.jpg", "storage": "store", "metadata": { ... } },
110
+ # }
111
+ # }
112
+ ```
113
+
114
+ Any additional options passed to `Attacher#create_derivatives` are forwarded to
115
+ [`Attacher#upload_derivatives`](#uploading-derivatives).
116
+
117
+ ```rb
118
+ attacher.create_derivatives(:thumbnails, storage: :other_store) # specify destination storage
119
+ attacher.create_derivatives(:thumbnails, upload_options: { acl: "public-read" }) # pass uploader options
120
+ ```
121
+
122
+ ### Nesting derivatives
123
+
124
+ Derivatives can be nested to any level, using both hashes and arrays, but the
125
+ top-level object must be a hash.
126
+
127
+ ```rb
128
+ Attacher.derivatives_processor :tiff do |original|
129
+ {
130
+ thumbnail: {
131
+ small: small,
132
+ medium: medium,
133
+ large: large,
134
+ },
135
+ layers: [
136
+ layer_1,
137
+ layer_2,
138
+ # ...
139
+ ]
140
+ }
141
+ end
142
+ ```
143
+
144
+ ## Retrieving derivatives
145
+
146
+ If you're using the `Shrine::Attachment` module, you can retrieve stored
147
+ derivatives by calling `#<name>_derivatives` on your model/entity.
148
+
149
+ ```rb
150
+ class Photo < Model(:image_data)
151
+ include ImageUploader::Attachment(:image)
152
+ end
153
+ ```
154
+ ```rb
155
+ photo.image_derivatives #=>
156
+ # {
157
+ # small: #<Shrine::UploadedFile>,
158
+ # medium: #<Shrine::UploadedFile>,
159
+ # large: #<Shrine::UploadedFile>,
160
+ # }
161
+ ```
162
+
163
+ A specific derivative can be retrieved in any of the following ways:
164
+
165
+ ```rb
166
+ photo.image_derivatives[:small] #=> #<Shrine::UploadedFile>
167
+ photo.image_derivatives(:small) #=> #<Shrine::UploadedFile>
168
+ photo.image(:small) #=> #<Shrine::UploadedFile>
169
+ ```
170
+
171
+ And with nested derivatives:
172
+
173
+ ```rb
174
+ photo.image_derivatives #=> { thumbnail: { small: ..., medium: ..., large: ... } }
175
+
176
+ photo.image_derivatives.dig(:thumbnail, :small) #=> #<Shrine::UploadedFile>
177
+ photo.image_derivatives(:thumbnail, :small) #=> #<Shrine::UploadedFile>
178
+ photo.image(:thumbnails :small) #=> #<Shrine::UploadedFile>
179
+ ```
180
+
181
+ When using `Shrine::Attacher` directly, you can retrieve derivatives using
182
+ `Attacher#derivatives`:
183
+
184
+ ```rb
185
+ attacher.derivatives #=>
186
+ # {
187
+ # small: #<Shrine::UploadedFile>,
188
+ # medium: #<Shrine::UploadedFile>,
189
+ # large: #<Shrine::UploadedFile>,
190
+ # }
191
+ ```
192
+
193
+ ## Derivative URL
194
+
195
+ If you're using the `Shrine::Attachment` module, you can use the `#<name>_url`
196
+ method to retrieve the URL of a derivative.
197
+
198
+ ```rb
199
+ class Photo < Model(:image_data)
200
+ include ImageUploader::Attachment(:image)
201
+ end
202
+ ```
203
+ ```rb
204
+ photo.image_url(:small) #=> "https://example.com/small.jpg"
205
+ photo.image_url(:medium) #=> "https://example.com/medium.jpg"
206
+ photo.image_url(:large) #=> "https://example.com/large.jpg"
207
+ ```
208
+
209
+ For nested derivatives you can pass multiple keys:
210
+
211
+ ```rb
212
+ photo.image_derivatives #=> { thumbnail: { small: ..., medium: ..., large: ... } }
213
+
214
+ photo.image_url(:thumbnail, :medium) #=> "https://example.com/medium.jpg"
215
+ ```
216
+
217
+ By default, `#<name>_url` method will return `nil` if derivative is not found.
218
+ You can use the [`default_url`][default_url] plugin to set up URL fallbacks:
219
+
220
+ ```rb
221
+ Attacher.default_url do |derivative: nil, **|
222
+ "https://fallbacks.com/#{derivative}.jpg" if derivative
223
+ end
224
+ ```
225
+ ```rb
226
+ photo.image_url(:medium) #=> "https://example.com/fallbacks.com/medium.jpg"
227
+ ```
228
+
229
+ Any additional URL options passed to `#<name>_url` will be forwarded to the
230
+ storage:
231
+
232
+ ```rb
233
+ photo.image_url(:small, response_content_disposition: "attachment")
234
+ ```
235
+
236
+ You can also retrieve the derivative URL via `UploadedFile#url`:
237
+
238
+ ```rb
239
+ photo.image_derivatives[:large].url
240
+ # or
241
+ attacher.derivatives[:large].url
242
+ ```
243
+
244
+ ## Processing derivatives
245
+
246
+ A derivatives processor block takes the original file, and is expected to
247
+ return a hash of processed files (it can be [nested](#nesting-derivatives)).
248
+
249
+ ```rb
250
+ Attacher.derivatives_processor :my_processor do |original|
251
+ # return a hash of processed files
252
+ end
253
+ ```
254
+
255
+ The [`Attacher#create_derivatives`](#creating-derivatives) method will call
256
+ the processor and upload results.
257
+
258
+ ```rb
259
+ attacher.create_derivatives(:my_processor)
260
+ ```
261
+
262
+ Internally this calls `Attacher#process_derivatives`, which calls the
263
+ processor and returns processed files:
264
+
265
+ ```rb
266
+ files = attacher.process_derivatives(:my_processor)
267
+ attacher.add_derivatives(files)
268
+ ```
269
+
270
+ ### Dynamic processing
271
+
272
+ The processor block is evaluated in context of the `Shrine::Attacher` instance,
273
+ which allows you to change your processing logic based on the record data.
274
+
275
+ ```rb
276
+ Attacher.derivatives_processor :my_processor do |original|
277
+ self #=> #<Shrine::Attacher>
278
+
279
+ record #=> #<Photo>
280
+ name #=> :image
281
+ context #=> { ... }
282
+
283
+ # ...
284
+ end
285
+ ```
286
+
287
+ Moreover, any options passed to `Attacher#process_derivatives` will be
288
+ forwarded to the processor:
289
+
290
+ ```rb
291
+ attacher.process_derivatives(:my_processor, foo: "bar")
292
+ ```
293
+ ```rb
294
+ Attacher.derivatives_processor :my_processor do |original, **options|
295
+ options #=> { :foo => "bar" }
296
+ # ...
297
+ end
298
+ ```
299
+
300
+ ### Source file
301
+
302
+ The `Attacher#process_derivatives` method will automatically download the
303
+ attached file and pass it to the processor:
304
+
305
+ ```rb
306
+ Attacher.derivatives_processor :my_processor do |original|
307
+ original #=> #<File:...>
308
+ # ...
309
+ end
310
+ ```
311
+ ```rb
312
+ attacher.process_derivatives(:my_processor) # downloads attached file and passes it to the processor
313
+ ```
314
+
315
+ If you already have the source file locally, or if you're calling multiple
316
+ processors in a row and want to avoid downloading the same source file each
317
+ time, you can pass the source file as the second argument to
318
+ `Attacher#process_derivatives`:
319
+
320
+ ```rb
321
+ # this way the source file is downloaded only once
322
+ attacher.file.download do |original|
323
+ attacher.process_derivatives(:thumbnails, original)
324
+ attacher.process_derivatives(:colors, original)
325
+ end
326
+ ```
327
+
328
+ ## Adding derivatives
329
+
330
+ If you already have processed files that you want to save, you can do that with
331
+ `Attacher#add_derivatives`:
332
+
333
+ ```rb
334
+ attacher.add_derivatives(
335
+ one: file_1,
336
+ two: file_2,
337
+ # ...
338
+ )
339
+
340
+ attacher.derivatives #=>
341
+ # {
342
+ # one: #<Shrine::UploadedFile>,
343
+ # two: #<Shrine::UploadedFile>,
344
+ # ...
345
+ # }
346
+ ```
347
+
348
+ New derivatives will be merged with existing ones:
349
+
350
+ ```rb
351
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
352
+ attacher.add_derivatives(two: two_file)
353
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
354
+ ```
355
+
356
+ The merging is deep, so the following will work as well:
357
+
358
+ ```rb
359
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
360
+ attacher.add_derivatives(nested: { two: two_file })
361
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
362
+ ```
363
+
364
+ For adding a single derivative, you can also use the singular
365
+ `Attacher#add_derivative`:
366
+
367
+ ```rb
368
+ attacher.add_derivative(:thumb, thumbnail_file)
369
+ ```
370
+
371
+ Any options passed to `Attacher#add_derivative(s)` will be forwarded to
372
+ [`Attacher#upload_derivatives`](#uploading-derivatives).
373
+
374
+ ```rb
375
+ attacher.add_derivative(:thumb, thumbnail_file, storage: :thumbnails_store) # specify destination storage
376
+ attacher.add_derivative(:thumb, thumbnail_file, upload_options: { acl: "public-read" }) # pass uploader options
377
+ ```
378
+
379
+ The `Attacher#add_derivative(s)` methods are thread-safe.
380
+
381
+ ## Uploading derivatives
382
+
383
+ If you want to upload processed files without setting them, you can use
384
+ `Attacher#upload_derivatives`:
385
+
386
+ ```rb
387
+ derivatives = attacher.upload_derivatives(
388
+ one: file_1,
389
+ two: file_2,
390
+ # ...
391
+ )
392
+
393
+ derivatives #=>
394
+ # {
395
+ # one: #<Shrine::UploadedFile>,
396
+ # two: #<Shrine::UploadedFile>,
397
+ # ...
398
+ # }
399
+ ```
400
+
401
+ For uploading a single derivative, you can also use the singular
402
+ `Attacher#upload_derivative`:
403
+
404
+ ```rb
405
+ attacher.upload_derivative(:thumb, thumbnail_file)
406
+ #=> #<Shrine::UploadedFile>
407
+ ```
408
+
409
+ ### Derivatives storage
410
+
411
+ By default, derivatives are uploaded to the permanent storage of the attacher
412
+ (`:store` by default). You can specify a different destination storage for
413
+ `Attacher#upload_derivative(s)` with the `:storage` option:
414
+
415
+ ```rb
416
+ attacher.upload_derivatives(derivatives, storage: :other_store)
417
+ ```
418
+
419
+ You can also set a default derivatives storage on the plugin level:
420
+
421
+ ```rb
422
+ plugin :derivatives, storage: :other_store
423
+ ```
424
+
425
+ The storage can be dynamic based on the derivative name:
426
+
427
+ ```rb
428
+ plugin :derivatives, storage: -> (derivative) do
429
+ if derivative == :thumb
430
+ :thumbnail_store
431
+ else
432
+ :store
433
+ end
434
+ end
435
+ ```
436
+
437
+ You can also set this option with `Attacher.derivatives_storage`:
438
+
439
+ ```rb
440
+ Attacher.derivatives_storage :other_store
441
+ # or
442
+ Attacher.derivatives_storage do |derivative|
443
+ if derivative == :thumb
444
+ :thumbnail_store
445
+ else
446
+ :store
447
+ end
448
+ end
449
+ ```
450
+
451
+ The storage block is evaluated in the context of a `Shrine::Attacher` instance:
452
+
453
+ ```rb
454
+ Attacher.derivatives_storage do |derivative|
455
+ self #=> #<Shrine::Attacher>
456
+
457
+ record #=> #<Photo>
458
+ name #=> :image
459
+ context #=> { ... }
460
+ end
461
+ ```
462
+
463
+ ### Uploader options
464
+
465
+ Any options other than `:storage` will be forwarded to the uploader:
466
+
467
+ ```rb
468
+ attacher.upload_derivative :thumb, thumbnail_file,
469
+ upload_options: { acl: "public-read" },
470
+ metadata: { "foo" => "bar" }),
471
+ location: "path/to/derivative"
472
+ ```
473
+
474
+ A `:derivative` option is automatically passed to the uploader and holds the
475
+ name of the derivative, which you can use when extracting metadata, generating
476
+ location or generating upload options:
477
+
478
+ ```rb
479
+ class MyUploader < Shrine
480
+ plugin :add_metadata
481
+
482
+ add_metadata :md5 do |io, derivative: nil, **|
483
+ calculate_signature(io, :md5) unless derivative
484
+ end
485
+
486
+ def generate_location(io, derivative: nil, **)
487
+ "location/for/#{derivative}"
488
+ end
489
+
490
+ plugin :upload_options, store: -> (io, derivative: nil, **) {
491
+ { acl: "public-read" } if derivative
492
+ }
493
+ end
494
+ ```
495
+
496
+ ### File deletion
497
+
498
+ Files given to `Attacher#upload_derivative(s)` are assumed to be temporary, so
499
+ for convenience they're automatically closed and unlinked after upload.
500
+
501
+ If you want to disable this behaviour, pass `delete: false`:
502
+
503
+ ```rb
504
+ attacher.upload_derivative(:thumb, thumbnail_file, delete: false)
505
+
506
+ File.exist?(thumbnail_file.path) #=> true
507
+ ```
508
+
509
+ ## Merging derivatives
510
+
511
+ If you want to save already uploaded derivatives, you can use
512
+ `Attacher#merge_derivatives`:
513
+
514
+ ```rb
515
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
516
+ attacher.merge_derivatives attacher.upload_derivatives(two: two_file)
517
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
518
+ ```
519
+
520
+ This does a deep merge, so the following will work as well:
521
+
522
+ ```rb
523
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
524
+ attacher.merge_derivatives attacher.upload_derivatives(nested: { two: two_file })
525
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
526
+ ```
527
+
528
+ The `Attacher#merge_derivatives` method is thread-safe.
529
+
530
+ ### Setting derivatives
531
+
532
+ If instead of adding you want to *override* existing derivatives, you can use
533
+ `Attacher#set_derivatives`:
534
+
535
+ ```rb
536
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
537
+ attacher.set_derivatives attacher.upload_derivatives(two: two_file)
538
+ attacher.derivatives #=> { two: #<Shrine::UploadedFile> }
539
+ ```
540
+
541
+ If you're using the [`model`][model] plugin, this method will trigger writing
542
+ derivatives data into the column attribute.
543
+
544
+ ## Promoting derivatives
545
+
546
+ Any assigned derivatives that are uploaded to temporary storage will be
547
+ automatically uploaded to permanent storage on `Attacher#promote`.
548
+
549
+ ```rb
550
+ attacher.derivatives[:one].storage_key #=> :cache
551
+ attacher.promote
552
+ attacher.derivatives[:one].storage_key #=> :store
553
+ ```
554
+
555
+ If you want more control over derivatives promotion, you can use
556
+ `Attacher#promote_derivatives`. Any additional options passed to it are
557
+ forwarded to the uploader.
558
+
559
+ ```rb
560
+ attacher.derivatives[:one].storage_key #=> :cache
561
+ attacher.promote_derivatives(upload_options: { acl: "public-read" })
562
+ attacher.derivatives[:one].storage_key #=> :store
563
+ ```
564
+
565
+ ## Removing derivatives
566
+
567
+ If you want to manually remove certain derivatives, you can do that with
568
+ `Attacher#remove_derivative`.
569
+
570
+ ```rb
571
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
572
+ attacher.remove_derivative(:two) #=> #<Shrine::UploadedFile> (removed derivative)
573
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
574
+ ```
575
+
576
+ You can also use the plural `Attacher#remove_derivatives` for removing multiple
577
+ derivatives:
578
+
579
+ ```rb
580
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }
581
+ attacher.remove_derivative(:two, :three) #=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)
582
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
583
+ ```
584
+
585
+ It's possible to remove nested derivatives as well:
586
+
587
+ ```rb
588
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
589
+ attacher.remove_derivative([:nested, :one]) #=> #<Shrine::UploadedFile> (removed derivative)
590
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
591
+ ```
592
+
593
+ The removed derivatives are not automatically deleted, because it's safer to
594
+ first persist the removal change, and only then perform the deletion.
595
+
596
+ ```rb
597
+ derivative = attacher.remove_derivative(:two)
598
+ # ... persist removal change ...
599
+ derivative.delete
600
+ ```
601
+
602
+ If you still want to delete the derivative at the time of removal, you can
603
+ pass `delete: true`:
604
+
605
+ ```rb
606
+ derivative = attacher.remove_derivative(:two, delete: true)
607
+ derivative.exists? #=> false
608
+ ```
609
+
610
+ ### Deleting derivatives
611
+
612
+ If you want to delete a collection of derivatives, you can use
613
+ `Attacher#delete_derivatives`:
614
+
615
+ ```rb
616
+ derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
617
+
618
+ attacher.delete_derivatives(derivatives)
619
+
620
+ derivatives[:one].exists? #=> false
621
+ derivatives[:two].exists? #=> false
622
+ ```
623
+
624
+ Without arguments `Attacher#delete_derivatives` deletes current derivatives:
625
+
626
+ ```rb
627
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
628
+
629
+ attacher.delete_derivatives
630
+
631
+ attacher.derivatives[:one].exists? #=> false
632
+ attacher.derivatives[:two].exists? #=> false
633
+ ```
634
+
635
+ Derivatives are automatically deleted on `Attacher#destroy`.
636
+
637
+ ## Without original
638
+
639
+ You can store derivatives even if there is no main attached file:
640
+
641
+ ```rb
642
+ attacher.file #=> nil
643
+ attacher.add_derivatives(one: one_file, two: two_file)
644
+ attacher.data #=>
645
+ # {
646
+ # "derivatives" => {
647
+ # "one" => { "id" => "...", "storage" => "...", "metadata": { ... } },
648
+ # "two" => { "id" => "...", "storage" => "...", "metadata": { ... } },
649
+ # }
650
+ # }
651
+ ```
652
+
653
+ However, note that in this case operations such as promotion and deletion will
654
+ not be automatically triggered in the attachment flow, you'd need to trigger
655
+ them manually as needed.
656
+
657
+ ## Iterating derivatives
658
+
659
+ If you want to iterate over a nested hash of derivatives (which can be
660
+ `Shrine::UploadedFile` objects or raw files), you can use
661
+ `Attacher#map_derivative` or `Shrine.map_derivative`:
662
+
663
+ ```rb
664
+ derivatives #=>
665
+ # {
666
+ # one: #<Shrine::UploadedFile>,
667
+ # two: { three: #<Shrine::UploadedFile> },
668
+ # four: [#<Shrine::UploadedFile>],
669
+ # }
670
+
671
+ # or Shrine.map_derivative
672
+ attacher.map_derivative(derivatives) do |name, file|
673
+ puts "#{name}, #{file}"
674
+ end
675
+
676
+ # output:
677
+ #
678
+ # :one, #<Shrine::UploadedFile>
679
+ # [:two, :three], #<Shrine::UploadedFile>
680
+ # [:four, 0], #<Shrine::UploadedFile>
681
+ ```
682
+
683
+ ## Parsing derivatives
684
+
685
+ If you want to directly parse derivatives data written to a record attribute,
686
+ you can use `Shrine.derivatives` (counterpart to `Shrine.uploaded_file`):
687
+
688
+ ```rb
689
+ # or MyUploader.derivatives
690
+ derivatives = Shrine.derivatives({
691
+ "one" => { "id" => "...", "storage" => "...", "metadata" => { ... } },
692
+ "two" => { "three" => { "id" => "...", "storage" => "...", "metadata" => { ... } } }
693
+ "four" => [{ "id" => "...", "storage" => "...", "metadata" => { ... } }]
694
+ })
695
+
696
+ derivatives #=>
697
+ # {
698
+ # one: #<Shrine::UploadedFile>,
699
+ # two: { three: #<Shrine::UploadedFile> },
700
+ # four: [#<Shrine::UploadedFile>],
701
+ # }
702
+ ```
703
+
704
+ Like `Shrine.uploaded_file`, the `Shrine.derivatives` method accepts data as a
705
+ hash (stringified or symbolized) or a JSON string.
706
+
707
+ ## Instrumentation
708
+
709
+ If the `instrumentation` plugin has been loaded, the `derivatives` plugin adds
710
+ instrumentation around derivatives processing.
711
+
712
+ ```rb
713
+ # instrumentation plugin needs to be loaded *before* derivatives
714
+ plugin :instrumentation
715
+ plugin :derivatives
716
+ ```
717
+
718
+ Processing derivatives will trigger a `derivatives.shrine` event with the
719
+ following payload:
720
+
721
+ | Key | Description |
722
+ | :-- | :---- |
723
+ | `:processor` | Name of the derivatives processor |
724
+ | `:processor_options` | Any options passed to the processor |
725
+ | `:uploader` | The uploader class that sent the event |
726
+
727
+ A default log subscriber is added as well which logs these events:
728
+
729
+ ```
730
+ Derivatives (2133ms) – {:processor=>:thumbnails, :processor_options=>{}, :uploader=>ImageUploader}
731
+ ```
732
+
733
+ You can also use your own log subscriber:
734
+
735
+ ```rb
736
+ plugin :derivatives, log_subscriber: -> (event) {
737
+ Shrine.logger.info JSON.generate(name: event.name, duration: event.duration, **event.payload)
738
+ }
739
+ ```
740
+ ```
741
+ {"name":"derivatives","duration":2133,"processor":"thumbnails","processor_options":{},"uploader":"ImageUploader"}
742
+ ```
743
+
744
+ Or disable logging altogether:
745
+
746
+ ```rb
747
+ plugin :derivatives, log_subscriber: nil
748
+ ```
749
+
750
+ [default_url]: /doc/plugins/default_url.md#readme
751
+ [entity]: /doc/plugins/entity.md#readme
752
+ [model]: /doc/plugins/model.md#readme