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,708 @@
1
+ ---
2
+ id: upgrading-to-3
3
+ title: Upgrading to Shrine 3.x
4
+ ---
5
+
6
+ This guide provides instructions for upgrading Shrine in your apps to version
7
+ 3.x. If you're looking for a full list of changes, see the **[3.0 release
8
+ notes]**.
9
+
10
+ ## Attacher
11
+
12
+ The `Shrine::Attacher` class has been rewritten in Shrine 3.0, though much of
13
+ the main API remained the same.
14
+
15
+ ### Model
16
+
17
+ The main change is that `Attacher.new` is now used for initializing the
18
+ attacher without a model:
19
+
20
+ ```rb
21
+ attacher = Shrine::Attacher.new
22
+ #=> #<Shrine::Attacher>
23
+
24
+ attacher = Shrine::Attacher.new(photo, :image)
25
+ # ~> ArgumentError: invalid number of arguments
26
+ ```
27
+
28
+ To initialize an attacher with a model, use `Attacher.from_model` provided by
29
+ the new [`model`][model] plugin (which is automatically loaded by
30
+ `activerecord` and `sequel` plugins):
31
+
32
+ ```rb
33
+ attacher = Shrine::Attacher.from_model(photo, :image)
34
+ # ...
35
+ ```
36
+
37
+ If you're using the `Shrine::Attachment` module with POROs, make sure to load
38
+ the `model` plugin.
39
+
40
+ ```rb
41
+ Shrine.plugin :model
42
+ ```
43
+ ```rb
44
+ class Photo < Struct.new(:image_data)
45
+ include Shrine::Attachment(:image)
46
+ end
47
+ ```
48
+
49
+ ### Data attribute
50
+
51
+ The `Attacher#read` method has been removed. If you want to generate serialized
52
+ attachment data, use `Attacher#column_data`. Otherwise if you want to generate
53
+ hash attachment data, use `Attacher#data`.
54
+
55
+ ```rb
56
+ attacher.column_data #=> '{"id":"...","storage":"...","metadata":{...}}'
57
+ attacher.data #=> { "id" => "...", "storage" => "...", "metadata" => { ... } }
58
+ ```
59
+
60
+ The `Attacher#data_attribute` has been renamed to `Attacher#attribute`.
61
+
62
+ ### State
63
+
64
+ The attacher now maintains its own state, so if you've previously modified the
65
+ `#<name>_data` record attribute and expected the changes to be picked up by the
66
+ attacher, you'll now need to call `Attacher#reload` for that:
67
+
68
+ ```rb
69
+ attacher.file #=> nil
70
+ record.image_data = '{"id":"...","storage":"...","metadata":{...}}'
71
+ attacher.file #=> nil
72
+ attacher.reload
73
+ attacher.file #=> #<Shrine::UploadedFile ...>
74
+ ```
75
+
76
+ ### Assigning
77
+
78
+ The `Attacher#assign` method now raises an exception when non-cached uploaded
79
+ file data is assigned:
80
+
81
+ ```rb
82
+ # Shrine 2.x
83
+ attacher.assign('{"id": "...", "storage": "store", "metadata": {...}}') # ignored
84
+
85
+ # Shrine 3.0
86
+ attacher.assign('{"id": "...", "storage": "store", "metadata": {...}}')
87
+ #~> Shrine::Error: expected cached file, got #<Shrine::UploadedFile storage=:store ...>
88
+ ```
89
+
90
+ ### Validation
91
+
92
+ The validation functionality has been extracted into the `validation` plugin.
93
+ If you're using the `validation_helpers` plugin, it will automatically load
94
+ `validation` for you. Otherwise you'll have to load it explicitly:
95
+
96
+ ```rb
97
+ Shrine.plugin :validation
98
+ ```
99
+ ```rb
100
+ class MyUploader < Shrine
101
+ Attacher.validate do
102
+ # ...
103
+ end
104
+ end
105
+ ```
106
+
107
+ ### Setting
108
+
109
+ The `Attacher#set` method has been renamed to `Attacher#change`, and the
110
+ private `Attacher#_set` method has been renamed to `Attacher#set` and made
111
+ public:
112
+
113
+ ```rb
114
+ attacher.change(uploaded_file) # sets file, remembers previous file, runs validations
115
+ attacher.set(uploaded_file) # sets file
116
+ ```
117
+
118
+ If you've previously used `Attacher#replace` directly to delete previous file,
119
+ it has now been renamed to `Attacher#destroy_previous`.
120
+
121
+ Also note that `Attacher#attached?` now returns whether a file is attached,
122
+ while `Attacher#changed?` continues to return whether the attachment has
123
+ changed.
124
+
125
+ ### Uploading and deleting
126
+
127
+ The `Attacher#store!` and `Attacher#cache!` methods have been removed, you
128
+ should now use `Attacher#upload` instead:
129
+
130
+ ```rb
131
+ attacher.upload(io) # uploads to permanent storage
132
+ attacher.upload(io, :cache) # uploads to temporary storage
133
+ attacher.upload(io, :other_store) # uploads to another storage
134
+ ```
135
+
136
+ The `Attacher#delete!` method has been removed as well, you should instead just
137
+ delete the file directly via `UploadedFile#delete`.
138
+
139
+ ### Promoting
140
+
141
+ If you were promoting manually, the `Attacher#promote` method will now only
142
+ save promoted file in memory, it won't persist the changes.
143
+
144
+ ```rb
145
+ attacher.promote
146
+ # ...
147
+ record.save # you need to persist the changes
148
+ ```
149
+
150
+ If you want the concurrenct-safe promotion with persistence, use the new
151
+ `Attacher#atomic_promote` method.
152
+
153
+ ```rb
154
+ attacher.atomic_promote
155
+ ```
156
+
157
+ The `Attacher#swap` method has been removed. If you were using it directly, you
158
+ can use `Attacher#set` and `Attacher#atomic_persist` instead:
159
+
160
+ ```rb
161
+ current_file = attacher.file
162
+ attacher.set(new_file)
163
+ attacher.atomic_persist(current_file)
164
+ ```
165
+
166
+ ## Backgrounding
167
+
168
+ The `backgrounding` plugin has been rewritten in Shrine 3.0 and has a new API.
169
+
170
+ ```rb
171
+ Shrine.plugin :backgrounding
172
+ Shrine::Attacher.promote_block do
173
+ PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
174
+ end
175
+ Shrine::Attacher.destroy_block do
176
+ DestroyJob.perform_async(self.class.name, data)
177
+ end
178
+ ```
179
+ ```rb
180
+ class PromoteJob
181
+ include Sidekiq::Worker
182
+
183
+ def perform(attacher_class, record_class, record_id, name, file_data)
184
+ attacher_class = Object.const_get(attacher_class)
185
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
186
+
187
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
188
+ attacher.atomic_promote
189
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
190
+ # attachment has changed or record has been deleted, nothing to do
191
+ end
192
+ end
193
+ ```
194
+ ```rb
195
+ class DestroyJob
196
+ include Sidekiq::Worker
197
+
198
+ def perform(attacher_class, data)
199
+ attacher_class = Object.const_get(attacher_class)
200
+
201
+ attacher = attacher_class.from_data(data)
202
+ attacher.destroy
203
+ end
204
+ end
205
+ ```
206
+
207
+ ### Dual support
208
+
209
+ When you're making the switch in production, there might still be jobs in the
210
+ queue that have the old argument format. So, we'll initially want to handle
211
+ both argument formats, and then switch to the new one once the jobs with old
212
+ format have been drained.
213
+
214
+ ```rb
215
+ class PromoteJob
216
+ include Sidekiq::Worker
217
+
218
+ def perform(*args)
219
+ if args.one?
220
+ file_data, (record_class, record_id), name, shrine_class =
221
+ args.first.values_at("attachment", "record", "name", "shrine_class")
222
+
223
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
224
+ attacher_class = Object.const_get(shrine_class)::Attacher
225
+ else
226
+ attacher_class, record_class, record_id, name, file_data = args
227
+
228
+ attacher_class = Object.const_get(attacher_class)
229
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
230
+ end
231
+
232
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
233
+ attacher.atomic_promote
234
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
235
+ # attachment has changed or record has been deleted, nothing to do
236
+ end
237
+ end
238
+ ```
239
+ ```rb
240
+ class DestroyJob
241
+ include Sidekiq::Worker
242
+
243
+ def perform(*args)
244
+ if args.one?
245
+ data, shrine_class = args.first.values_at("attachment", "shrine_class")
246
+
247
+ data = JSON.parse(data)
248
+ attacher_class = Object.const_get(shrine_class)::Attacher
249
+ else
250
+ attacher_class, data = args
251
+
252
+ attacher_class = Object.const_get(attacher_class)
253
+ end
254
+
255
+ attacher = attacher_class.from_data(data)
256
+ attacher.destroy
257
+ end
258
+ end
259
+ ```
260
+
261
+ ### Attacher backgrounding
262
+
263
+ In Shrine 2.x, `Attacher#_promote` and `Attacher#_delete` methods could be used
264
+ to spawn promote and delete jobs. This is now done by `Attacher#promote_cached`
265
+ and `Attacher#destroy_attached`:
266
+
267
+ ```rb
268
+ attacher.promote_cached # will spawn background job if registered
269
+ attacher.destroy_attached # will spawn background job if registered
270
+ ```
271
+
272
+ If you want to explicitly call backgrounding blocks, you can use
273
+ `Attacher#promote_background` and `Attacher#destroy_background`:
274
+
275
+ ```rb
276
+ attacher.promote_background # calls promote block
277
+ attacher.destroy_background # calls destroy block
278
+ ```
279
+
280
+ ## Versions
281
+
282
+ The `versions`, `processing`, `recache`, and `delete_raw` plugins have been
283
+ deprecated in favour of the new **[`derivatives`][derivatives]** plugin.
284
+
285
+ Let's assume you have the following `versions` configuration:
286
+
287
+ ```rb
288
+ class ImageUploader < Shrine
289
+ plugin :processing
290
+ plugin :versions
291
+ plugin :delete_raw
292
+
293
+ process(:store) do |file, context|
294
+ versions = { original: file }
295
+
296
+ file.download do |original|
297
+ magick = ImageProcessing::MiniMagick.source(original)
298
+
299
+ versions[:large] = magick.resize_to_limit!(800, 800)
300
+ versions[:medium] = magick.resize_to_limit!(500, 500)
301
+ versions[:small] = magick.resize_to_limit!(300, 300)
302
+ end
303
+
304
+ versions
305
+ end
306
+ end
307
+ ```
308
+
309
+ When an attached file is promoted to permanent storage, the versions would
310
+ automatically get generated:
311
+
312
+ ```rb
313
+ photo = Photo.new(photo_params)
314
+
315
+ if photo.valid?
316
+ photo.save # generates versions on promotion
317
+ # ...
318
+ else
319
+ # ...
320
+ end
321
+ ```
322
+
323
+ With `derivatives`, the original file is automatically downloaded and retained
324
+ during processing, so the setup is simpler:
325
+
326
+ ```rb
327
+ Shrine.plugin :derivatives,
328
+ create_on_promote: true, # automatically create derivatives on promotion
329
+ versions_compatibility: true # handle versions column format
330
+ ```
331
+ ```rb
332
+ class ImageUploader < Shrine
333
+ Attacher.derivatives do |original|
334
+ magick = ImageProcessing::MiniMagick.source(original)
335
+
336
+ # the :original file should NOT be included anymore
337
+ {
338
+ large: magick.resize_to_limit!(800, 800),
339
+ medium: magick.resize_to_limit!(500, 500),
340
+ small: magick.resize_to_limit!(300, 300),
341
+ }
342
+ end
343
+ end
344
+ ```
345
+ ```rb
346
+ photo = Photo.new(photo_params)
347
+
348
+ if photo.valid?
349
+ photo.save # creates derivatives on promotion
350
+ # ...
351
+ else
352
+ # ...
353
+ end
354
+ ```
355
+
356
+ ### Accessing derivatives
357
+
358
+ The derivative URLs are accessed in the same way as versions:
359
+
360
+ ```rb
361
+ photo.image_url(:small)
362
+ ```
363
+
364
+ But the files themselves are accessed differently:
365
+
366
+ ```rb
367
+ # versions
368
+ photo.image #=>
369
+ # {
370
+ # original: #<Shrine::UploadedFile ...>,
371
+ # large: #<Shrine::UploadedFile ...>,
372
+ # medium: #<Shrine::UploadedFile ...>,
373
+ # small: #<Shrine::UploadedFile ...>,
374
+ # }
375
+ photo.image[:medium] #=> #<Shrine::UploadedFile ...>
376
+ ```
377
+ ```rb
378
+ # derivatives
379
+ photo.image_derivatives #=>
380
+ # {
381
+ # large: #<Shrine::UploadedFile ...>,
382
+ # medium: #<Shrine::UploadedFile ...>,
383
+ # small: #<Shrine::UploadedFile ...>,
384
+ # }
385
+ photo.image(:medium) #=> #<Shrine::UploadedFile ...>
386
+ ```
387
+
388
+ ### Migrating versions
389
+
390
+ The `versions` and `derivatives` plugins save processed file data to the
391
+ database column in different formats:
392
+
393
+ ```rb
394
+ # versions
395
+ {
396
+ "original": { "id": "...", "storage": "...", "metadata": { ... } },
397
+ "large": { "id": "...", "storage": "...", "metadata": { ... } },
398
+ "medium": { "id": "...", "storage": "...", "metadata": { ... } },
399
+ "small": { "id": "...", "storage": "...", "metadata": { ... } }
400
+ }
401
+ ```
402
+ ```rb
403
+ # derivatives
404
+ {
405
+ "id": "...",
406
+ "storage": "...",
407
+ "metadata": { ... },
408
+ "derivatives": {
409
+ "large": { "id": "...", "storage": "...", "metadata": { ... } },
410
+ "medium": { "id": "...", "storage": "...", "metadata": { ... } },
411
+ "small": { "id": "...", "storage": "...", "metadata": { ... } }
412
+ }
413
+ }
414
+ ```
415
+
416
+ The `:versions_compatibility` flag to the `derivatives` plugin enables it to
417
+ read the `versions` format, which aids in transition. Once the `derivatives`
418
+ plugin has been deployed to production, you can update existing records with
419
+ the new column format:
420
+
421
+ ```rb
422
+ Photo.find_each do |photo|
423
+ photo.image_attacher.write
424
+ photo.image_attacher.atomic_persist
425
+ end
426
+ ```
427
+
428
+ Afterwards you should be able to remove the `:versions_compatibility` flag.
429
+
430
+ ### Backgrounding derivatives
431
+
432
+ If you're using the `backgrounding` plugin, you can trigger derivatives
433
+ creation in the `PromoteJob` instead of the controller:
434
+
435
+ ```rb
436
+ class PromoteJob
437
+ include Sidekiq::Worker
438
+
439
+ def perform(attacher_class, record_class, record_id, name, file_data)
440
+ attacher_class = Object.const_get(attacher_class)
441
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
442
+
443
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
444
+ attacher.create_derivatives # call derivatives processor
445
+ attacher.atomic_promote
446
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
447
+ # attachment has changed or record has been deleted, nothing to do
448
+ end
449
+ end
450
+ ```
451
+
452
+ #### Recache
453
+
454
+ If you were using the `recache` plugin, you can replicate the behaviour by
455
+ creating another derivatives processor that you will trigger in the controller:
456
+
457
+ ```rb
458
+ class ImageUploader < Shrine
459
+ Attacher.derivatives do |original|
460
+ # this will be triggered in the background job
461
+ end
462
+
463
+ Attacher.derivatives :foreground do |original|
464
+ # this will be triggered in the controller
465
+ end
466
+ end
467
+ ```
468
+ ```rb
469
+ photo = Photo.new(photo_params)
470
+
471
+ if photo.valid?
472
+ photo.image_derivatives!(:foreground) if photo.image_changed?
473
+ photo.save
474
+ # ...
475
+ else
476
+ # ...
477
+ end
478
+ ```
479
+
480
+ ### Default URL
481
+
482
+ If you were using the `default_url` plugin, the `Attacher.default_url` now
483
+ receives a `:derivative` option:
484
+
485
+ ```rb
486
+ Attacher.default_url do |derivative: nil, **|
487
+ "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
488
+ end
489
+ ```
490
+
491
+ #### Fallback to original
492
+
493
+ With the `versions` plugin, a missing version URL would automatically fall back
494
+ to the original file. The `derivatives` plugin has no such fallback, but you
495
+ can configure it manually:
496
+
497
+ ```rb
498
+ Attacher.default_url do |derivative: nil, **|
499
+ file&.url if derivative
500
+ end
501
+ ```
502
+
503
+ #### Fallback to version
504
+
505
+ The `versions` plugin had the ability to fall back missing version URL to
506
+ another version that already exists. The `derivatives` plugin doesn't have this
507
+ built in, but you can implement it as follows:
508
+
509
+ ```rb
510
+ DERIVATIVE_FALLBACKS = { foo: :bar, ... }
511
+
512
+ Attacher.default_url do |derivative: nil, **|
513
+ derivatives[DERIVATIVE_FALLBACKS[derivative]]&.url if derivative
514
+ end
515
+ ```
516
+
517
+ ### Location
518
+
519
+ The `Shrine#generate_location` method will now receive a `:derivative`
520
+ parameter instead of `:version`:
521
+
522
+ ```rb
523
+ class MyUploader < Shrine
524
+ def generate_location(io, derivative: nil, **)
525
+ derivative #=> :large, :medium, :small, ...
526
+ # ...
527
+ end
528
+ end
529
+ ```
530
+
531
+ ### Overwriting original
532
+
533
+ With the `derivatives` plugin, saving processed files separately from the
534
+ original file, so the original file is automatically kept. This means it's not
535
+ possible anymore to overwrite the original file as part of processing.
536
+
537
+ However, **it's highly recommended to always keep the original file**, even if
538
+ you don't plan to use it. That way, if there is ever a need to reprocess
539
+ derivatives, you have the original file to use as a base.
540
+
541
+ That being said, if you still want to overwrite the original file, [this
542
+ thread][overwriting original] has some tips.
543
+
544
+ ## Other
545
+
546
+ ### Processing
547
+
548
+ The `processing` plugin has been deprecated over the new
549
+ [`derivatives`][derivatives] plugin. If you were previously replacing the
550
+ original file:
551
+
552
+ ```rb
553
+ class MyUploader < Shrine
554
+ plugin :processing
555
+
556
+ process(:store) do |io, context|
557
+ ImageProcessing::MiniMagick
558
+ .source(io.download)
559
+ .resize_to_limit!(1600, 1600)
560
+ end
561
+ end
562
+ ```
563
+
564
+ you should now add the processed file as a derivative:
565
+
566
+ ```rb
567
+ class MyUploader < Shrine
568
+ plugin :derivatives
569
+
570
+ Attacher.derivatives do |original|
571
+ magick = ImageProcessing::MiniMagick.source(original)
572
+
573
+ { normalized: magick.resize_to_limit!(1600, 1600) }
574
+ end
575
+ end
576
+ ```
577
+
578
+ ### Parallelize
579
+
580
+ The `parallelize` plugin has been removed. With `derivatives` plugin you can
581
+ parallelize uploading processed files manually:
582
+
583
+ ```rb
584
+ # Gemfile
585
+ gem "concurrent-ruby"
586
+ ```
587
+ ```rb
588
+ require "concurrent"
589
+
590
+ attacher = photo.image_attacher
591
+ derivatives = attacher.process_derivatives
592
+
593
+ tasks = derivatives.map do |name, file|
594
+ Concurrent::Promises.future(name, file) do |name, file|
595
+ attacher.add_derivative(name, file)
596
+ end
597
+ end
598
+
599
+ Concurrent::Promises.zip(*tasks).wait!
600
+ ```
601
+
602
+ ### Logging
603
+
604
+ The `logging` plugin has been removed in favour of the
605
+ [`instrumentation`][instrumentation] plugin. You can replace code like
606
+
607
+ ```rb
608
+ Shrine.plugin :logging, logger: Rails.logger
609
+ ```
610
+
611
+ with
612
+
613
+ ```rb
614
+ Shrine.logger = Rails.logger
615
+
616
+ Shrine.plugin :instrumentation
617
+ ```
618
+
619
+ ### Backup
620
+
621
+ The `backup` plugin has been removed in favour of the new
622
+ [`mirroring`][mirroring] plugin. You can replace code like
623
+
624
+ ```rb
625
+ Shrine.plugin :backup, storage: :backup_store
626
+ ```
627
+
628
+ with
629
+
630
+ ```rb
631
+ Shrine.plugin :mirroring, mirror: { store: :backup_store }
632
+ ```
633
+
634
+ ### Copy
635
+
636
+ The `copy` plugin has been removed as its behaviour can now be achieved easily.
637
+ You can replace code like
638
+
639
+ ```rb
640
+ Shrine.plugin :copy
641
+ ```
642
+ ```rb
643
+ attacher.copy(other_attacher)
644
+ ```
645
+
646
+ with
647
+
648
+ ```rb
649
+ attacher.set nil # clear original attachment
650
+ attacher.attach other_attacher.file, storage: other_attacher.file.storage_key
651
+ attacher.add_derivatives other_attacher.derivatives # if using derivatives
652
+ ```
653
+
654
+ ### Moving
655
+
656
+ The `moving` plugin has been removed in favour of the `:move` option for
657
+ `FileSystem#upload`. You can set this option as default using the
658
+ `upload_options` plugin (the example assumes both `:cache` and `:store` are
659
+ FileSystem storages):
660
+
661
+ ```rb
662
+ Shrine.plugin :upload_options, cache: { move: true }, store: { move: true }
663
+ ```
664
+
665
+ ### Parsed JSON
666
+
667
+ The `parsed_json` plugin has been removed as it's now the default behaviour.
668
+
669
+ ```rb
670
+ # this now works by default
671
+ photo.image = { "id" => "d7e54d6ef2.jpg", "storage" => "cache", "metadata" => { ... } }
672
+ ```
673
+
674
+ ### Module Include
675
+
676
+ The `module_include` plugin has been deprecated over overriding core classes
677
+ directly. You can replace code like
678
+
679
+ ```rb
680
+ class MyUploader < Shrine
681
+ plugin :module_include
682
+
683
+ file_methods do
684
+ def image?
685
+ mime_type.start_with?("image")
686
+ end
687
+ end
688
+ end
689
+ ```
690
+
691
+ with
692
+
693
+ ```rb
694
+ class MyUploader < Shrine
695
+ class UploadedFile
696
+ def image?
697
+ mime_type.start_with?("image")
698
+ end
699
+ end
700
+ end
701
+ ```
702
+
703
+ [3.0 release notes]: https://shrinerb.com/docs/release_notes/3.0.0
704
+ [model]: https://shrinerb.com/docs/plugins/model
705
+ [derivatives]: https://shrinerb.com/docs/plugins/derivatives
706
+ [instrumentation]: https://shrinerb.com/docs/plugins/instrumentation
707
+ [mirroring]: https://shrinerb.com/docs/plugins/mirroring
708
+ [overwriting original]: https://discourse.shrinerb.com/t/keep-original-file-after-processing/50/4