shrine 2.5.0 → 2.6.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -13
  3. data/doc/attacher.md +7 -6
  4. data/doc/carrierwave.md +19 -17
  5. data/doc/design.md +1 -1
  6. data/doc/direct_s3.md +8 -5
  7. data/doc/multiple_files.md +4 -4
  8. data/doc/paperclip.md +7 -6
  9. data/doc/refile.md +67 -4
  10. data/doc/securing_uploads.md +41 -25
  11. data/doc/testing.md +6 -15
  12. data/lib/shrine.rb +19 -10
  13. data/lib/shrine/plugins/activerecord.rb +4 -4
  14. data/lib/shrine/plugins/add_metadata.rb +7 -3
  15. data/lib/shrine/plugins/background_helpers.rb +1 -1
  16. data/lib/shrine/plugins/backgrounding.rb +19 -6
  17. data/lib/shrine/plugins/cached_attachment_data.rb +4 -4
  18. data/lib/shrine/plugins/data_uri.rb +105 -31
  19. data/lib/shrine/plugins/default_url.rb +1 -1
  20. data/lib/shrine/plugins/delete_raw.rb +7 -3
  21. data/lib/shrine/plugins/determine_mime_type.rb +96 -44
  22. data/lib/shrine/plugins/direct_upload.rb +3 -1
  23. data/lib/shrine/plugins/download_endpoint.rb +14 -5
  24. data/lib/shrine/plugins/logging.rb +4 -4
  25. data/lib/shrine/plugins/metadata_attributes.rb +61 -0
  26. data/lib/shrine/plugins/migration_helpers.rb +1 -1
  27. data/lib/shrine/plugins/rack_file.rb +54 -30
  28. data/lib/shrine/plugins/recache.rb +1 -1
  29. data/lib/shrine/plugins/refresh_metadata.rb +29 -0
  30. data/lib/shrine/plugins/remote_url.rb +26 -4
  31. data/lib/shrine/plugins/remove_invalid.rb +5 -4
  32. data/lib/shrine/plugins/restore_cached_data.rb +10 -13
  33. data/lib/shrine/plugins/sequel.rb +4 -4
  34. data/lib/shrine/plugins/signature.rb +146 -0
  35. data/lib/shrine/plugins/store_dimensions.rb +68 -24
  36. data/lib/shrine/plugins/validation_helpers.rb +48 -29
  37. data/lib/shrine/plugins/versions.rb +16 -8
  38. data/lib/shrine/storage/file_system.rb +27 -16
  39. data/lib/shrine/storage/s3.rb +99 -58
  40. data/lib/shrine/version.rb +1 -1
  41. data/shrine.gemspec +1 -1
  42. metadata +9 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9bfafd65b7f7fe8b99b1c5f93ef378bfc3dc7eac
4
- data.tar.gz: 698ed63662d4394c346f5ae2946e892de53d694e
3
+ metadata.gz: af515b9a7c0e52d86cc58b7765dd6f894ab152e5
4
+ data.tar.gz: 6fa6cf185cc41cb6185538423bf144f8dd854f40
5
5
  SHA512:
6
- metadata.gz: 2fcf2377c360c399342238366a1458f48f078d7b946b52af09b3e9acdbc8761e7b07270700f6a9b09b4195a43536947d6fc145da10c75667f88bd95dd9284edf
7
- data.tar.gz: f293b1ba574687eae1edc8abad8fbdfbb2db2a4028470964f612ccf51a9e74a8dd810caf2e8dc6c784b9136fc6503ac168175d955404b923def1fd89eafc1a61
6
+ metadata.gz: f5a3a92fa19e363cffc64853ff5aafd1385d200698b6fc803606d366c487eca53f2c39c1ea3fc436613f3ed16916da1b0587a0ffa9672c6bd7175b2ec3c2000b
7
+ data.tar.gz: fdc756c2aea5c035348cb2f640edcfdc2773e8ea1c2ed041ae9d270c2a609e7f5500e79c352979e47f37736dec033cb9fae34c4a2ff439d60ddc673bbf3a8c0f
data/README.md CHANGED
@@ -31,7 +31,7 @@ Shrine.storages = {
31
31
  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), # permanent
32
32
  }
33
33
 
34
- Shrine.plugin :sequel # :activerecord
34
+ Shrine.plugin :sequel # or :activerecord
35
35
  Shrine.plugin :cached_attachment_data # for forms
36
36
  Shrine.plugin :rack_file # for non-Rails apps
37
37
  ```
@@ -60,7 +60,7 @@ end
60
60
 
61
61
  ```rb
62
62
  class Photo < Sequel::Model # ActiveRecord::Base
63
- include ImageUploader[:image] # adds an `image` virtual attribute
63
+ include ImageUploader::Attachment.new(:image) # adds an `image` virtual attribute
64
64
  end
65
65
  ```
66
66
 
@@ -265,9 +265,9 @@ Shrine.plugin :sequel # :activerecord
265
265
 
266
266
  ```rb
267
267
  class Photo < Sequel::Model # ActiveRecord::Base
268
- include ImageUploader[:image] #
269
- include ImageUploader.attachment(:image) # these are all equivalent
270
268
  include ImageUploader::Attachment.new(:image) #
269
+ include ImageUploader.attachment(:image) # these are all equivalent
270
+ include ImageUploader[:image] #
271
271
  end
272
272
  ```
273
273
 
@@ -292,13 +292,13 @@ photo.image #=> nil
292
292
  photo.image = File.open("waterfall.jpg")
293
293
  photo.image #=> #<Shrine::UploadedFile @data={...}>
294
294
  photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg"
295
- photo.image_data #=> '{"storage":"cache","id":"0sdfllasfi842.jpg","metadata":{...}}'
295
+ photo.image_data #=> '{"id":"0sdfllasfi842.jpg","storage":"cache","metadata":{...}}'
296
296
 
297
297
  # the cached file is promoted to permanent storage and saved to `image_data` column
298
298
  photo.save
299
299
  photo.image #=> #<Shrine::UploadedFile @data={...}>
300
300
  photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg"
301
- photo.image_data #=> '{"storage":"store","id":"l02kladf8jlda.jpg","metadata":{...}}'
301
+ photo.image_data #=> '{"id":"l02kladf8jlda.jpg","storage":"store","metadata":{...}}'
302
302
 
303
303
  # the attached file is deleted with the record
304
304
  photo.destroy
@@ -322,8 +322,8 @@ uploads], via the hidden form field.
322
322
 
323
323
  ```rb
324
324
  photo.image = '{
325
- "storage": "cache",
326
325
  "id": "9260ea09d8effd.jpg",
326
+ "storage": "cache",
327
327
  "metadata": { ... }
328
328
  }'
329
329
  ```
@@ -486,7 +486,7 @@ class ImageUploader < Shrine
486
486
  plugin :processing
487
487
 
488
488
  process(:store) do |io, context|
489
- resize_to_limit!(io.download, 800, 800)
489
+ resize_to_limit!(io.download, 800, 800) { |cmd| cmd.auto_orient } # orient rotated images
490
490
  end
491
491
  end
492
492
  ```
@@ -524,7 +524,7 @@ class ImageUploader < Shrine
524
524
  process(:store) do |io, context|
525
525
  original = io.download
526
526
 
527
- size_800 = resize_to_limit!(original, 800, 800)
527
+ size_800 = resize_to_limit!(original, 800, 800) { |cmd| cmd.auto_orient } # orient rotated images
528
528
  size_500 = resize_to_limit(size_800, 500, 500)
529
529
  size_300 = resize_to_limit(size_500, 300, 300)
530
530
 
@@ -639,7 +639,7 @@ class DocumentUploader < Shrine
639
639
 
640
640
  Attacher.validate do
641
641
  validate_max_size 5*1024*1024, message: "is too large (max is 5 MB)"
642
- validate_mime_type_inclusion ["application/pdf"]
642
+ validate_mime_type_inclusion %w[application/pdf]
643
643
  end
644
644
  end
645
645
  ```
@@ -744,9 +744,10 @@ The above setup will provide the following endpoints:
744
744
  * `GET /images/cache/presign` - for direct uploads to external service (e.g. Amazon S3)
745
745
 
746
746
  Now when the user selects a file, the client can immediately start uploading
747
- the file asynchronously using one of these endpoints. For JavaScript you can
748
- use generic file upload libraries like [jQuery-File-Upload], [Dropzone] or
749
- [FineUploader].
747
+ the file asynchronously using one of these endpoints. The JSON data of the
748
+ uploaded file can then be written to the hidden attachment field, and submitted
749
+ instead of the file. For JavaScript you can use generic file upload libraries
750
+ like [jQuery-File-Upload], [Dropzone] or [FineUploader].
750
751
 
751
752
  See the [direct_upload] plugin documentation and [Direct Uploads to S3][direct uploads]
752
753
  guide for more details, as well as the [Roda][roda_demo] and
data/doc/attacher.md CHANGED
@@ -7,7 +7,7 @@ fields to the form just works.
7
7
 
8
8
  ```rb
9
9
  class Photo < Sequel::Model
10
- include ImageUploader[:image]
10
+ include ImageUploader::Attachment.new(:image)
11
11
  end
12
12
  ```
13
13
 
@@ -59,8 +59,8 @@ attacher.assign(io)
59
59
 
60
60
  # writes the given cached file to the data column
61
61
  attacher.assign '{
62
- "storage": "cache",
63
62
  "id": "9260ea09d8effd.jpg",
63
+ "storage": "cache",
64
64
  "metadata": { ... }
65
65
  }'
66
66
  ```
@@ -87,14 +87,15 @@ The `#read` method will just return the value of the underlying
87
87
  `<attachment>_data` attribute.
88
88
 
89
89
  ```rb
90
- attacher.read #=> '{"storage":"cache","id":"dsg024lfs.jpg",...}'
90
+ attacher.read #=> '{"id":"dsg024lfs.jpg","storage":"cache","metadata":{...}}'
91
91
  ```
92
92
 
93
93
  In general you can use `#uploaded_file` to contruct a `Shrine::UploadedFile`
94
94
  from a JSON string.
95
95
 
96
96
  ```rb
97
- attacher.uploaded_file('{"storage":"cache","id":"dsg024lfs.jpg",...}') #=> #<Shrine::UploadedFile>
97
+ attachment_data = '{"id":"dsg024lfs.jpg","storage":"cache","metadata":{...}}'
98
+ attacher.uploaded_file(attachment_data) #=> #<Shrine::UploadedFile>
98
99
  ```
99
100
 
100
101
  ## URL
@@ -133,8 +134,8 @@ to permanent storage after the record is saved. You can use `#finalize` for
133
134
  that, since that will also automatically delete any previously attached files.
134
135
 
135
136
  ```rb
136
- # We run the finalization only if a new file was attached
137
- attacher.finalize if attacher.attached?
137
+ # Replaces previous attachment and replaces new
138
+ attacher.finalize
138
139
  ```
139
140
 
140
141
  This is normally automatically added to a callback by the ORM plugin when going
data/doc/carrierwave.md CHANGED
@@ -146,7 +146,7 @@ class ImageUploader < Shrine
146
146
  plugin :validation_helpers
147
147
 
148
148
  Attacher.validate do
149
- validate_extension_inclusion [/jpe?g/, "gif", "png"]
149
+ validate_extension_inclusion %w[jpg jpeg gif png]
150
150
  validate_mime_type_inclusion %w[image/jpeg image/gif image/png]
151
151
  validate_max_size 10*1024*1024 unless record.admin?
152
152
  end
@@ -176,7 +176,7 @@ end
176
176
  ```
177
177
  ```rb
178
178
  class Photo < ActiveRecord::Base
179
- include ImageUploader[:avatar]
179
+ include ImageUploader::Attachment.new(:avatar)
180
180
  end
181
181
  ```
182
182
 
@@ -248,6 +248,7 @@ attachments:
248
248
 
249
249
  ```rb
250
250
  require "fastimage"
251
+ require "mime/types"
251
252
 
252
253
  module CarrierwaveShrineSynchronization
253
254
  def self.included(model)
@@ -285,8 +286,8 @@ module CarrierwaveShrineSynchronization
285
286
  path = uploader.store_path(read_attribute(uploader.mounted_as))
286
287
 
287
288
  size = uploader.file.size if changes.key?(uploader.mounted_as)
288
- size ||= FastImage.new(uploader.url).content_length
289
- size ||= File.size(File.join(uploader.root, path))
289
+ size ||= FastImage.new(uploader.url).content_length # OPTIONAL (makes an HTTP request)
290
+ size ||= File.size(File.join(uploader.root, path)) if File.exist?(path)
290
291
  filename = File.basename(path)
291
292
  mime_type = MIME::Types.type_for(path).first.to_s.presence
292
293
 
@@ -451,24 +452,24 @@ class ImageUploader < Shrine
451
452
  plugin :validation_helpers
452
453
 
453
454
  Attacher.validate do
454
- validate_extension_inclusion [/jpe?g/, 'png'] # whitelist
455
- validate_extension_exclusion ['php'] # blacklist
455
+ validate_extension_inclusion %w[jpg jpeg png] # whitelist
456
+ validate_extension_exclusion %w[php] # blacklist
456
457
  end
457
458
  end
458
459
  ```
459
460
 
460
- #### `#blacklist_mime_type_pattern`, `#whitelist_mime_type_pattern`
461
+ #### `#blacklist_mime_type_pattern`, `#whitelist_mime_type_pattern`, `#content_type_whitelist`, `#content_type_blacklist`
461
462
 
462
463
  In Shrine MIME type whitelisting/blacklisting is part of validations, and is
463
- provided by the `validation_helpers` plugin:
464
+ provided by the `validation_helpers` plugin, though it doesn't support regexes:
464
465
 
465
466
  ```rb
466
467
  class ImageUploader < Shrine
467
468
  plugin :validation_helpers
468
469
 
469
470
  Attacher.validate do
470
- validate_mime_type_inclusion [/image/] # whitelist
471
- validate_mime_type_exclusion [/video/] # blacklist
471
+ validate_mime_type_inclusion %w[image/jpeg image/png] # whitelist
472
+ validate_mime_type_exclusion %w[text/x-php] # blacklist
472
473
  end
473
474
  end
474
475
  ```
@@ -511,7 +512,7 @@ Shrine.plugin :sequel
511
512
  ```
512
513
  ```rb
513
514
  class User < Sequel::Model
514
- include ImageUploader[:avatar]
515
+ include ImageUploader::Attachment.new(:avatar)
515
516
  end
516
517
  ```
517
518
 
@@ -554,18 +555,19 @@ argument (`user.avatar_url(:thumb)`).
554
555
 
555
556
  #### `#<attachment>_cache`
556
557
 
557
- Shrine doesn't provide this method, instead it expects to recieve the
558
- attachment through the accessor, you can assign it `<attachment>_data`:
558
+ Shrine has the `cached_attachment_data` plugin, which gives model a reader method
559
+ that you can use for retaining the cached file:
559
560
 
561
+ ```rb
562
+ Shrine.plugin :cached_attachment_data
563
+ ```
560
564
  ```erb
561
565
  <%= form_for @user do |f| %>
562
- <%= f.hidden_field :avatar, value: @user.avatar_data %>
566
+ <%= f.hidden_field :avatar, value: @user.cached_avatar_data %>
563
567
  <%= f.file_field :avatar %>
564
568
  <% end %>
565
569
  ```
566
570
 
567
- You might also want to look at the `cached_attachment_data` plugin.
568
-
569
571
  #### `#remote_<attachment>_url`
570
572
 
571
573
  In Shrine this method is provided by the `remote_url` plugin.
@@ -623,7 +625,7 @@ class ImageUploader < Shrine
623
625
  # Evaluated inside an instance of Shrine::Attacher.
624
626
  if record.guest?
625
627
  validate_max_size 2*1024*1024, message: "is too large (max is 2 MB)"
626
- validate_mime_type_inclusion ["image/jpg", "image/png", "image/gif"]
628
+ validate_mime_type_inclusion %w[image/jpg image/png image/gif]
627
629
  end
628
630
  end
629
631
  end
data/doc/design.md CHANGED
@@ -187,7 +187,7 @@ We can include this module to a model:
187
187
 
188
188
  ```rb
189
189
  class Photo
190
- include Shrine[:image]
190
+ include Shrine::Attachment.new(:image)
191
191
  end
192
192
  ```
193
193
  ```rb
data/doc/direct_s3.md CHANGED
@@ -13,11 +13,14 @@ beneficial for several use cases:
13
13
  * With multiple servers it's generally not possible to cache files to the disk,
14
14
  unless you're using a distibuted filesystem that's shared between servers.
15
15
 
16
- * Heroku restricts file uploads to disk, allowing you to save files only in
17
- the temporary folder, which gets wiped out between deploys.
16
+ * On Heroku any uploaded files that aren't part of version control don't persist,
17
+ they get removed each time you do a new deploy or when the dyno automatically
18
+ changes the location.
18
19
 
19
- * Heroku has a 30-second request limit, so if the client has a slow connection
20
- and/or your files are larger, uploads to your app can easily hit that limit.
20
+ * If your request workers have a timeout configured or you're using Heroku,
21
+ uploading a large files to S3 or any external service inside the
22
+ request-response lifecycle might not be able to finish before the request
23
+ times out.
21
24
 
22
25
  You can start by setting both temporary and permanent storage to S3 with
23
26
  different prefixes (or even buckets):
@@ -108,7 +111,7 @@ necessary request parameters:
108
111
  {
109
112
  "url" => "https://my-bucket.s3-eu-west-1.amazonaws.com",
110
113
  "fields" => {
111
- "key" => "b7d575850ba61b44c8a9ff889dfdb14d88cdc25f8dd121004c8",
114
+ "key" => "cache/b7d575850ba61b44c8a9ff889dfdb14d88cdc25f8dd121004c8",
112
115
  "policy" => "eyJleHBpcmF0aW9uIjoiMjAxNS0QwMToxMToyOVoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaHJpbmUtdGVzdGluZyJ9LHsia2V5IjoiYjdkNTc1ODUwYmE2MWI0NGU3Y2M4YTliZmY4OGU5ZGZkYjE2NTQ0ZDk4OGNkYzI1ZjhkZDEyMTAwNGM4In0seyJ4LWFtei1jcmVkZW50aWFsIjoiQUtJQUlKRjU1VE1aWlk0NVVUNlEvMjAxNTEwMjQvZXUtd2VzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotYWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsieC1hbXotZGF0ZSI6IjIwMTUxMDI0VDAwMTEyOVoifV19",
113
116
  "x-amz-credential" => "AKIAIJF55TMZYT6Q/20151024/eu-west-1/s3/aws4_request",
114
117
  "x-amz-algorithm" => "AWS4-HMAC-SHA256",
@@ -60,7 +60,7 @@ In our new model we can create a Shrine attachment attribute:
60
60
 
61
61
  ```rb
62
62
  class Photo < Sequel::Model
63
- include ImageUploader[:image]
63
+ include ImageUploader::Attachment.new(:image)
64
64
  end
65
65
  ```
66
66
 
@@ -96,9 +96,9 @@ After each upload finishes, you can generate a nested hash for the new
96
96
  associated record, and write the uploaded file JSON to the attachment field:
97
97
 
98
98
  ```rb
99
- album[photos_attributes][0][image] = '{"storage":"cache","id":"38k25.jpg","metadata":{...}}'
100
- album[photos_attributes][1][image] = '{"storage":"cache","id":"sg0fg.jpg","metadata":{...}}'
101
- album[photos_attributes][2][image] = '{"storage":"cache","id":"041jd.jpg","metadata":{...}}'
99
+ album[photos_attributes][0][image] = '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
100
+ album[photos_attributes][1][image] = '{"id":"sg0fg.jpg","storage":"cache","metadata":{...}}'
101
+ album[photos_attributes][2][image] = '{"id":"041jd.jpg","storage":"cache","metadata":{...}}'
102
102
  ```
103
103
 
104
104
  Once you submit this to the app, the ORM's nested attributes behaviour will
data/doc/paperclip.md CHANGED
@@ -64,7 +64,7 @@ class ImageUploader < Shrine
64
64
  end
65
65
 
66
66
  class Photo < ActiveRecord::Base
67
- include ImageUploader[:image]
67
+ include ImageUploader::Attachment.new(:image)
68
68
  end
69
69
  ```
70
70
 
@@ -204,7 +204,7 @@ gives your models similar set of methods that Paperclip gives:
204
204
 
205
205
  ```rb
206
206
  class Photo < Sequel::Model
207
- include ImageUploader[:image]
207
+ include ImageUploader::Attachment.new(:image)
208
208
  end
209
209
  ```
210
210
 
@@ -286,6 +286,7 @@ attachments:
286
286
 
287
287
  ```rb
288
288
  require "fastimage"
289
+ require "mime/types"
289
290
 
290
291
  module PaperclipShrineSynchronization
291
292
  def self.included(model)
@@ -342,8 +343,8 @@ module PaperclipShrineSynchronization
342
343
  file = attachment.instance_variable_get("@queued_for_write")[style.name]
343
344
 
344
345
  size = file.size if file
345
- size ||= FastImage.new(url).content_length
346
- size ||= File.size(path)
346
+ size ||= FastImage.new(url).content_length # OPTIONAL (makes an HTTP request)
347
+ size ||= File.size(path) if File.exist?(path)
347
348
  filename = File.basename(path)
348
349
  mime_type = MIME::Types.type_for(path).first.to_s.presence
349
350
 
@@ -368,7 +369,7 @@ end
368
369
 
369
370
  After you deploy this code, the `image_data` column should now be successfully
370
371
  synchronized with new attachments. Next step is to run a script which writes
371
- all existing CarrierWave attachments to `image_data`:
372
+ all existing Paperclip attachments to `image_data`:
372
373
 
373
374
  ```rb
374
375
  Photo.find_each do |photo|
@@ -393,7 +394,7 @@ an attachment module:
393
394
 
394
395
  ```rb
395
396
  class User < Sequel::Model
396
- include ImageUploader[:avatar] # adds `avatar`, `avatar=` and `avatar_url` methods
397
+ include ImageUploader::Attachment.new(:avatar) # adds `avatar`, `avatar=` and `avatar_url` methods
397
398
  end
398
399
  ```
399
400
 
data/doc/refile.md CHANGED
@@ -118,7 +118,7 @@ class ImageUploader < Shrine
118
118
  end
119
119
 
120
120
  class Photo < Sequel::Model
121
- include ImageUploader[:image]
121
+ include ImageUploader::Attachment.new(:image)
122
122
  end
123
123
  ```
124
124
 
@@ -371,7 +371,7 @@ of an uploader:
371
371
 
372
372
  ```rb
373
373
  class User
374
- include ImageUploader[:avatar]
374
+ include ImageUploader::Attachment.new(:avatar)
375
375
  end
376
376
  ```
377
377
 
@@ -385,8 +385,8 @@ class ImageUploader < Shrine
385
385
  plugin :validation_helpers
386
386
 
387
387
  Attacher.validate do
388
- validate_extension_inclusion [/jpe?g/, "png"]
389
- validate_mime_type_inclusion ["image/jpeg", "image/png"]
388
+ validate_extension_inclusion %w[jpg jpeg png]
389
+ validate_mime_type_inclusion %w[image/jpeg image/png]
390
390
  end
391
391
  end
392
392
  ```
@@ -410,6 +410,68 @@ end
410
410
 
411
411
  No equivalent currently exists in Shrine.
412
412
 
413
+ ### `accepts_attachments_for`
414
+
415
+ No equivalent in Shrine, but take a look at the "[Multiple Files]" guide.
416
+
417
+ ### Form helpers
418
+
419
+ #### `attachment_field`
420
+
421
+ The following Refile code
422
+
423
+ ```erb
424
+ <%= form_for @user do |form| %>
425
+ <%= form.attachment_field :profile_image %>
426
+ <% end %>
427
+ ```
428
+
429
+ is equivalent to the following Shrine code
430
+
431
+ ```rb
432
+ Shrine.plugin :cached_attachment_data
433
+ ```
434
+ ```erb
435
+ <%= form_for @user do |form| %>
436
+ <%= form.hidden_field :profile_image, value: @user.cached_profile_image_data %>
437
+ <%= form.file_field :profile_image %>
438
+ <% end %>
439
+ ```
440
+
441
+ ### Model methods
442
+
443
+ #### `remove_<attachment>`
444
+
445
+ Shrine comes with a `remove_attachment` plugin which adds the same
446
+ `#remove_<attachment>` method to the model.
447
+
448
+ ```rb
449
+ Shrine.plugin :remove_attachment
450
+ ```
451
+ ```erb
452
+ <%= form_for @user do |form| %>
453
+ <%= form.hidden_field :profile_image, value: @user.cached_profile_image_data %>
454
+ <%= form.file_field :profile_image %>
455
+ <%= form.check_box :remove_profile_image %>
456
+ <% end %>
457
+ ```
458
+
459
+ #### `remote_<attachment>_url`
460
+
461
+ Shrine comes with a `remote_url` plugin which adds the same
462
+ `#<attachment>_remote_url` method to the model.
463
+
464
+ ```rb
465
+ Shrine.plugin :remote_url
466
+ ```
467
+ ```erb
468
+ <%= form_for @user do |form| %>
469
+ <%= form.hidden_field :profile_image, value: @user.cached_profile_image_data %>
470
+ <%= form.file_field :profile_image %>
471
+ <%= form.text_field :profile_image_remote_url %>
472
+ <% end %>
473
+ ```
474
+
413
475
  [shrine-cloudinary]: https://github.com/janko-m/shrine-cloudinary
414
476
  [shrine-imgix]: https://github.com/janko-m/shrine-imgix
415
477
  [shrine-uploadcare]: https://github.com/janko-m/shrine-uploadcare
@@ -418,3 +480,4 @@ No equivalent currently exists in Shrine.
418
480
  [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
419
481
  [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
420
482
  [demo app]: https://github.com/janko-m/shrine/tree/master/demo
483
+ [Multiple Files]: http://shrinerb.com/rdoc/files/doc/multiple_files_md.html