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
@@ -15,6 +15,18 @@ uploaded_file.metadata #=>
15
15
  # }
16
16
  ```
17
17
 
18
+ Under the hood, `Shrine#upload` calls `Shrine#extract_metadata`, which you can
19
+ also use directly to extract metadata from any IO object:
20
+
21
+ ```rb
22
+ uploader.extract_metadata(io) #=>
23
+ # {
24
+ # "size" => 345993,
25
+ # "filename" => "matrix.mp4",
26
+ # "mime_type" => "video/mp4",
27
+ # }
28
+ ```
29
+
18
30
  The following metadata is extracted by default:
19
31
 
20
32
  | Key | Default source |
@@ -23,7 +35,9 @@ The following metadata is extracted by default:
23
35
  | `mime_type` | extracted from `io.content_type` |
24
36
  | `size` | extracted from `io.size` |
25
37
 
26
- You can access extracted metadata in three ways:
38
+ ## Accessing metadata
39
+
40
+ You can access the stored metadata in three ways:
27
41
 
28
42
  ```rb
29
43
  # via methods (if they're defined)
@@ -42,17 +56,7 @@ uploaded_file["filename"]
42
56
  uploaded_file["mime_type"]
43
57
  ```
44
58
 
45
- Under the hood, `Shrine#upload` calls `Shrine#extract_metadata`, which you can
46
- also use directly to extract metadata from any IO object:
47
-
48
- ```rb
49
- uploader.extract_metadata(io) #=>
50
- # {
51
- # "size" => 345993,
52
- # "filename" => "matrix.mp4",
53
- # "mime_type" => "video/mp4",
54
- # }
55
- ```
59
+ ## Controlling extraction
56
60
 
57
61
  `Shrine#upload` accepts a `:metadata` option which accepts the following values:
58
62
 
@@ -85,11 +89,11 @@ By default, the `mime_type` metadata will be copied over from the
85
89
  `#content_type` attribute of the input file (if present). However, since
86
90
  `#content_type` value comes from the `Content-Type` header of the upload
87
91
  request, it's *not guaranteed* to hold the actual MIME type of the file (browser
88
- determines this header based on file extension). Moreover, only
89
- `ActionDispatch::Http::UploadedFile`, `Shrine::RackFile`, and
90
- `Shrine::DataFile` objects have `#content_type` defined, so, when uploading
91
- simple file objects, `mime_type` will be nil. That makes relying on
92
- `#content_type` both a security risk and limiting.
92
+ determines this header based on file extension).
93
+
94
+ Moreover, only `ActionDispatch::Http::UploadedFile`, `Shrine::RackFile`, and
95
+ `Shrine::DataFile` objects have `#content_type` defined, so when uploading
96
+ objects such as `File`, the `mime_type` value will be nil by default.
93
97
 
94
98
  To remedy that, Shrine comes with a
95
99
  [`determine_mime_type`][determine_mime_type] plugin which is able to extract
@@ -112,15 +116,14 @@ You can choose different analyzers, and even mix-and-match them. See the
112
116
 
113
117
  ## Image Dimensions
114
118
 
115
- Shrine comes with a `store_dimensions` plugin for extracting image dimensions.
116
- It adds `width` and `height` metadata values, and also adds `#width`,
117
- `#height`, and `#dimensions` methods to the `Shrine::UploadedFile` object. By
118
- default, the plugin uses [FastImage] to analyze dimensions, but you can also
119
- have it use [MiniMagick] or [ruby-vips]:
119
+ Shrine comes with a [`store_dimensions`][store_dimensions] plugin for
120
+ extracting image dimensions. It adds `width` and `height` metadata values, and
121
+ also adds `#width`, `#height`, and `#dimensions` methods to the
122
+ `Shrine::UploadedFile` object.
120
123
 
121
124
  ```rb
122
125
  # Gemfile
123
- gem "fastimage"
126
+ gem "fastimage" # default analyzer
124
127
  ```
125
128
  ```rb
126
129
  Shrine.plugin :store_dimensions
@@ -136,12 +139,17 @@ uploaded_file.height #=> 900
136
139
  uploaded_file.dimensions #=> [1600, 900]
137
140
  ```
138
141
 
142
+ By default, the plugin uses [FastImage] to analyze dimensions, but you can also
143
+ have it use [MiniMagick] or [ruby-vips]. See the
144
+ [`store_dimensions`][store_dimensions] plugin docs for more details.
145
+
139
146
  ## Custom metadata
140
147
 
141
148
  In addition to the built-in metadata, Shrine allows you to extract and store
142
- any custom metadata, using the `add_metadata` plugin (which extends
143
- `Shrine#extract_metadata`). For example, you might want to extract EXIF data
144
- from images:
149
+ any custom metadata, using the [`add_metadata`][add_metadata] plugin (which
150
+ internally extends `Shrine#extract_metadata`).
151
+
152
+ For example, you might want to extract EXIF data from images:
145
153
 
146
154
  ```rb
147
155
  # Gemfile
@@ -203,9 +211,20 @@ uploaded_file.metadata #=>
203
211
 
204
212
  The yielded `io` object will not always be an object that responds to `#path`.
205
213
  For example, with the `data_uri` plugin the `io` can be a `StringIO` wrapper,
206
- with `restore_cached_data` or `refresh_metadata` plugins the `io` might be a
207
- `Shrine::UploadedFile` object. So we're using `Shrine.with_file` to ensure we
208
- have a file object.
214
+ while with `restore_cached_data` or `refresh_metadata` plugins the `io` might
215
+ be a `Shrine::UploadedFile` object. So, we're using `Shrine.with_file` to
216
+ ensure we have a file object.
217
+
218
+ ### Adding metadata
219
+
220
+ If you wish to add metadata to an already attached file, you can do it as
221
+ follows:
222
+
223
+ ```rb
224
+ photo.image_attacher.add_metadata("foo" => "bar")
225
+ photo.image.metadata #=> { ..., "foo" => "bar" }
226
+ photo.save # persist changes
227
+ ```
209
228
 
210
229
  ## Metadata columns
211
230
 
@@ -225,15 +244,20 @@ photo.image_type #=> "image/jpeg"
225
244
  When attaching files that were uploaded directly to the cloud or a [tus
226
245
  server], Shrine won't automatically extract metadata from them, instead it will
227
246
  copy any existing metadata that was set on the client side. The reason why this
228
- is the default behaviour is because extracting the metadata would require (at
229
- least partially) retrieving file content from the storage, which could
230
- potentially be expensive depending on the storage and the type of metadata
231
- being extracted.
247
+ is the default behaviour is because metadata extraction requires (at least
248
+ partially) retrieving file content from the storage, which could potentially be
249
+ expensive depending on the storage and the type of metadata being extracted.
250
+
251
+ ```rb
252
+ # no additional metadata will be extracted in this assignment by default
253
+ photo.image = '{"id":"9e6581a4ea1.jpg","storage":"cache","metadata":{...}}'
254
+ ```
232
255
 
233
- There are two ways of extracting metadata from directly uploaded files. If you
234
- want metadata to be automatically extracted on assignment (which is useful if
235
- you want to validate the extracted metadata or have it immediately available
236
- for any other reason), you can load the `restore_cached_data` plugin:
256
+ ### Extracting on attachment
257
+
258
+ If you want metadata to be automatically extracted on assignment (which is
259
+ useful if you want to validate the extracted metadata or have it immediately
260
+ available for any other reason), you can load the `restore_cached_data` plugin:
237
261
 
238
262
  ```rb
239
263
  Shrine.plugin :restore_cached_data # automatically extract metadata from cached files on assignment
@@ -248,7 +272,9 @@ photo.image.metadata #=>
248
272
  # }
249
273
  ```
250
274
 
251
- ### Backgrounding
275
+ ### Extracting in the background
276
+
277
+ #### A) Extracting with promotion
252
278
 
253
279
  If you're using [backgrounding], you can extract metadata during background
254
280
  promotion using the `refresh_metadata` plugin (which the `restore_cached_data`
@@ -271,12 +297,14 @@ class PromoteJob
271
297
  record = Object.const_get(record_class).find(record_id) # if using Active Record
272
298
 
273
299
  attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
274
- attacher.refresh_metadata!
300
+ attacher.refresh_metadata! # extract metadata
275
301
  attacher.atomic_promote
276
302
  end
277
303
  end
278
304
  ```
279
305
 
306
+ #### B) Extracting separately from promotion
307
+
280
308
  You can also extract metadata in the background separately from promotion:
281
309
 
282
310
  ```rb
@@ -303,11 +331,13 @@ class MetadataJob
303
331
  end
304
332
  ```
305
333
 
334
+ ### Combining foreground and background
335
+
306
336
  If you have some metadata that you want to extract in the foreground and some
307
337
  that you want to extract in the background, you can use the uploader context:
308
338
 
309
339
  ```rb
310
- class MyUploader < Shrine
340
+ class VideoUploader < Shrine
311
341
  plugin :add_metadata
312
342
 
313
343
  add_metadata do |io, **options|
@@ -324,7 +354,7 @@ class MyUploader < Shrine
324
354
  end
325
355
  ```
326
356
  ```rb
327
- class MetadataJob
357
+ class PromoteJob
328
358
  include Sidekiq::Worker
329
359
 
330
360
  def perform(attacher_class, record_class, record_id, name, file_data)
@@ -332,12 +362,16 @@ class MetadataJob
332
362
  record = Object.const_get(record_class).find(record_id) # if using Active Record
333
363
 
334
364
  attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
335
- attacher.refresh_metadata!(background: true)
336
- attacher.atomic_persist
365
+ attacher.refresh_metadata!(background: true) # specify the flag
366
+ attacher.atomic_promote
337
367
  end
338
368
  end
339
369
  ```
340
370
 
371
+ Now triggering metadata extraction in the controller on attachment (using
372
+ `restore_cached_data` or `refresh_metadata` plugin) will skip the video
373
+ metadata block, which will be triggered later in the background job.
374
+
341
375
  ### Optimizations
342
376
 
343
377
  If you want to do both metadata extraction and file processing during
@@ -389,4 +423,6 @@ end
389
423
  [ruby-vips]: https://github.com/libvips/ruby-vips
390
424
  [tus server]: https://github.com/janko/tus-ruby-server
391
425
  [determine_mime_type]: https://shrinerb.com/docs/plugins/determine_mime_type
426
+ [store_dimensions]: https://shrinerb.com/docs/plugins/store_dimensions
427
+ [add_metadata]: https://shrinerb.com/docs/plugins/add_metadata
392
428
  [backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
@@ -14,7 +14,7 @@ relationship with the main table, and files will be attached on the records in
14
14
  the new table. That way each record from the main table can implicitly have
15
15
  multiple attachments through the associated records.
16
16
 
17
- ```plaintext
17
+ ```
18
18
  album1
19
19
  photo1
20
20
  - attachment1
@@ -74,13 +74,15 @@ Sequel.migration do
74
74
  change do
75
75
  create_table :albums do
76
76
  primary_key :id
77
- column :title, :text
77
+
78
+ String :title
78
79
  end
79
80
 
80
81
  create_table :photos do
81
82
  primary_key :id
82
83
  foreign_key :album_id, :albums
83
- column :image_data, :text
84
+
85
+ String :image_data
84
86
  end
85
87
  end
86
88
  end
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: Shrine for Paperclip Users
2
+ title: Upgrading from Paperclip
3
3
  ---
4
4
 
5
5
  This guide is aimed at helping Paperclip 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 Paperclip and Shrine
9
- 2. Instructions how to migrate and existing app that uses Paperclip to Shrine
9
+ 2. Instructions how to migrate an existing app that uses Paperclip to Shrine
10
10
  3. Extensive reference of Paperclip's interface with Shrine equivalents
11
11
 
12
12
  ## Overview
@@ -136,12 +136,12 @@ into separate columns.
136
136
  While Paperclip works only with Active Record, Shrine is designed to integrate
137
137
  with any persistence library (there are integrations for [Active
138
138
  Record][activerecord], [Sequel][sequel], [ROM][rom], [Hanami][hanami] and
139
- [Hanami][mongoid]), and can also be used standalone:
139
+ [Mongoid][mongoid]), and can also be used standalone:
140
140
 
141
141
  ```rb
142
142
  attacher = ImageUploader::Attacher.new
143
143
  attacher.attach File.open("nature.jpg")
144
- attacher.file #=> #<Shrine::UploadedFile @id="f4ba5bdbf366ef0b.jpg" ...>
144
+ attacher.file #=> #<Shrine::UploadedFile id="f4ba5bdbf366ef0b.jpg" ...>
145
145
  attacher.url #=> "https://my-bucket.s3.amazonaws.com/f4ba5bdbf366ef0b.jpg"
146
146
  attacher.data #=> { "id" => "f4ba5bdbf366ef0b.jpg", "storage" => "store", "metadata" => { ... } }
147
147
  ```
@@ -182,7 +182,7 @@ require "image_processing/mini_magick"
182
182
  class ImageUploader < Shrine
183
183
  plugin :derivatives
184
184
 
185
- Attacher.derivatives_processor do |original|
185
+ Attacher.derivatives do |original|
186
186
  magick = ImageProcessing::MiniMagick.source(original)
187
187
 
188
188
  {
@@ -206,15 +206,15 @@ uploaded processed files into the database (including any extracted metadata),
206
206
  which then becomes the source of truth on which versions have been generated.
207
207
 
208
208
  ```rb
209
- photo.image #=> #<Shrine::UploadedFile @id="original.jpg" ...>
209
+ photo.image #=> #<Shrine::UploadedFile id="original.jpg" ...>
210
210
  photo.image_derivatives #=> {}
211
211
 
212
212
  photo.image_derivatives! # triggers processing
213
213
  photo.image_derivatives #=>
214
214
  # {
215
- # large: #<Shrine::UploadedFile @id="large.jpg" @metadata={"size"=>873232, ...} ...>,
216
- # medium: #<Shrine::UploadedFile @id="medium.jpg" @metadata={"size"=>94823, ...} ...>,
217
- # small: #<Shrine::UploadedFile @id="small.jpg" @metadata={"size"=>37322, ...} ...>,
215
+ # large: #<Shrine::UploadedFile id="large.jpg" metadata={"size"=>873232, ...} ...>,
216
+ # medium: #<Shrine::UploadedFile id="medium.jpg" metadata={"size"=>94823, ...} ...>,
217
+ # small: #<Shrine::UploadedFile id="small.jpg" metadata={"size"=>37322, ...} ...>,
218
218
  # }
219
219
  ```
220
220
 
@@ -297,16 +297,21 @@ file.mime_type #=> "application/x-php"
297
297
  ## Migrating from Paperclip
298
298
 
299
299
  You have an existing app using Paperclip and you want to transfer it to Shrine.
300
- First we need to make new uploads write to the `<attachment>_data` column.
301
- Let's assume we have a `Photo` model with the "image" attachment:
300
+ Let's assume we have a `Photo` model with the "image" attachment.
301
+
302
+ ### 1. Add Shrine column
303
+
304
+ First we need to create the `image_data` column for Shrine:
302
305
 
303
306
  ```rb
304
307
  add_column :photos, :image_data, :text
305
308
  ```
306
309
 
307
- Afterwards we need to make new uploads write to the `image_data` column. This
308
- can be done by including the below module to all models that have Paperclip
309
- attachments:
310
+ ### 2. Dual write
311
+
312
+ Next, we need to make new Paperclip attachments write to the `image_data`
313
+ column. This can be done by including the below module to all models that have
314
+ Paperclip attachments:
310
315
 
311
316
  ```rb
312
317
  require "shrine"
@@ -318,8 +323,7 @@ Shrine.storages = {
318
323
 
319
324
  Shrine.plugin :model
320
325
  Shrine.plugin :derivatives
321
- ```
322
- ```rb
326
+
323
327
  module PaperclipShrineSynchronization
324
328
  def self.included(model)
325
329
  model.before_save do
@@ -336,8 +340,8 @@ module PaperclipShrineSynchronization
336
340
  if attachment.size.present?
337
341
  attacher.set shrine_file(attachment)
338
342
 
339
- attachment.styles.each do |name, style|
340
- attacher.merge_derivatives(name => shrine_file(style))
343
+ attachment.styles.each do |style_name, style|
344
+ attacher.merge_derivatives(style_name => shrine_file(style))
341
345
  end
342
346
  else
343
347
  attacher.set nil
@@ -372,7 +376,7 @@ module PaperclipShrineSynchronization
372
376
  # If you'll be using a `:prefix` on your Shrine storage, or you're storing
373
377
  # files on the filesystem, make sure to subtract the appropriate part
374
378
  # from the path assigned to `:id`.
375
- def style_to_shrine_data(style)
379
+ def shrine_style_file(style)
376
380
  Shrine.uploaded_file(
377
381
  storage: :store,
378
382
  id: style.attachment.path(style.name),
@@ -389,34 +393,35 @@ end
389
393
  ```
390
394
 
391
395
  After you deploy this code, the `image_data` column should now be successfully
392
- synchronized with new attachments. Next step is to run a script which writes
393
- all existing Paperclip attachments to `image_data`:
396
+ synchronized with new attachments.
397
+
398
+ ### 3. Data migration
399
+
400
+ Next step is to run a script which writes all existing Paperclip attachments to
401
+ `image_data`:
394
402
 
395
403
  ```rb
396
404
  Photo.find_each do |photo|
397
- Paperclip::AttachmentRegistry.each_definition do |klass, name, options|
398
- photo.write_shrine_data(name) if klass == Photo
399
- end
405
+ photo.write_shrine_data(:image)
400
406
  photo.save!
401
407
  end
402
408
  ```
403
409
 
410
+ ### 4. Rewrite code
411
+
404
412
  Now you should be able to rewrite your application so that it uses Shrine
405
- instead of Paperclip, using equivalent Shrine storages. For help with
406
- translating the code from Paperclip to Shrine, you can consult the reference
407
- below.
413
+ instead of Paperclip (you can consult the reference in the next section). You
414
+ can remove the `PaperclipShrineSynchronization` module as well.
408
415
 
409
- You'll notice that Shrine metadata will be absent from the migrated files' data
410
- (specifically versions). You can run a script that will fill in any missing
411
- metadata defined in your Shrine uploader:
416
+ ### 5. Remove Paperclip columns
412
417
 
413
- ```rb
414
- Shrine.plugin :refresh_metadata
418
+ If everything is looking good, we can remove Paperclip columns:
415
419
 
416
- Photo.find_each do |photo|
417
- photo.image_attacher.refresh_metadata!
418
- photo.save
419
- end
420
+ ```rb
421
+ remove_column :photos, :image_file_name
422
+ remove_column :photos, :image_file_size
423
+ remove_column :photos, :image_content_type
424
+ remove_column :photos, :image_updated_at
420
425
  ```
421
426
 
422
427
  ## Paperclip to Shrine direct mapping
@@ -457,8 +462,8 @@ Processing is defined by using the `derivatives` plugin:
457
462
  class ImageUploader < Shrine
458
463
  plugin :derivatives
459
464
 
460
- Attacher.derivatives_processor do |original|
461
- magick = ImageProcessing::MiniMagick.source(image)
465
+ Attacher.derivatives do |original|
466
+ magick = ImageProcessing::MiniMagick.source(original)
462
467
 
463
468
  {
464
469
  large: magick.resize_to_limit!(800, 800),
@@ -477,8 +482,8 @@ For default URLs you can use the `default_url` plugin:
477
482
  class ImageUploader < Shrine
478
483
  plugin :default_url
479
484
 
480
- Attacher.default_url do |options|
481
- "/attachments/#{name}/default.jpg"
485
+ Attacher.default_url do |derivative: nil, **|
486
+ "/images/placeholders/#{derivative || "original"}.jpg"
482
487
  end
483
488
  end
484
489
  ```
@@ -506,16 +511,79 @@ Alternatively, if you want to generate locations yourself you can override the
506
511
 
507
512
  ```rb
508
513
  class ImageUploader < Shrine
509
- def generate_location(io, **options)
510
- # ...
514
+ def generate_location(io, record: nil, name: nil, **)
515
+ [ storage_key,
516
+ record && record.class.name.underscore,
517
+ record && record.id,
518
+ super,
519
+ io.original_filename ].compact.join("/")
511
520
  end
512
521
  end
513
522
  ```
523
+ ```
524
+ cache/user/123/2feff8c724e7ce17/nature.jpg
525
+ store/user/456/7f99669fde1e01fc/kitten.jpg
526
+ ...
527
+ ```
514
528
 
515
529
  #### `:validate_media_type`
516
530
 
517
531
  Shrine has this functionality in the `determine_mime_type` plugin.
518
532
 
533
+ ### `validates_attachment`
534
+
535
+ #### `:presence`
536
+
537
+ For presence validation you can use your ORM's presence validator:
538
+
539
+ ```rb
540
+ class Photo < ActiveRecord::Base
541
+ include ImageUploader::Attachment(:image)
542
+ validates_presence_of :image
543
+ end
544
+ ```
545
+
546
+ #### `:content_type`
547
+
548
+ You can do MIME type validation with Shrine's `validation_helpers` plugin:
549
+
550
+ ```rb
551
+ class ImageUploader < Shrine
552
+ plugin :validation_helpers
553
+
554
+ Attacher.validate do
555
+ validate_mime_type %w[image/jpeg image/png image/webp]
556
+ end
557
+ end
558
+ ```
559
+
560
+ Make sure to also load the `determine_mime_type` plugin to detect MIME type
561
+ from file content.
562
+
563
+ ```rb
564
+ # Gemfile
565
+ gem "mimemagic"
566
+ ```
567
+ ```rb
568
+ Shrine.plugin :determine_mime_type, analyzer: -> (io, analyzers) do
569
+ analyzers[:mimemagic].call(io) || analyzers[:file].call(io)
570
+ end
571
+ ```
572
+
573
+ #### `:size`
574
+
575
+ You can do filesize validation with Shrine's `validation_helpers` plugin:
576
+
577
+ ```rb
578
+ class ImageUploader < Shrine
579
+ plugin :validation_helpers
580
+
581
+ Attacher.validate do
582
+ validate_max_size 10*1024*1024
583
+ end
584
+ end
585
+ ```
586
+
519
587
  ### `Paperclip::Attachment`
520
588
 
521
589
  This section explains the equivalent of Paperclip attachment's methods, in