shrine 2.19.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +83 -979
  5. data/doc/advantages.md +231 -204
  6. data/doc/attacher.md +304 -153
  7. data/doc/carrierwave.md +297 -226
  8. data/doc/changing_derivatives.md +308 -0
  9. data/doc/changing_location.md +103 -21
  10. data/doc/changing_storage.md +110 -0
  11. data/doc/creating_persistence_plugins.md +132 -0
  12. data/doc/creating_plugins.md +43 -23
  13. data/doc/creating_storages.md +19 -5
  14. data/doc/design.md +147 -97
  15. data/doc/direct_s3.md +38 -28
  16. data/doc/external/articles.md +63 -0
  17. data/doc/external/extensions.md +53 -0
  18. data/doc/external/misc.md +32 -0
  19. data/doc/getting_started.md +1156 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +93 -30
  22. data/doc/paperclip.md +384 -262
  23. data/doc/plugins/activerecord.md +177 -46
  24. data/doc/plugins/add_metadata.md +139 -38
  25. data/doc/plugins/atomic_helpers.md +217 -0
  26. data/doc/plugins/backgrounding.md +156 -98
  27. data/doc/plugins/cached_attachment_data.md +7 -5
  28. data/doc/plugins/column.md +121 -0
  29. data/doc/plugins/data_uri.md +23 -22
  30. data/doc/plugins/default_storage.md +36 -10
  31. data/doc/plugins/default_url.md +30 -13
  32. data/doc/plugins/delete_raw.md +4 -2
  33. data/doc/plugins/derivation_endpoint.md +186 -101
  34. data/doc/plugins/derivatives.md +839 -0
  35. data/doc/plugins/determine_mime_type.md +4 -2
  36. data/doc/plugins/download_endpoint.md +64 -8
  37. data/doc/plugins/dynamic_storage.md +5 -3
  38. data/doc/plugins/entity.md +263 -0
  39. data/doc/plugins/form_assign.md +55 -0
  40. data/doc/plugins/included.md +31 -8
  41. data/doc/plugins/infer_extension.md +21 -10
  42. data/doc/plugins/instrumentation.md +38 -16
  43. data/doc/plugins/keep_files.md +16 -17
  44. data/doc/plugins/metadata_attributes.md +42 -13
  45. data/doc/plugins/mirroring.md +118 -0
  46. data/doc/plugins/model.md +210 -0
  47. data/doc/plugins/module_include.md +4 -2
  48. data/doc/plugins/multi_cache.md +24 -0
  49. data/doc/plugins/persistence.md +101 -0
  50. data/doc/plugins/presign_endpoint.md +9 -4
  51. data/doc/plugins/pretty_location.md +16 -3
  52. data/doc/plugins/processing.md +4 -2
  53. data/doc/plugins/rack_file.md +8 -2
  54. data/doc/plugins/rack_response.md +6 -2
  55. data/doc/plugins/recache.md +4 -2
  56. data/doc/plugins/refresh_metadata.md +49 -9
  57. data/doc/plugins/remote_url.md +84 -47
  58. data/doc/plugins/remove_attachment.md +27 -6
  59. data/doc/plugins/remove_invalid.md +21 -6
  60. data/doc/plugins/restore_cached_data.md +11 -3
  61. data/doc/plugins/sequel.md +159 -35
  62. data/doc/plugins/signature.md +16 -5
  63. data/doc/plugins/store_dimensions.md +14 -2
  64. data/doc/plugins/tempfile.md +4 -2
  65. data/doc/plugins/type_predicates.md +96 -0
  66. data/doc/plugins/upload_endpoint.md +13 -13
  67. data/doc/plugins/upload_options.md +6 -4
  68. data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
  69. data/doc/plugins/validation.md +97 -0
  70. data/doc/plugins/validation_helpers.md +16 -13
  71. data/doc/plugins/versions.md +15 -19
  72. data/doc/processing.md +438 -221
  73. data/doc/refile.md +188 -170
  74. data/doc/release_notes/1.0.0.md +4 -0
  75. data/doc/release_notes/1.1.0.md +6 -2
  76. data/doc/release_notes/1.2.0.md +4 -0
  77. data/doc/release_notes/1.3.0.md +4 -0
  78. data/doc/release_notes/1.4.0.md +4 -0
  79. data/doc/release_notes/1.4.1.md +4 -0
  80. data/doc/release_notes/1.4.2.md +4 -0
  81. data/doc/release_notes/2.0.0.md +4 -0
  82. data/doc/release_notes/2.0.1.md +4 -0
  83. data/doc/release_notes/2.1.0.md +5 -1
  84. data/doc/release_notes/2.1.1.md +4 -0
  85. data/doc/release_notes/2.10.0.md +4 -0
  86. data/doc/release_notes/2.10.1.md +4 -0
  87. data/doc/release_notes/2.11.0.md +4 -0
  88. data/doc/release_notes/2.12.0.md +4 -0
  89. data/doc/release_notes/2.13.0.md +4 -0
  90. data/doc/release_notes/2.14.0.md +5 -1
  91. data/doc/release_notes/2.15.0.md +11 -7
  92. data/doc/release_notes/2.16.0.md +4 -0
  93. data/doc/release_notes/2.17.0.md +4 -0
  94. data/doc/release_notes/2.18.0.md +4 -0
  95. data/doc/release_notes/2.19.0.md +6 -3
  96. data/doc/release_notes/2.2.0.md +4 -0
  97. data/doc/release_notes/2.3.0.md +4 -0
  98. data/doc/release_notes/2.3.1.md +4 -0
  99. data/doc/release_notes/2.4.0.md +4 -0
  100. data/doc/release_notes/2.4.1.md +4 -0
  101. data/doc/release_notes/2.5.0.md +4 -0
  102. data/doc/release_notes/2.6.0.md +4 -0
  103. data/doc/release_notes/2.6.1.md +4 -0
  104. data/doc/release_notes/2.7.0.md +4 -0
  105. data/doc/release_notes/2.8.0.md +4 -0
  106. data/doc/release_notes/2.9.0.md +4 -0
  107. data/doc/release_notes/3.0.0.md +981 -0
  108. data/doc/release_notes/3.0.1.md +22 -0
  109. data/doc/release_notes/3.1.0.md +73 -0
  110. data/doc/release_notes/3.2.0.md +96 -0
  111. data/doc/release_notes/3.2.1.md +31 -0
  112. data/doc/release_notes/3.2.2.md +14 -0
  113. data/doc/release_notes/3.3.0.md +105 -0
  114. data/doc/release_notes/3.4.0.md +35 -0
  115. data/doc/release_notes/3.5.0.md +63 -0
  116. data/doc/release_notes/3.6.0.md +23 -0
  117. data/doc/retrieving_uploads.md +5 -2
  118. data/doc/securing_uploads.md +60 -37
  119. data/doc/storage/file_system.md +20 -3
  120. data/doc/storage/memory.md +19 -0
  121. data/doc/storage/s3.md +122 -78
  122. data/doc/testing.md +141 -133
  123. data/doc/upgrading_to_3.md +708 -0
  124. data/doc/validation.md +54 -90
  125. data/lib/shrine/attacher.rb +292 -169
  126. data/lib/shrine/attachment.rb +13 -46
  127. data/lib/shrine/plugins/_persistence.rb +93 -0
  128. data/lib/shrine/plugins/activerecord.rb +77 -34
  129. data/lib/shrine/plugins/add_metadata.rb +25 -17
  130. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  131. data/lib/shrine/plugins/backgrounding.rb +77 -113
  132. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  133. data/lib/shrine/plugins/column.rb +102 -0
  134. data/lib/shrine/plugins/data_uri.rb +38 -36
  135. data/lib/shrine/plugins/default_storage.rb +45 -15
  136. data/lib/shrine/plugins/default_url.rb +12 -24
  137. data/lib/shrine/plugins/default_url_options.rb +3 -30
  138. data/lib/shrine/plugins/delete_raw.rb +10 -16
  139. data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
  140. data/lib/shrine/plugins/derivatives.rb +645 -0
  141. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  142. data/lib/shrine/plugins/download_endpoint.rb +118 -133
  143. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  144. data/lib/shrine/plugins/entity.rb +158 -0
  145. data/lib/shrine/plugins/form_assign.rb +108 -0
  146. data/lib/shrine/plugins/included.rb +6 -6
  147. data/lib/shrine/plugins/infer_extension.rb +17 -20
  148. data/lib/shrine/plugins/instrumentation.rb +59 -43
  149. data/lib/shrine/plugins/keep_files.rb +3 -15
  150. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  151. data/lib/shrine/plugins/mirroring.rb +142 -0
  152. data/lib/shrine/plugins/model.rb +160 -0
  153. data/lib/shrine/plugins/module_include.rb +3 -3
  154. data/lib/shrine/plugins/multi_cache.rb +27 -0
  155. data/lib/shrine/plugins/presign_endpoint.rb +27 -28
  156. data/lib/shrine/plugins/pretty_location.rb +15 -9
  157. data/lib/shrine/plugins/processing.rb +22 -9
  158. data/lib/shrine/plugins/rack_file.rb +2 -42
  159. data/lib/shrine/plugins/rack_response.rb +21 -10
  160. data/lib/shrine/plugins/recache.rb +6 -5
  161. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  162. data/lib/shrine/plugins/remote_url.rb +49 -49
  163. data/lib/shrine/plugins/remove_attachment.rb +12 -6
  164. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  165. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  166. data/lib/shrine/plugins/sequel.rb +86 -36
  167. data/lib/shrine/plugins/signature.rb +10 -16
  168. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  169. data/lib/shrine/plugins/tempfile.rb +1 -3
  170. data/lib/shrine/plugins/type_predicates.rb +113 -0
  171. data/lib/shrine/plugins/upload_endpoint.rb +28 -24
  172. data/lib/shrine/plugins/upload_options.rb +14 -15
  173. data/lib/shrine/plugins/url_options.rb +31 -0
  174. data/lib/shrine/plugins/validation.rb +80 -0
  175. data/lib/shrine/plugins/validation_helpers.rb +35 -58
  176. data/lib/shrine/plugins/versions.rb +107 -87
  177. data/lib/shrine/plugins.rb +22 -0
  178. data/lib/shrine/storage/file_system.rb +46 -64
  179. data/lib/shrine/storage/linter.rb +42 -7
  180. data/lib/shrine/storage/memory.rb +49 -0
  181. data/lib/shrine/storage/s3.rb +173 -160
  182. data/lib/shrine/uploaded_file.rb +32 -32
  183. data/lib/shrine/version.rb +3 -3
  184. data/lib/shrine.rb +87 -150
  185. data/shrine.gemspec +11 -12
  186. metadata +92 -82
  187. data/doc/migrating_storage.md +0 -76
  188. data/doc/plugins/backup.md +0 -31
  189. data/doc/plugins/copy.md +0 -24
  190. data/doc/plugins/delete_promoted.md +0 -12
  191. data/doc/plugins/direct_upload.md +0 -172
  192. data/doc/plugins/hooks.md +0 -58
  193. data/doc/plugins/logging.md +0 -42
  194. data/doc/plugins/migration_helpers.md +0 -60
  195. data/doc/plugins/moving.md +0 -19
  196. data/doc/plugins/multi_delete.md +0 -20
  197. data/doc/plugins/parallelize.md +0 -16
  198. data/doc/plugins/parsed_json.md +0 -23
  199. data/doc/regenerating_versions.md +0 -143
  200. data/lib/shrine/plugins/background_helpers.rb +0 -5
  201. data/lib/shrine/plugins/backup.rb +0 -90
  202. data/lib/shrine/plugins/copy.rb +0 -50
  203. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  204. data/lib/shrine/plugins/direct_upload.rb +0 -217
  205. data/lib/shrine/plugins/hooks.rb +0 -90
  206. data/lib/shrine/plugins/logging.rb +0 -142
  207. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  208. data/lib/shrine/plugins/moving.rb +0 -57
  209. data/lib/shrine/plugins/multi_delete.rb +0 -32
  210. data/lib/shrine/plugins/parallelize.rb +0 -78
  211. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -0,0 +1,839 @@
1
+ ---
2
+ title: Derivatives
3
+ ---
4
+
5
+ The [`derivatives`][derivatives] plugin allows storing processed files ("derivatives") alongside
6
+ the main attached file. The processed file data will be saved together with the
7
+ main attachment data in the same record attribute.
8
+
9
+ ```rb
10
+ Shrine.plugin :derivatives
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ You'll usually want to create derivatives from an attached file. The simplest
16
+ way to do this is to define a processor which returns the processed files, and
17
+ then trigger it when you want to create derivatives.
18
+
19
+ Here is an example of generating image thumbnails:
20
+
21
+ ```rb
22
+ # Gemfile
23
+ gem "image_processing", "~> 1.8"
24
+ ```
25
+ ```rb
26
+ require "image_processing/mini_magick"
27
+
28
+ class ImageUploader < Shrine
29
+ Attacher.derivatives do |original|
30
+ magick = ImageProcessing::MiniMagick.source(original)
31
+
32
+ {
33
+ small: magick.resize_to_limit!(300, 300),
34
+ medium: magick.resize_to_limit!(500, 500),
35
+ large: magick.resize_to_limit!(800, 800),
36
+ }
37
+ end
38
+ end
39
+ ```
40
+ ```rb
41
+ photo = Photo.new(image: file)
42
+ photo.image_derivatives! # creates derivatives
43
+ photo.save
44
+ ```
45
+
46
+ You can then retrieve the URL of a processed derivative:
47
+
48
+ ```rb
49
+ photo.image_url(:large) #=> "https://s3.amazonaws.com/path/to/large.jpg"
50
+ ```
51
+
52
+ The derivatives data is stored in the `<attachment>_data` column alongside the
53
+ main file:
54
+
55
+ ```rb
56
+ photo.image_data #=>
57
+ # {
58
+ # "id": "path/to/original.jpg",
59
+ # "store": "store",
60
+ # "metadata": { ... },
61
+ # "derivatives": {
62
+ # "small": { "id": "path/to/small.jpg", "storage": "store", "metadata": { ... } },
63
+ # "medium": { "id": "path/to/medium.jpg", "storage": "store", "metadata": { ... } },
64
+ # "large": { "id": "path/to/large.jpg", "storage": "store", "metadata": { ... } },
65
+ # }
66
+ # }
67
+ ```
68
+
69
+ And they can be retrieved as `Shrine::UploadedFile` objects:
70
+
71
+ ```rb
72
+ photo.image(:large) #=> #<Shrine::UploadedFile id="path/to/large.jpg" storage=:store metadata={...}>
73
+ photo.image(:large).url #=> "https://s3.amazonaws.com/path/to/large.jpg"
74
+ photo.image(:large).size #=> 5825949
75
+ photo.image(:large).mime_type #=> "image/jpeg"
76
+ ```
77
+
78
+ ## Retrieving derivatives
79
+
80
+ The list of stored derivatives can be retrieved with `#<name>_derivatives`:
81
+
82
+ ```rb
83
+ photo.image_derivatives #=>
84
+ # {
85
+ # small: #<Shrine::UploadedFile ...>,
86
+ # medium: #<Shrine::UploadedFile ...>,
87
+ # large: #<Shrine::UploadedFile ...>,
88
+ # }
89
+ ```
90
+
91
+ A specific derivative can be retrieved in any of the following ways:
92
+
93
+ ```rb
94
+ photo.image_derivatives[:small] #=> #<Shrine::UploadedFile ...>
95
+ photo.image_derivatives(:small) #=> #<Shrine::UploadedFile ...>
96
+ photo.image(:small) #=> #<Shrine::UploadedFile ...>
97
+ ```
98
+
99
+ Or with nested derivatives:
100
+
101
+ ```rb
102
+ photo.image_derivatives #=> { thumbnail: { small: ..., medium: ..., large: ... } }
103
+
104
+ photo.image_derivatives.dig(:thumbnail, :small) #=> #<Shrine::UploadedFile ...>
105
+ photo.image_derivatives(:thumbnail, :small) #=> #<Shrine::UploadedFile ...>
106
+ photo.image(:thumbnails, :small) #=> #<Shrine::UploadedFile ...>
107
+ ```
108
+
109
+ ### Derivative URL
110
+
111
+ You can retrieve the URL of a derivative URL with `#<name>_url`:
112
+
113
+ ```rb
114
+ photo.image_url(:small) #=> "https://example.com/small.jpg"
115
+ photo.image_url(:medium) #=> "https://example.com/medium.jpg"
116
+ photo.image_url(:large) #=> "https://example.com/large.jpg"
117
+ ```
118
+
119
+ For nested derivatives you can pass multiple keys:
120
+
121
+ ```rb
122
+ photo.image_derivatives #=> { thumbnail: { small: ..., medium: ..., large: ... } }
123
+
124
+ photo.image_url(:thumbnail, :medium) #=> "https://example.com/medium.jpg"
125
+ ```
126
+
127
+ By default, `#<name>_url` method will return `nil` if derivative is not found.
128
+ You can use the [`default_url`][default_url] plugin to set up URL fallbacks:
129
+
130
+ ```rb
131
+ Attacher.default_url do |derivative: nil, **|
132
+ "/fallbacks/#{derivative}.jpg" if derivative
133
+ end
134
+ ```
135
+ ```rb
136
+ photo.image_url(:medium) #=> "https://example.com/fallbacks/medium.jpg"
137
+ ```
138
+
139
+ Any additional URL options passed to `#<name>_url` will be forwarded to the
140
+ storage:
141
+
142
+ ```rb
143
+ photo.image_url(:small, response_content_disposition: "attachment")
144
+ ```
145
+
146
+ You can also retrieve the derivative URL via `UploadedFile#url`:
147
+
148
+ ```rb
149
+ photo.image_derivatives[:large].url
150
+ ```
151
+
152
+ ## Attacher API
153
+
154
+ The derivatives API is primarily defined on the `Shrine::Attacher` class, with
155
+ some important methods also being exposed through the `Shrine::Attachment`
156
+ module.
157
+
158
+ Here is a model example with equivalent attacher code:
159
+
160
+ ```rb
161
+ photo.image_derivatives!(:thumbnails)
162
+ photo.image_derivatives #=> { ... }
163
+
164
+ photo.image_url(:large) #=> "https://..."
165
+ photo.image(:large) #=> #<Shrine::UploadedFile ...>
166
+ ```
167
+ ```rb
168
+ attacher.create_derivatives(:thumbnails)
169
+ attacher.get_derivatives #=> { ... }
170
+
171
+ attacher.url(:large) #=> "https://..."
172
+ attacher.get(:large) #=> "#<Shrine::UploadedFile>"
173
+ ```
174
+
175
+ ## Creating derivatives
176
+
177
+ By default, the `Attacher#create_derivatives` method downloads the attached
178
+ file, calls the processor, uploads results to attacher's permanent storage, and
179
+ saves uploaded files on the attacher.
180
+
181
+ ```rb
182
+ attacher.file #=> #<Shrine::UploadedFile id="original.jpg" storage=:store ...>
183
+ attacher.create_derivatives # calls default processor and uploads results
184
+ attacher.derivatives #=>
185
+ # {
186
+ # small: #<Shrine::UploadedFile id="small.jpg" storage=:store ...>,
187
+ # medium: #<Shrine::UploadedFile id="medium.jpg" storage=:store ...>,
188
+ # large: #<Shrine::UploadedFile id="large.jpg" storage=:store ...>,
189
+ # }
190
+ ```
191
+
192
+ Any additional arguments are forwarded to
193
+ [`Attacher#process_derivatives`](#processing-derivatives):
194
+
195
+ ```rb
196
+ attacher.create_derivatives(different_source) # pass a different source file
197
+ attacher.create_derivatives(foo: "bar") # pass custom options to the processor
198
+ ```
199
+
200
+ ### Create on promote
201
+
202
+ You can also have derivatives created automatically on promotion:
203
+
204
+ ```rb
205
+ Shrine.plugin :derivatives, create_on_promote: true
206
+ ```
207
+ ```rb
208
+ attacher.assign(file)
209
+ attacher.finalize # creates derivatives on promotion
210
+ attacher.derivatives #=> { small: ..., medium: ..., large: ... }
211
+ ```
212
+
213
+ ### Naming processors
214
+
215
+ If you want to have multiple processors for an uploader, you can assign each
216
+ processor a name:
217
+
218
+ ```rb
219
+ class ImageUploader < Shrine
220
+ Attacher.derivatives :thumbnails do |original|
221
+ { large: ..., medium: ..., small: ... }
222
+ end
223
+
224
+ Attacher.derivatives :crop do |original|
225
+ { cropped: ... }
226
+ end
227
+ end
228
+ ```
229
+
230
+ Then when creating derivatives you can specify the name of the desired
231
+ processor. New derivatives will be merged with any existing ones.
232
+
233
+ ```rb
234
+ attacher.create_derivatives(:thumbnails)
235
+ attacher.derivatives #=> { large: ..., medium: ..., small: ... }
236
+
237
+ attacher.create_derivatives(:crop)
238
+ attacher.derivatives #=> { large: ..., medium: ..., small: ..., cropped: ... }
239
+ ```
240
+
241
+ ### Derivatives storage
242
+
243
+ By default, derivatives are uploaded to the permanent storage of the attacher.
244
+ You can change the destination storage by passing `:storage` to the creation
245
+ call:
246
+
247
+ ```rb
248
+ attacher.create_derivatives(storage: :cache) # will be promoted together with main file
249
+ attacher.create_derivatives(storage: :other_store)
250
+ ```
251
+
252
+ You can also change the default destination storage with the `:storage` plugin
253
+ option:
254
+
255
+ ```rb
256
+ plugin :derivatives, storage: :other_store
257
+ ```
258
+
259
+ The storage can be dynamic based on the derivative name:
260
+
261
+ ```rb
262
+ plugin :derivatives, storage: -> (derivative) do
263
+ if derivative == :thumb
264
+ :thumbnail_store
265
+ else
266
+ :store
267
+ end
268
+ end
269
+ ```
270
+
271
+ You can also set this option with `Attacher.derivatives_storage`:
272
+
273
+ ```rb
274
+ Attacher.derivatives_storage :other_store
275
+ # or
276
+ Attacher.derivatives_storage do |derivative|
277
+ if derivative == :thumb
278
+ :thumbnail_store
279
+ else
280
+ :store
281
+ end
282
+ end
283
+ ```
284
+
285
+ The storage block is evaluated in the context of a `Shrine::Attacher` instance:
286
+
287
+ ```rb
288
+ Attacher.derivatives_storage do |derivative|
289
+ self #=> #<Shrine::Attacher>
290
+
291
+ file #=> #<Shrine::UploadedFile>
292
+ record #=> #<Photo>
293
+ name #=> :image
294
+ context #=> { ... }
295
+
296
+ # ...
297
+ end
298
+ ```
299
+
300
+ ### Nesting derivatives
301
+
302
+ Derivatives can be nested to any level, using both hashes and arrays, but the
303
+ top-level object must be a hash.
304
+
305
+ ```rb
306
+ Attacher.derivatives :tiff do |original|
307
+ {
308
+ thumbnail: {
309
+ small: small,
310
+ medium: medium,
311
+ large: large,
312
+ },
313
+ layers: [
314
+ layer_1,
315
+ layer_2,
316
+ # ...
317
+ ]
318
+ }
319
+ end
320
+ ```
321
+ ```rb
322
+ attacher.derivatives #=>
323
+ # {
324
+ # thumbnail: {
325
+ # small: #<Shrine::UploadedFile ...>,
326
+ # medium: #<Shrine::UploadedFile ...>,
327
+ # large: #<Shrine::UploadedFile ...>,
328
+ # },
329
+ # layers: [
330
+ # #<Shrine::UploadedFile ...>,
331
+ # #<Shrine::UploadedFile ...>,
332
+ # # ...
333
+ # ]
334
+ # }
335
+ ```
336
+
337
+ ## Processing derivatives
338
+
339
+ A derivatives processor block takes the original file, and is expected to
340
+ return a hash of processed files (it can be [nested](#nesting-derivatives)).
341
+
342
+ ```rb
343
+ Attacher.derivatives :my_processor do |original|
344
+ # return a hash of processed files
345
+ end
346
+ ```
347
+
348
+ The `Attacher#create_derivatives` method internally calls
349
+ `Attacher#process_derivatives`, which in turn calls the processor:
350
+
351
+ ```rb
352
+ files = attacher.process_derivatives(:my_processor)
353
+ attacher.add_derivatives(files)
354
+ ```
355
+
356
+ ### Dynamic processing
357
+
358
+ The processor block is evaluated in context of the `Shrine::Attacher` instance,
359
+ which allows you to change your processing logic based on the record data.
360
+
361
+ ```rb
362
+ Attacher.derivatives :my_processor do |original|
363
+ self #=> #<Shrine::Attacher>
364
+
365
+ file #=> #<Shrine::UploadedFile>
366
+ record #=> #<Photo>
367
+ name #=> :image
368
+ context #=> { ... }
369
+
370
+ # ...
371
+ end
372
+ ```
373
+
374
+ Moreover, any options passed to `Attacher#process_derivatives` will be
375
+ forwarded to the processor:
376
+
377
+ ```rb
378
+ attacher.process_derivatives(:my_processor, foo: "bar")
379
+ ```
380
+ ```rb
381
+ Attacher.derivatives :my_processor do |original, **options|
382
+ options #=> { :foo => "bar" }
383
+ # ...
384
+ end
385
+ ```
386
+
387
+ ### Source file
388
+
389
+ By default, the `Attacher#process_derivatives` method will download the
390
+ attached file and pass it to the processor:
391
+
392
+ ```rb
393
+ Attacher.derivatives :my_processor do |original|
394
+ original #=> #<File:...>
395
+ # ...
396
+ end
397
+ ```
398
+ ```rb
399
+ attacher.process_derivatives(:my_processor) # downloads attached file and passes it to the processor
400
+ ```
401
+
402
+ If you want to use a different source file, you can pass it in to the process
403
+ call. Typically you'd pass a local file on disk. If you pass a
404
+ `Shrine::UploadedFile` object or another IO-like object, it will be
405
+ automatically downloaded/copied to a local TempFile on disk.
406
+
407
+ ```rb
408
+ # named processor:
409
+ attacher.process_derivatives(:my_processor, source_file)
410
+
411
+ # default processor:
412
+ attacher.process_derivatives(source_file)
413
+ ```
414
+
415
+ If you want to call multiple processors in a row with the same source file, you
416
+ can use this to avoid re-downloading the same source file each time:
417
+
418
+ ```rb
419
+ attacher.file.download do |original|
420
+ attacher.process_derivatives(:thumbnails, original)
421
+ attacher.process_derivatives(:colors, original)
422
+ end
423
+ ```
424
+
425
+ If a processor might not always need a local source file, you avoid a
426
+ potentially expensive download/copy by registering the processor with
427
+ `download: false`, in which case the source file will be passed to the
428
+ processor as is.
429
+
430
+ ```rb
431
+ Attacher.derivatives :my_processor, download: false do |source|
432
+ source #=> Could be File, Shrine::UploadedFile, or other IO-like object
433
+ shrine_class.with_file(source) do |file|
434
+ # can force download/copy if necessary with `with_file`,
435
+ end
436
+ end
437
+ ```
438
+
439
+ ## Adding derivatives
440
+
441
+ If you already have processed files that you want to save, you can do that with
442
+ `Attacher#add_derivatives`:
443
+
444
+ ```rb
445
+ attacher.add_derivatives({
446
+ one: file_1,
447
+ two: file_2,
448
+ # ...
449
+ })
450
+
451
+ attacher.derivatives #=>
452
+ # {
453
+ # one: #<Shrine::UploadedFile>,
454
+ # two: #<Shrine::UploadedFile>,
455
+ # ...
456
+ # }
457
+ ```
458
+
459
+ New derivatives will be merged with existing ones:
460
+
461
+ ```rb
462
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
463
+ attacher.add_derivatives({ two: two_file })
464
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
465
+ ```
466
+
467
+ The merging is deep, so the following will work as well:
468
+
469
+ ```rb
470
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
471
+ attacher.add_derivatives({ nested: { two: two_file } })
472
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
473
+ ```
474
+
475
+ For adding a single derivative, you can also use the singular
476
+ `Attacher#add_derivative`:
477
+
478
+ ```rb
479
+ attacher.add_derivative(:thumb, thumbnail_file)
480
+ ```
481
+
482
+ > Note that new derivatives will replace any existing derivatives living under
483
+ the same key, but won't delete them. If this is your case, make sure to save a
484
+ reference to the old derivatives before assigning new ones, and then delete
485
+ them after persisting the change.
486
+
487
+ Any options passed to `Attacher#add_derivative(s)` will be forwarded to
488
+ [`Attacher#upload_derivatives`](#uploading-derivatives).
489
+
490
+ ```rb
491
+ attacher.add_derivative(:thumb, thumbnail_file, storage: :thumbnails_store) # specify destination storage
492
+ attacher.add_derivative(:thumb, thumbnail_file, upload_options: { acl: "public-read" }) # pass uploader options
493
+ ```
494
+
495
+ The `Attacher#add_derivative(s)` methods are thread-safe.
496
+
497
+ ## Uploading derivatives
498
+
499
+ If you want to upload processed files without setting them, you can use
500
+ `Attacher#upload_derivatives`:
501
+
502
+ ```rb
503
+ derivatives = attacher.upload_derivatives({
504
+ one: file_1,
505
+ two: file_2,
506
+ # ...
507
+ })
508
+
509
+ derivatives #=>
510
+ # {
511
+ # one: #<Shrine::UploadedFile>,
512
+ # two: #<Shrine::UploadedFile>,
513
+ # ...
514
+ # }
515
+ ```
516
+
517
+ For uploading a single derivative, you can also use the singular
518
+ `Attacher#upload_derivative`:
519
+
520
+ ```rb
521
+ attacher.upload_derivative(:thumb, thumbnail_file)
522
+ #=> #<Shrine::UploadedFile>
523
+ ```
524
+
525
+ ### Uploader options
526
+
527
+ You can specify the destination storage by passing `:storage` option to
528
+ `Attacher#upload_derivative(s)`. This will override the [default derivatives
529
+ storage](#derivatives-storage) setting.
530
+
531
+ ```rb
532
+ attacher.upload_derivative(:thumb, thumnbail_file, storage: :other_store)
533
+ #=> #<Shrine::UploadedFile @id="thumb.jpg" @storage_key=:other_store ...>
534
+ ```
535
+
536
+ Any other options will be forwarded to the uploader:
537
+
538
+ ```rb
539
+ attacher.upload_derivative :thumb, thumbnail_file,
540
+ upload_options: { acl: "public-read" },
541
+ metadata: { "foo" => "bar" }),
542
+ location: "path/to/derivative"
543
+ ```
544
+
545
+ The `:derivative` name is automatically passed to the uploader:
546
+
547
+ ```rb
548
+ class MyUploader < Shrine
549
+ plugin :add_metadata
550
+
551
+ add_metadata :md5 do |io, derivative: nil, **|
552
+ calculate_signature(io, :md5) unless derivative
553
+ end
554
+
555
+ def generate_location(io, derivative: nil, **)
556
+ "location/for/#{derivative}"
557
+ end
558
+
559
+ plugin :upload_options, store: -> (io, derivative: nil, **) {
560
+ { acl: "public-read" } if derivative
561
+ }
562
+ end
563
+ ```
564
+
565
+ ### File deletion
566
+
567
+ Files given to `Attacher#upload_derivative(s)` are assumed to be temporary, so
568
+ for convenience they're automatically closed and unlinked after upload.
569
+
570
+ If you want to disable this behaviour, pass `delete: false`:
571
+
572
+ ```rb
573
+ attacher.upload_derivative(:thumb, thumbnail_file, delete: false)
574
+ ```
575
+
576
+ ## Merging derivatives
577
+
578
+ If you want to save already uploaded derivatives, you can use
579
+ `Attacher#merge_derivatives`:
580
+
581
+ ```rb
582
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
583
+ attacher.merge_derivatives attacher.upload_derivatives({ two: two_file })
584
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
585
+ ```
586
+
587
+ This does a deep merge, so the following will work as well:
588
+
589
+ ```rb
590
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
591
+ attacher.merge_derivatives attacher.upload_derivatives({ nested: { two: two_file } })
592
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
593
+ ```
594
+
595
+ > Note that new derivatives will replace any existing derivatives living under
596
+ the same key, but won't delete them. If this is your case, make sure to save a
597
+ reference to the old derivatives before assigning new ones, and then delete
598
+ them after persisting the change.
599
+
600
+ The `Attacher#merge_derivatives` method is thread-safe.
601
+
602
+ ### Setting derivatives
603
+
604
+ If instead of adding you want to *override* existing derivatives, you can use
605
+ `Attacher#set_derivatives`:
606
+
607
+ ```rb
608
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
609
+ attacher.set_derivatives attacher.upload_derivatives({ two: two_file })
610
+ attacher.derivatives #=> { two: #<Shrine::UploadedFile> }
611
+ ```
612
+
613
+ If you're using the [`model`][model] plugin, this method will trigger writing
614
+ derivatives data into the column attribute.
615
+
616
+ ## Promoting derivatives
617
+
618
+ Any assigned derivatives that are uploaded to temporary storage will be
619
+ automatically uploaded to permanent storage on `Attacher#promote`.
620
+
621
+ ```rb
622
+ attacher.derivatives[:one].storage_key #=> :cache
623
+ attacher.promote
624
+ attacher.derivatives[:one].storage_key #=> :store
625
+ ```
626
+
627
+ If you want more control over derivatives promotion, you can use
628
+ `Attacher#promote_derivatives`. Any additional options passed to it are
629
+ forwarded to the uploader.
630
+
631
+ ```rb
632
+ attacher.derivatives[:one].storage_key #=> :cache
633
+ attacher.promote_derivatives(upload_options: { acl: "public-read" })
634
+ attacher.derivatives[:one].storage_key #=> :store
635
+ ```
636
+
637
+ ## Removing derivatives
638
+
639
+ If you want to manually remove certain derivatives, you can do that with
640
+ `Attacher#remove_derivative`.
641
+
642
+ ```rb
643
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
644
+ attacher.remove_derivative(:two) #=> #<Shrine::UploadedFile> (removed derivative)
645
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
646
+ ```
647
+
648
+ You can also use the plural `Attacher#remove_derivatives` for removing multiple
649
+ derivatives:
650
+
651
+ ```rb
652
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }
653
+ attacher.remove_derivative(:two, :three) #=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)
654
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
655
+ ```
656
+
657
+ It's possible to remove nested derivatives as well:
658
+
659
+ ```rb
660
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
661
+ attacher.remove_derivative([:nested, :one]) #=> #<Shrine::UploadedFile> (removed derivative)
662
+ attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
663
+ ```
664
+
665
+ The removed derivatives are not automatically deleted, because it's safer to
666
+ first persist the removal change, and only then perform the deletion.
667
+
668
+ ```rb
669
+ derivative = attacher.remove_derivative(:two)
670
+ # ... persist removal change ...
671
+ derivative.delete
672
+ ```
673
+
674
+ If you still want to delete the derivative at the time of removal, you can
675
+ pass `delete: true`:
676
+
677
+ ```rb
678
+ derivative = attacher.remove_derivative(:two, delete: true)
679
+ derivative.exists? #=> false
680
+ ```
681
+
682
+ ### Deleting derivatives
683
+
684
+ If you want to delete a collection of derivatives, you can use
685
+ `Attacher#delete_derivatives`:
686
+
687
+ ```rb
688
+ derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
689
+
690
+ attacher.delete_derivatives(derivatives)
691
+
692
+ derivatives[:one].exists? #=> false
693
+ derivatives[:two].exists? #=> false
694
+ ```
695
+
696
+ Without arguments `Attacher#delete_derivatives` deletes current derivatives:
697
+
698
+ ```rb
699
+ attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
700
+
701
+ attacher.delete_derivatives
702
+
703
+ attacher.derivatives[:one].exists? #=> false
704
+ attacher.derivatives[:two].exists? #=> false
705
+ ```
706
+
707
+ Derivatives are automatically deleted on `Attacher#destroy`.
708
+
709
+ ## Miscellaneous
710
+
711
+ ### Without original
712
+
713
+ You can store derivatives even if there is no main attached file:
714
+
715
+ ```rb
716
+ attacher.file #=> nil
717
+ attacher.add_derivatives({ one: one_file, two: two_file })
718
+ attacher.data #=>
719
+ # {
720
+ # "derivatives" => {
721
+ # "one" => { "id" => "...", "storage" => "...", "metadata": { ... } },
722
+ # "two" => { "id" => "...", "storage" => "...", "metadata": { ... } },
723
+ # }
724
+ # }
725
+ ```
726
+
727
+ However, note that in this case operations such as promotion and deletion will
728
+ not be automatically triggered in the attachment flow, you'd need to trigger
729
+ them manually as needed.
730
+
731
+ ### Iterating derivatives
732
+
733
+ If you want to iterate over a nested hash of derivatives (which can be
734
+ `Shrine::UploadedFile` objects or raw files), you can use
735
+ `Attacher#map_derivative` or `Shrine.map_derivative`:
736
+
737
+ ```rb
738
+ derivatives #=>
739
+ # {
740
+ # one: #<Shrine::UploadedFile>,
741
+ # two: { three: #<Shrine::UploadedFile> },
742
+ # four: [#<Shrine::UploadedFile>],
743
+ # }
744
+
745
+ # or Shrine.map_derivative
746
+ attacher.map_derivative(derivatives) do |name, file|
747
+ puts "#{name}, #{file}"
748
+ end
749
+
750
+ # output:
751
+ #
752
+ # :one, #<Shrine::UploadedFile>
753
+ # [:two, :three], #<Shrine::UploadedFile>
754
+ # [:four, 0], #<Shrine::UploadedFile>
755
+ ```
756
+
757
+ ### Parsing derivatives
758
+
759
+ If you want to directly parse derivatives data written to a record attribute,
760
+ you can use `Shrine.derivatives` (counterpart to `Shrine.uploaded_file`):
761
+
762
+ ```rb
763
+ # or MyUploader.derivatives
764
+ derivatives = Shrine.derivatives({
765
+ "one" => { "id" => "...", "storage" => "...", "metadata" => { ... } },
766
+ "two" => { "three" => { "id" => "...", "storage" => "...", "metadata" => { ... } } }
767
+ "four" => [{ "id" => "...", "storage" => "...", "metadata" => { ... } }]
768
+ })
769
+
770
+ derivatives #=>
771
+ # {
772
+ # one: #<Shrine::UploadedFile>,
773
+ # two: { three: #<Shrine::UploadedFile> },
774
+ # four: [#<Shrine::UploadedFile>],
775
+ # }
776
+ ```
777
+
778
+ Like `Shrine.uploaded_file`, the `Shrine.derivatives` method accepts data as a
779
+ hash (stringified or symbolized) or a JSON string.
780
+
781
+ ### Marshalling
782
+
783
+ The `Attacher` instance uses a mutex to make `Attacher#merge_derivatives`
784
+ thread-safe, which is not marshallable. If you want to be able to marshal the
785
+ attacher instance, you can skip mutex usage:
786
+
787
+ ```rb
788
+ plugin :derivatives, mutex: false
789
+ ```
790
+
791
+ ## Instrumentation
792
+
793
+ If the `instrumentation` plugin has been loaded, the `derivatives` plugin adds
794
+ instrumentation around derivatives processing.
795
+
796
+ ```rb
797
+ # instrumentation plugin needs to be loaded *before* derivatives
798
+ plugin :instrumentation
799
+ plugin :derivatives
800
+ ```
801
+
802
+ Processing derivatives will trigger a `derivatives.shrine` event with the
803
+ following payload:
804
+
805
+ | Key | Description |
806
+ | :-- | :---- |
807
+ | `:processor` | Name of the derivatives processor |
808
+ | `:processor_options` | Any options passed to the processor |
809
+ | `:io` | The source file passed to the processor |
810
+ | `:attacher` | The attacher instance doing the processing |
811
+ | `:uploader` | The uploader class that sent the event |
812
+
813
+ A default log subscriber is added as well which logs these events:
814
+
815
+ ```
816
+ Derivatives (2133ms) – {:processor=>:thumbnails, :processor_options=>{}, :uploader=>ImageUploader}
817
+ ```
818
+
819
+ You can also use your own log subscriber:
820
+
821
+ ```rb
822
+ plugin :derivatives, log_subscriber: -> (event) {
823
+ Shrine.logger.info JSON.generate(name: event.name, duration: event.duration, **event.payload)
824
+ }
825
+ ```
826
+ ```
827
+ {"name":"derivatives","duration":2133,"processor":"thumbnails","processor_options":{},"io":"#<File:...>","uploader":"ImageUploader"}
828
+ ```
829
+
830
+ Or disable logging altogether:
831
+
832
+ ```rb
833
+ plugin :derivatives, log_subscriber: nil
834
+ ```
835
+
836
+ [derivatives]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/derivatives.rb
837
+ [default_url]: https://shrinerb.com/docs/plugins/default_url
838
+ [entity]: https://shrinerb.com/docs/plugins/entity
839
+ [model]: https://shrinerb.com/docs/plugins/model