shrine 3.0.0 → 3.2.2

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -33
  3. data/LICENSE.txt +1 -1
  4. data/README.md +94 -4
  5. data/doc/advantages.md +35 -18
  6. data/doc/attacher.md +16 -17
  7. data/doc/carrierwave.md +75 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/external/articles.md +56 -41
  11. data/doc/external/extensions.md +38 -34
  12. data/doc/getting_started.md +182 -112
  13. data/doc/metadata.md +79 -43
  14. data/doc/multiple_files.md +5 -3
  15. data/doc/paperclip.md +110 -42
  16. data/doc/plugins/activerecord.md +5 -5
  17. data/doc/plugins/add_metadata.md +92 -35
  18. data/doc/plugins/backgrounding.md +12 -2
  19. data/doc/plugins/column.md +36 -7
  20. data/doc/plugins/data_uri.md +2 -2
  21. data/doc/plugins/default_url.md +6 -3
  22. data/doc/plugins/derivation_endpoint.md +26 -28
  23. data/doc/plugins/derivatives.md +205 -169
  24. data/doc/plugins/determine_mime_type.md +2 -2
  25. data/doc/plugins/entity.md +3 -3
  26. data/doc/plugins/form_assign.md +5 -5
  27. data/doc/plugins/included.md +25 -5
  28. data/doc/plugins/infer_extension.md +2 -2
  29. data/doc/plugins/instrumentation.md +1 -1
  30. data/doc/plugins/metadata_attributes.md +21 -10
  31. data/doc/plugins/model.md +4 -4
  32. data/doc/plugins/persistence.md +1 -0
  33. data/doc/plugins/refresh_metadata.md +5 -4
  34. data/doc/plugins/remote_url.md +8 -3
  35. data/doc/plugins/remove_invalid.md +9 -1
  36. data/doc/plugins/sequel.md +4 -4
  37. data/doc/plugins/signature.md +11 -2
  38. data/doc/plugins/store_dimensions.md +2 -2
  39. data/doc/plugins/type_predicates.md +96 -0
  40. data/doc/plugins/upload_endpoint.md +7 -11
  41. data/doc/plugins/upload_options.md +1 -1
  42. data/doc/plugins/url_options.md +2 -2
  43. data/doc/plugins/validation.md +14 -4
  44. data/doc/plugins/validation_helpers.md +3 -3
  45. data/doc/plugins/versions.md +11 -11
  46. data/doc/processing.md +289 -125
  47. data/doc/refile.md +39 -18
  48. data/doc/release_notes/2.19.0.md +1 -1
  49. data/doc/release_notes/3.0.0.md +275 -258
  50. data/doc/release_notes/3.0.1.md +22 -0
  51. data/doc/release_notes/3.1.0.md +73 -0
  52. data/doc/release_notes/3.2.0.md +96 -0
  53. data/doc/release_notes/3.2.1.md +32 -0
  54. data/doc/release_notes/3.2.2.md +14 -0
  55. data/doc/securing_uploads.md +3 -3
  56. data/doc/storage/file_system.md +1 -1
  57. data/doc/storage/memory.md +19 -0
  58. data/doc/storage/s3.md +105 -86
  59. data/doc/testing.md +2 -2
  60. data/doc/upgrading_to_3.md +115 -33
  61. data/doc/validation.md +3 -2
  62. data/lib/shrine.rb +8 -8
  63. data/lib/shrine/attacher.rb +19 -14
  64. data/lib/shrine/attachment.rb +5 -5
  65. data/lib/shrine/plugins.rb +22 -0
  66. data/lib/shrine/plugins/add_metadata.rb +12 -3
  67. data/lib/shrine/plugins/default_storage.rb +6 -6
  68. data/lib/shrine/plugins/default_url.rb +1 -1
  69. data/lib/shrine/plugins/derivation_endpoint.rb +10 -6
  70. data/lib/shrine/plugins/derivatives.rb +19 -17
  71. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  72. data/lib/shrine/plugins/entity.rb +6 -6
  73. data/lib/shrine/plugins/metadata_attributes.rb +1 -1
  74. data/lib/shrine/plugins/model.rb +3 -3
  75. data/lib/shrine/plugins/presign_endpoint.rb +2 -2
  76. data/lib/shrine/plugins/pretty_location.rb +1 -1
  77. data/lib/shrine/plugins/processing.rb +1 -1
  78. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  79. data/lib/shrine/plugins/remote_url.rb +3 -3
  80. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  81. data/lib/shrine/plugins/signature.rb +7 -6
  82. data/lib/shrine/plugins/store_dimensions.rb +18 -9
  83. data/lib/shrine/plugins/type_predicates.rb +113 -0
  84. data/lib/shrine/plugins/upload_endpoint.rb +3 -3
  85. data/lib/shrine/plugins/upload_options.rb +2 -2
  86. data/lib/shrine/plugins/url_options.rb +2 -2
  87. data/lib/shrine/plugins/validation.rb +9 -7
  88. data/lib/shrine/storage/linter.rb +4 -4
  89. data/lib/shrine/storage/s3.rb +62 -38
  90. data/lib/shrine/uploaded_file.rb +5 -1
  91. data/lib/shrine/version.rb +2 -2
  92. data/shrine.gemspec +6 -7
  93. metadata +23 -29
@@ -157,21 +157,21 @@ end
157
157
 
158
158
  ## Processing
159
159
 
160
- Most file attachment libraries provide either processing files up front
161
- (Paperclip, CarrierWave) or on-the-fly (Dragonfly, Refile, Active Storage).
160
+ Most file attachment libraries allow you to process files either "eagerly"
161
+ (Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active Storage).
162
162
  However, each approach is suitable for different requirements. For instance,
163
163
  while on-the-fly processing is suitable for fast processing (image thumbnails,
164
164
  document previews), longer running processing (video transcoding, raw images)
165
165
  should be moved into a background job.
166
166
 
167
- That's why Shrine supports both [up front][derivatives] and
167
+ That's why Shrine supports both [eager][derivatives] and
168
168
  [on-the-fly][derivation_endpoint] processing. For example, if you're handling
169
169
  image uploads, you can choose to either generate a set of pre-defined
170
170
  thumbnails during attachment:
171
171
 
172
172
  ```rb
173
173
  class ImageUploader < Shrine
174
- Attacher.derivatives_processor do |original|
174
+ Attacher.derivatives do |original|
175
175
  magick = ImageProcessing::MiniMagick.source(original)
176
176
 
177
177
  {
@@ -261,7 +261,7 @@ gem "streamio-ffmpeg"
261
261
  ```
262
262
  ```rb
263
263
  class VideoUploader < Shrine
264
- Attacher.derivatives_processor do |original|
264
+ Attacher.derivatives do |original|
265
265
  transcoded = Tempfile.new ["transcoded", ".mp4"]
266
266
  screenshot = Tempfile.new ["screenshot", ".jpg"]
267
267
 
@@ -276,12 +276,12 @@ end
276
276
  ```rb
277
277
  movie.video_derivatives! # create derivatives
278
278
 
279
- movie.video #=> #<Shrine::UploadedFile @id="5a5cd0.mov" ...>
280
- movie.video(:transcoded) #=> #<Shrine::UploadedFile @id="7481d6.mp4" ...>
281
- movie.video(:screenshot) #=> #<Shrine::UploadedFile @id="8f3136.jpg" ...>
279
+ movie.video #=> #<Shrine::UploadedFile id="5a5cd0.mov" ...>
280
+ movie.video(:transcoded) #=> #<Shrine::UploadedFile id="7481d6.mp4" ...>
281
+ movie.video(:screenshot) #=> #<Shrine::UploadedFile id="8f3136.jpg" ...>
282
282
  ```
283
283
 
284
- ## Metadata & Validation
284
+ ## Metadata
285
285
 
286
286
  Shrine automatically [extracts metadata][metadata] from each uploaded file,
287
287
  including derivatives like image thumbnails, and saves them into the database
@@ -289,6 +289,17 @@ column. In addition to filename, filesize, and MIME type that are extracted by
289
289
  default, you can also extract [image dimensions][store_dimensions], or your own
290
290
  [custom metadata][add_metadata].
291
291
 
292
+ ```rb
293
+ class ImageUploader < Shrine
294
+ plugin :determine_mime_type # mime_type
295
+ plugin :store_dimensions # width & height
296
+
297
+ add_metadata :resolution do |io|
298
+ image = MiniMagick::Image.new(io.path)
299
+ image.resolution
300
+ end
301
+ end
302
+ ```
292
303
  ```rb
293
304
  photo.image.metadata #=>
294
305
  # {
@@ -297,23 +308,30 @@ photo.image.metadata #=>
297
308
  # "mime_type" => "image/jpeg",
298
309
  # "width" => 600,
299
310
  # "height" => 400,
311
+ # "resolution" => [72, 72],
300
312
  # ...
301
313
  # }
302
314
  ```
303
315
 
304
- For common metadata you can use the built-in [validators][validation_helpers],
305
- but you can also [validate any custom metadata][custom validations].
316
+ ## Validation
317
+
318
+ For file validations there are [built-in validators][validation_helpers], but
319
+ you can also just use plain Ruby code:
306
320
 
307
321
  ```rb
308
- class DocumentUploader < Shrine
322
+ class ImageUploader < Shrine
323
+ plugin :validation_helpers
324
+
309
325
  Attacher.validate do
310
- # validation macros
311
326
  validate_max_size 10*1024*1024
312
- validate_mime_type %W[application/pdf]
327
+ validate_extension %w[jpg jpeg png webp]
328
+
329
+ if validate_mime_type %W[image/jpeg image/png image/webp]
330
+ validate_max_dimensions [5000, 5000]
313
331
 
314
- # custom validations
315
- if file["page_count"] > 30
316
- errors << "must not have more than 30 pages"
332
+ unless ImageProcessing::MiniMagick.valid_image?(file.download.path)
333
+ error << "seems to be corrupted"
334
+ end
317
335
  end
318
336
  end
319
337
  end
@@ -432,7 +450,6 @@ on top of Rack, so that they can be used with any Ruby web framework.
432
450
  [backgrounding libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries
433
451
  [Down streaming]: https://github.com/janko/down#streaming
434
452
  [validation_helpers]: https://shrinerb.com/docs/plugins/validation_helpers
435
- [custom validations]: https://shrinerb.com/docs/validation#custom-validations
436
453
  [derivatives]: https://shrinerb.com/docs/plugins/derivatives
437
454
  [derivation_endpoint]: https://shrinerb.com/docs/plugins/derivation_endpoint
438
455
  [libvips performance]: https://github.com/libvips/libvips/wiki/Speed-and-memory-use#results
@@ -96,14 +96,14 @@ If given a raw file, it will upload it to temporary storage:
96
96
 
97
97
  ```rb
98
98
  attacher.assign(file)
99
- attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg", @storage_key=:cache>
99
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:cache ...>
100
100
  ```
101
101
 
102
102
  If given cached file data (JSON or Hash), it will set the cached file:
103
103
 
104
104
  ```rb
105
105
  attacher.assign('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
106
- attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg", @storage_key=:cache>
106
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:cache ...>
107
107
  ```
108
108
 
109
109
  If given an empty string, it will no-op:
@@ -130,8 +130,8 @@ attacher.attach_cached(file)
130
130
 
131
131
  # sets cached file
132
132
  attacher.attach_cached('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
133
- attacher.attach_cached("id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... })
134
- attacher.attach_cached(id: "asdf.jpg", storage: "cache", metadata: { ... })
133
+ attacher.attach_cached({ "id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... } })
134
+ attacher.attach_cached({ id: "asdf.jpg", storage: "cache", metadata: { ... } })
135
135
 
136
136
  # unsets attached file
137
137
  attacher.attach_cached(nil)
@@ -143,7 +143,7 @@ The `Attacher#attach` method uploads a given file to permanent storage:
143
143
 
144
144
  ```rb
145
145
  attacher.attach(file)
146
- attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg" @storage=:store>
146
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:store ...>
147
147
  ```
148
148
 
149
149
  This method is useful when attaching files from scripts, where validation
@@ -153,7 +153,7 @@ You can specify a different destination storage with the `:storage` option:
153
153
 
154
154
  ```rb
155
155
  attacher.attach(file, storage: :other_store)
156
- attacher.file #=> #<Shrine::UploadedFile @id="asdf.jpg" @storage=:other_store>
156
+ attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:other_store ...>
157
157
  ```
158
158
 
159
159
  Any additional options passed to `Attacher#attach`, `Attacher#attach_cached`
@@ -171,9 +171,9 @@ If you want to upload a file to without attaching it, you can use
171
171
  `Attacher#upload`:
172
172
 
173
173
  ```rb
174
- attacher.upload(file) #=> #<Shrine::UploadedFile @storage=:store ...>
175
- attacher.upload(file, :cache) #=> #<Shrine::UploadedFile @storage=:cache ...>
176
- attacher.upload(file, :other_store) #=> #<Shrine::UploadedFile @storage=:other_store ...>
174
+ attacher.upload(file) #=> #<Shrine::UploadedFile storage=:store ...>
175
+ attacher.upload(file, :cache) #=> #<Shrine::UploadedFile storage=:cache ...>
176
+ attacher.upload(file, :other_store) #=> #<Shrine::UploadedFile storage=:other_store ...>
177
177
  ```
178
178
 
179
179
  This is useful if you want to attacher [context](#context) such as `:record`
@@ -202,20 +202,19 @@ attacher.changed? #=> true
202
202
  You can use `Attacher#change` to attach an `UploadedFile` object as is:
203
203
 
204
204
  ```rb
205
- uploaded_file #=> #<Shrine::UploadedFile>
205
+ uploaded_file #=> #<Shrine::UploadedFile id="foo" ...>
206
206
  attacher.change(uploaded_file)
207
- attacher.file #=> #<Shrine::UploadedFile> (same object)
207
+ attacher.file #=> #<Shrine::UploadedFile id="foo" ...>
208
208
  attacher.changed? #=> true
209
-
210
209
  ```
211
210
 
212
211
  If you want to attach a file without triggering dirty tracking or validation,
213
212
  you can use `Attacher#set`:
214
213
 
215
214
  ```rb
216
- uploaded_file #=> #<Shrine::UploadedFile>
215
+ uploaded_file #=> #<Shrine::UploadedFile id="foo" ...>
217
216
  attacher.set(uploaded_file)
218
- attacher.file #=> #<Shrine::UploadedFile> (same object)
217
+ attacher.file #=> #<Shrine::UploadedFile id="foo" ...>
219
218
  attacher.changed? #=> false
220
219
  ```
221
220
 
@@ -246,16 +245,16 @@ storage, and in this case uploads it to permanent storage.
246
245
  ```rb
247
246
  attacher.attach_cached(io)
248
247
  attacher.finalize # uploads attached file to permanent storage
249
- attacher.file #=> #<Shrine::UploadedFile @storage=:store ...>
248
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
250
249
  ```
251
250
 
252
251
  Internally it calls `Attacher#promote_cached`, which you can call directly if
253
252
  you want to pass any promote options:
254
253
 
255
254
  ```rb
256
- attacher.file #=> #<Shrine::UploadedFile @storage=:cache ...>
255
+ attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
257
256
  attacher.promote_cached # uploads attached file to permanent storage if new and cached
258
- attacher.file #=> #<Shrine::UploadedFile @storage=:store ...>
257
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
259
258
  ```
260
259
 
261
260
  You can also call `Attacher#promote` if you want to upload attached file to
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: Shrine for CarrierWave Users
2
+ title: Upgrading from CarrierWave
3
3
  ---
4
4
 
5
5
  This guide is aimed at helping CarrierWave users transition to Shrine, and it
6
6
  consists of three parts:
7
7
 
8
8
  1. Explanation of the key differences in design between CarrierWave and Shrine
9
- 2. Instructions how to migrate and existing app that uses CarrierWave to Shrine
9
+ 2. Instructions how to migrate an existing app that uses CarrierWave to Shrine
10
10
  3. Extensive reference of CarrierWave's interface with Shrine equivalents
11
11
 
12
12
  ## Overview
@@ -159,7 +159,7 @@ require "image_processing/mini_magick"
159
159
  class ImageUploader < Shrine
160
160
  plugin :derivatives
161
161
 
162
- Attacher.derivatives_processor do |original|
162
+ Attacher.derivatives do |original|
163
163
  magick = ImageProcessing::MiniMagick.source(original)
164
164
 
165
165
  {
@@ -179,15 +179,15 @@ uploaded processed files into the database (including any extracted metadata),
179
179
  which then becomes the source of truth on which versions have been generated.
180
180
 
181
181
  ```rb
182
- photo.image #=> #<Shrine::UploadedFile @id="original.jpg" ...>
182
+ photo.image #=> #<Shrine::UploadedFile id="original.jpg" ...>
183
183
  photo.image_derivatives #=> {}
184
184
 
185
185
  photo.image_derivatives! # triggers processing
186
186
  photo.image_derivatives #=>
187
187
  # {
188
- # large: #<Shrine::UploadedFile @id="large.jpg" @metadata={"size"=>873232, ...} ...>,
189
- # medium: #<Shrine::UploadedFile @id="medium.jpg" @metadata={"size"=>94823, ...} ...>,
190
- # small: #<Shrine::UploadedFile @id="small.jpg" @metadata={"size"=>37322, ...} ...>,
188
+ # large: #<Shrine::UploadedFile id="large.jpg" metadata={"size"=>873232, ...} ...>,
189
+ # medium: #<Shrine::UploadedFile id="medium.jpg" metadata={"size"=>94823, ...} ...>,
190
+ # small: #<Shrine::UploadedFile id="small.jpg" metadata={"size"=>37322, ...} ...>,
191
191
  # }
192
192
  ```
193
193
 
@@ -268,18 +268,24 @@ Files] guide explains this setup in more detail.
268
268
  ## Migrating from CarrierWave
269
269
 
270
270
  You have an existing app using CarrierWave and you want to transfer it to
271
- Shrine. Let's assume we have a `Photo` model with the "image" attachment. First
272
- we need to create the `image_data` column for Shrine:
271
+ Shrine. Let's assume we have a `Photo` model with the "image" attachment.
272
+
273
+ ### 1. Add Shrine column
274
+
275
+ First we need to create the `image_data` column for Shrine:
273
276
 
274
277
  ```rb
275
278
  add_column :photos, :image_data, :text # or :json or :jsonb if supported
276
279
  ```
277
280
 
278
- Afterwards we need to make new uploads write to the `image_data` column. This
279
- can be done by including the below module to all models that have CarrierWave
280
- attachments:
281
+ ### 2. Dual write
282
+
283
+ Next, we need to make new CarrierWave attachments write to the
284
+ `image_data` column. This can be done by including the below module to all
285
+ models that have CarrierWave attachments:
281
286
 
282
287
  ```rb
288
+ # config/initializers/shrine.rb (Rails)
283
289
  require "shrine"
284
290
 
285
291
  Shrine.storages = {
@@ -289,8 +295,7 @@ Shrine.storages = {
289
295
 
290
296
  Shrine.plugin :model
291
297
  Shrine.plugin :derivatives
292
- ```
293
- ```rb
298
+
294
299
  module CarrierwaveShrineSynchronization
295
300
  def self.included(model)
296
301
  model.before_save do
@@ -302,13 +307,13 @@ module CarrierwaveShrineSynchronization
302
307
 
303
308
  def write_shrine_data(name)
304
309
  uploader = send(name)
305
- attacher = Shrine::Attacher.form_model(self, name)
310
+ attacher = Shrine::Attacher.from_model(self, name)
306
311
 
307
312
  if read_attribute(name).present?
308
313
  attacher.set shrine_file(uploader)
309
314
 
310
- uploader.versions.each do |name, version|
311
- attacher.merge_derivatives(name => shrine_file(version))
315
+ uploader.versions.each do |version_name, version|
316
+ attacher.merge_derivatives(version_name => shrine_file(version))
312
317
  end
313
318
  else
314
319
  attacher.set nil
@@ -340,20 +345,27 @@ end
340
345
  ```
341
346
 
342
347
  After you deploy this code, the `image_data` column should now be successfully
343
- synchronized with new attachments. Next step is to run a script which writes
344
- all existing CarrierWave attachments to `image_data`:
348
+ synchronized with new attachments.
349
+
350
+ ### 3. Data migration
351
+
352
+ Next step is to run a script which writes all existing CarrierWave attachments
353
+ to `image_data`:
345
354
 
346
355
  ```rb
347
356
  Photo.find_each do |photo|
348
- Photo.uploaders.each_key { |name| photo.write_shrine_data(name) }
357
+ photo.write_shrine_data(:image)
349
358
  photo.save!
350
359
  end
351
360
  ```
352
361
 
362
+ ### 4. Rewrite code
363
+
353
364
  Now you should be able to rewrite your application so that it uses Shrine
354
- instead of CarrierWave, using equivalent Shrine storages. For help with
355
- translating the code from CarrierWave to Shrine, you can consult the reference
356
- below.
365
+ instead of CarrierWave (you can consult the reference in the next section). You
366
+ can remove the `CarrierwaveShrineSynchronization` module as well.
367
+
368
+ ### 5. Backill metadata
357
369
 
358
370
  You'll notice that Shrine metadata will be absent from the migrated files'
359
371
  data. You can run a script that will fill in any missing metadata defined in
@@ -363,11 +375,20 @@ your Shrine uploader:
363
375
  Shrine.plugin :refresh_metadata
364
376
 
365
377
  Photo.find_each do |photo|
366
- photo.image_attacher.refresh_metadata!
367
- photo.save
378
+ attacher = photo.image_attacher
379
+ attacher.refresh_metadata!
380
+ attacher.atomic_persist
368
381
  end
369
382
  ```
370
383
 
384
+ ### 6. Remove CarrierWave column
385
+
386
+ If everything is looking good, we can remove the CarrierWave column:
387
+
388
+ ```rb
389
+ remove_column :photos, :image
390
+ ```
391
+
371
392
  ## CarrierWave to Shrine direct mapping
372
393
 
373
394
  ### `CarrierWave::Uploader::Base`
@@ -395,7 +416,7 @@ Processing is defined by using the `derivatives` plugin:
395
416
  class ImageUploader < Shrine
396
417
  plugin :derivatives
397
418
 
398
- Attacher.derivatives_processor do |original|
419
+ Attacher.derivatives do |original|
399
420
  magick = ImageProcessing::MiniMagick.source(image)
400
421
 
401
422
  {
@@ -455,16 +476,25 @@ photo.image.original_filename #=> "avatar.jpg"
455
476
 
456
477
  #### `#store_dir`, `#cache_dir`
457
478
 
458
- Shrine here provides a `#generate_location` method, which is triggered for all
459
- storages:
479
+ Shrine here provides a single `#generate_location` method that's triggered for
480
+ all storages:
460
481
 
461
482
  ```rb
462
483
  class ImageUploader < Shrine
463
- def generate_location(io, record: nil, **)
464
- "#{record.class}/#{record.id}/#{io.original_filename}"
484
+ def generate_location(io, record: nil, name: nil, **)
485
+ [ storage_key,
486
+ record && record.class.name.underscore,
487
+ record && record.id,
488
+ super,
489
+ io.original_filename ].compact.join("/")
465
490
  end
466
491
  end
467
492
  ```
493
+ ```
494
+ cache/user/123/2feff8c724e7ce17/nature.jpg
495
+ store/user/456/7f99669fde1e01fc/kitten.jpg
496
+ ...
497
+ ```
468
498
 
469
499
  You might also want to use the `pretty_location` plugin for automatically
470
500
  generating an organized folder structure.
@@ -477,8 +507,8 @@ For default URLs you can use the `default_url` plugin:
477
507
  class ImageUploader < Shrine
478
508
  plugin :default_url
479
509
 
480
- Attacher.default_url do |options|
481
- "/attachments/#{name}/default.jpg"
510
+ Attacher.default_url do |derivative: nil, **|
511
+ "/fallbacks/#{derivative || "original"}.jpg"
482
512
  end
483
513
  end
484
514
  ```
@@ -499,7 +529,7 @@ class ImageUploader < Shrine
499
529
  end
500
530
  ```
501
531
 
502
- #### `#blacklist_mime_type_pattern`, `#whitelist_mime_type_pattern`, `#content_type_whitelist`, `#content_type_blacklist`
532
+ #### `#content_type_whitelist`, `#content_type_blacklist`
503
533
 
504
534
  In Shrine, MIME type whitelisting/blacklisting is part of validations, and is
505
535
  provided by the `validation_helpers` plugin, though it doesn't support regexes:
@@ -515,6 +545,17 @@ class ImageUploader < Shrine
515
545
  end
516
546
  ```
517
547
 
548
+ Make sure to also load the `determine_mime_type` plugin to detect MIME type
549
+ from file content.
550
+
551
+ ```rb
552
+ # Gemfile
553
+ gem "mimemagic"
554
+ ```
555
+ ```rb
556
+ Shrine.plugin :determine_mime_type, analyzer: :mimemagic
557
+ ```
558
+
518
559
  #### `#size_range`
519
560
 
520
561
  In Shrine file size validations are typically done using the
@@ -622,7 +663,7 @@ shows what are Shrine's equivalents.
622
663
 
623
664
  #### `root`, `base_path`, `permissions`, `directory_permissions`
624
665
 
625
- In Shrine these are configured on the FileSystem storage directly.
666
+ In Shrine these are configured on the `FileSystem` storage directly.
626
667
 
627
668
  #### `storage`, `storage_engines`
628
669