shrine 3.1.0 → 3.2.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.

@@ -4,44 +4,47 @@ title: Extensions
4
4
 
5
5
  ## Storages
6
6
 
7
- * [shrine-aliyun-oss](https://github.com/zillou/shrine-aliyun-oss)
8
- * [shrine-cloudinary](https://github.com/shrinerb/shrine-cloudinary)
9
- * [shrine-flickr](https://github.com/shrinerb/shrine-flickr)
10
- * [shrine-fog](https://github.com/shrinerb/shrine-fog)
11
- * [shrine-ftp](https://github.com/ProjectResound/shrine-ftp)
12
- * [shrine-google_cloud_storage](https://github.com/renchap/shrine-google_cloud_storage)
13
- * [shrine-gdrive_storage](https://github.com/edwardsharp/shrine-gdrive_storage)
14
- * [shrine-gridfs](https://github.com/shrinerb/shrine-gridfs)
15
- * [shrine-imgix](https://github.com/shrinerb/shrine-imgix)
16
- * [shrine-memory](https://github.com/shrinerb/shrine-memory)
17
- * [shrine-redis](https://github.com/dbongo/shrine-redis)
18
- * [shrine-scp](https://github.com/jordanandree/shrine-scp)
19
- * [shrine-sql](https://github.com/shrinerb/shrine-sql)
20
- * [shrine-thumbor](https://github.com/havran/shrine-thumbor)
21
- * [shrine-transloadit](https://github.com/shrinerb/shrine-transloadit)
22
- * [shrine-uploadcare](https://github.com/shrinerb/shrine-uploadcare)
23
- * [shrine-url](https://github.com/shrinerb/shrine-url)
24
- * [shrine-storage-you_tube](https://github.com/thedyrt/shrine-storage-you_tube)
25
- * [shrine-webdav](https://github.com/funbox/shrine-webdav)
7
+ | Gem | Description |
8
+ | :---- | :---------- |
9
+ | [shrine-aliyun-oss](https://github.com/zillou/shrine-aliyun-oss) | Storage using [Alibaba Cloud OSS](https://www.alibabacloud.com/product/oss) |
10
+ | [shrine-cloudinary](https://github.com/shrinerb/shrine-cloudinary) | Storage using [Cloudinary](https://cloudinary.com/) |
11
+ | [shrine-flickr](https://github.com/shrinerb/shrine-flickr) | Storage using [Flickr](https://flickr.com/) |
12
+ | [shrine-fog](https://github.com/shrinerb/shrine-fog) | Storage using [Fog](http://fog.io/) |
13
+ | [shrine-ftp](https://github.com/ProjectResound/shrine-ftp) | Storage using an FTP server |
14
+ | [shrine-google_cloud_storage](https://github.com/renchap/shrine-google_cloud_storage) | Storage using [Google Cloud Storage](https://cloud.google.com/storage/) |
15
+ | [shrine-gdrive_storage](https://github.com/edwardsharp/shrine-gdrive_storage) | Storage using [Google Drive](https://www.google.com/drive/) |
16
+ | [shrine-gridfs](https://github.com/shrinerb/shrine-gridfs) | Storage using [Mongo GridFS](https://docs.mongodb.com/manual/core/gridfs/) |
17
+ | [shrine-redis](https://github.com/dbongo/shrine-redis) | Storage using [Redis](https://redis.io/) |
18
+ | [shrine-scp](https://github.com/jordanandree/shrine-scp) | Storage using `scp` |
19
+ | [shrine-sql](https://github.com/shrinerb/shrine-sql) | Storage using an SQL database |
20
+ | [shrine-uploadcare](https://github.com/shrinerb/shrine-uploadcare) | Storage using [Uploadcare](https://uploadcare.com) |
21
+ | [shrine-url](https://github.com/shrinerb/shrine-url) | Storage for handling remote URLs |
22
+ | [shrine-storage-you_tube](https://github.com/thedyrt/shrine-storage-you_tube) | Storage using [YouTube](https://www.youtube.com/) |
23
+ | [shrine-webdav](https://github.com/funbox/shrine-webdav) | Storage using a [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server |
26
24
 
27
25
  ## Plugins
28
26
 
29
- * [administrate-field-shrine](https://github.com/catsky/administrate-field-shrine)
30
- * [rails_admin_shrine](https://github.com/iquest/rails_admin_shrine)
31
- * [shrine-color](https://github.com/jnylen/shrine-color)
32
- * [shrine-configurable_storage](https://github.com/SleeplessByte/shrine-configurable_storage)
33
- * [shrine-content_addressable](https://github.com/SleeplessByte/shrine-content_addressable)
34
- * [shrine-lambda](https://github.com/texpert/shrine-lambda)
35
- * [hanami-shrine](https://github.com/katafrakt/hanami-shrine)
36
- * [shrine-mongoid](https://github.com/shrinerb/shrine-mongoid)
37
- * [shrine-rails](https://github.com/abepetrillo/shrine-rails)
38
- * [shrine-reform](https://github.com/shrinerb/shrine-reform)
39
- * [shrine-rom](https://github.com/shrinerb/shrine-rom)
40
- * [shrine-tus](https://github.com/shrinerb/shrine-tus)
27
+ | Gem | Description |
28
+ | :---- | :-------- |
29
+ | [administrate-field-shrine](https://github.com/catsky/administrate-field-shrine) | Plugin for [Administrate](https://github.com/thoughtbot/administrate) |
30
+ | [rails_admin_shrine](https://github.com/iquest/rails_admin_shrine) | Plugin for [RailsAdmin](https://github.com/sferik/rails_admin) |
31
+ | [shrine-color](https://github.com/jnylen/shrine-color) | Plugin for finding dominant color in an image |
32
+ | [shrine-configurable_storage](https://github.com/SleeplessByte/shrine-configurable_storage) | Plugin for lazy storage registration |
33
+ | [shrine-content_addressable](https://github.com/SleeplessByte/shrine-content_addressable) | Plugin for generating content addressable locations |
34
+ | [shrine-imgix](https://github.com/shrinerb/shrine-imgix) | Plugin for [Imgix](https://www.imgix.com/) |
35
+ | [shrine-transloadit](https://github.com/shrinerb/shrine-transloadit) | Plugin for [Transloadit](https://transloadit.com/) |
36
+ | [shrine-lambda](https://github.com/texpert/shrine-lambda) | Plugin for [AWS Lambda](https://aws.amazon.com/lambda/) |
37
+ | [hanami-shrine](https://github.com/katafrakt/hanami-shrine) | Plugin for [Hanami](https://hanamirb.org/) |
38
+ | [shrine-mongoid](https://github.com/shrinerb/shrine-mongoid) | Plugin for [Mongoid](https://mongoid.org) |
39
+ | [shrine-rails](https://github.com/abepetrillo/shrine-rails) | Plugin for [Rails](https://rubyonrails.org/) |
40
+ | [shrine-rom](https://github.com/shrinerb/shrine-rom) | Plugin for [ROM](https://rom-rb.org/) |
41
+ | [shrine-tus](https://github.com/shrinerb/shrine-tus) | Plugin for [tus](https://tus.io) server integration |
41
42
 
42
43
  ## Libraries
43
44
 
44
- * [ckeditor](https://github.com/galetahub/ckeditor)
45
- * [imgproxy](https://github.com/imgproxy/imgproxy.rb)
46
- * [rails_admin](https://github.com/sferik/rails_admin)
47
- * [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart)
45
+ | Gem | Description |
46
+ | :----- | :------- |
47
+ | [ckeditor](https://github.com/galetahub/ckeditor) | Integration for [CKEditor](https://ckeditor.com/ckeditor-4/) |
48
+ | [imgproxy](https://github.com/imgproxy/imgproxy.rb) | Integration for [imgproxy](https://github.com/imgproxy/imgproxy) |
49
+ | [rails_admin](https://github.com/sferik/rails_admin) | Integration for [RailsAdmin](https://github.com/sferik/rails_admin) |
50
+ | [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart) | Integration for [Uppy AWS S3 Multipart](https://uppy.io/docs/aws-s3-multipart/) |
@@ -167,9 +167,13 @@ specific storage service, by implementing a common public interface. Storage
167
167
  instances are registered under an identifier in `Shrine.storages`, so that they
168
168
  can later be used by [uploaders][uploader].
169
169
 
170
- Previously we've shown the [FileSystem] storage which saves files to disk, but
171
- Shrine also ships with [S3] storage which stores files on [AWS S3] (or any
172
- S3-compatible service such as [DigitalOcean Spaces] or [MinIO]).
170
+ Shrine ships with the following storages:
171
+
172
+ * [`Shrine::Storage::FileSystem`][FileSystem] stores files on disk
173
+ * [`Shrine::Storage::S3`][S3] – stores files on [AWS S3] (or [DigitalOcean Spaces], [MinIO], ...)
174
+ * [`Shrine::Storage::Memory`][Memory] – stores file in memory (convenient for [testing][Testing with Shrine])
175
+
176
+ Here is how we might configure Shrine with S3 storage:
173
177
 
174
178
  ```rb
175
179
  # Gemfile
@@ -180,9 +184,9 @@ require "shrine/storage/s3"
180
184
 
181
185
  s3_options = {
182
186
  bucket: "<YOUR BUCKET>", # required
187
+ region: "<YOUR REGION>", # required
183
188
  access_key_id: "<YOUR ACCESS KEY ID>",
184
189
  secret_access_key: "<YOUR SECRET ACCESS KEY>",
185
- region: "<YOUR REGION>",
186
190
  }
187
191
 
188
192
  Shrine.storages = {
@@ -196,9 +200,9 @@ suitable for [direct uploads][presigned upload]. The `:cache` and `:store`
196
200
  names are special only in terms that the [attacher] will automatically pick
197
201
  them up, you can also register more storage objects under different names.
198
202
 
199
- See the [FileSystem] and [S3] storage docs for more details. There are [many
200
- more Shrine storages][storages] provided by external gems, and you can also
201
- [create your own storage][Creating Storages].
203
+ See the [FileSystem]/[S3]/[Memory] storage docs for more details. There are
204
+ [many more Shrine storages][storages] provided by external gems, and you can
205
+ also [create your own storage][Creating Storages].
202
206
 
203
207
  ## Uploader
204
208
 
@@ -256,14 +260,14 @@ Shrine is able to upload any IO-like object that implement methods [`#read`],
256
260
  [`#rewind`], [`#eof?`] and [`#close`] whose behaviour matches the [`IO`] class.
257
261
  This includes but is not limited to the following objects:
258
262
 
259
- * `File`
260
- * `Tempfile`
261
- * `StringIO`
262
- * `ActionDispatch::Http::UploadedFile` (Rails form upload)
263
- * `Shrine::RackFile` ([`rack_file`][rack_file plugin] plugin)
264
- * `Shrine::DataFile` ([`data_uri`][data_uri plugin] plugin)
265
- * `Shrine::UploadedFile`
266
- * `Down::ChunkedIO` ([Down] gem)
263
+ * [`File`](https://ruby-doc.org/core/File.html)
264
+ * [`Tempfile`](https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html)
265
+ * [`StringIO`](https://ruby-doc.org/stdlib/libdoc/stringio/rdoc/StringIO.html)
266
+ * [`ActionDispatch::Http::UploadedFile`](https://api.rubyonrails.org/classes/ActionDispatch/Http/UploadedFile.html)
267
+ * [`Shrine::RackFile`](https://shrinerb.com/docs/plugins/rack_file)
268
+ * [`Shrine::DataFile`](https://shrinerb.com/docs/plugins/data_uri)
269
+ * [`Shrine::UploadedFile`](#uploaded-file)
270
+ * [`Down::ChunkedIO`](https://github.com/janko/down#streaming)
267
271
  * ...
268
272
 
269
273
  ```rb
@@ -424,9 +428,9 @@ See [Using Attacher] guide for more details.
424
428
 
425
429
  ### Temporary storage
426
430
 
427
- Shrine uses temporary storage to support retaining uploaded files across form
428
- redisplays and [direct uploads]. But you can disable this behaviour, and have
429
- files go straight to permanent storage:
431
+ Shrine uses temporary storage to support [file validation][validation] and
432
+ [direct uploads]. If you don't need these features, you can tell Shrine to
433
+ upload files directly to permanent storage:
430
434
 
431
435
  ```rb
432
436
  Shrine.plugin :model, cache: false
@@ -446,7 +450,7 @@ attacher.file.storage_key #=> :store
446
450
 
447
451
  ## Plugin system
448
452
 
449
- By default Shrine comes with a small core which provides only the essential
453
+ By default, Shrine comes with a small core which provides only the essential
450
454
  functionality. All additional features are available via [plugins], which also
451
455
  ship with Shrine. This way you can choose exactly what and how much Shrine does
452
456
  for you, and you load the code only for features that you use.
@@ -504,7 +508,7 @@ gem "marcel", "~> 0.3"
504
508
  Shrine.plugin :determine_mime_type, analyzer: :marcel
505
509
  ```
506
510
  ```rb
507
- photo = Photo.create(image: StringIO.new("<?php ... ?>"))
511
+ photo = Photo.new(image: StringIO.new("<?php ... ?>"))
508
512
  photo.image.mime_type #=> "application/x-php"
509
513
  ```
510
514
 
@@ -517,10 +521,10 @@ the [Extracting Metadata] guide for more details.
517
521
 
518
522
  ## Processing
519
523
 
520
- Shrine allows you to process attached files up front or on-the-fly. For
521
- example, if your app is accepting image uploads, you can generate a predefined
522
- set of of thumbnails when the image is attached to a record, or you can have
523
- thumbnails generated dynamically as they're needed.
524
+ Shrine allows you to process attached files both "eagerly" and "on-the-fly".
525
+ For example, if your app is accepting image uploads, you can generate a
526
+ predefined set of of thumbnails when the image is attached to a record, or you
527
+ can have thumbnails generated dynamically as they're needed.
524
528
 
525
529
  For image processing, it's recommended to use the **[ImageProcessing]** gem,
526
530
  which is a high-level wrapper for processing with
@@ -530,10 +534,12 @@ which is a high-level wrapper for processing with
530
534
  $ brew install imagemagick vips
531
535
  ```
532
536
 
533
- ### Processing up front
537
+ ### Eager processing
534
538
 
535
- You can use the [`derivatives`][derivatives plugin] plugin to generate a set of
536
- pre-defined processed files:
539
+ We can can use the [`derivatives`][derivatives plugin] plugin to generate a
540
+ pre-defined set of processed files (e.g. image thumbnails). We do this by
541
+ registering a derivatives processor block and then explicitly triggering
542
+ creation:
537
543
 
538
544
  ```rb
539
545
  # Gemfile
@@ -559,32 +565,33 @@ end
559
565
  ```
560
566
  ```rb
561
567
  photo = Photo.new(image: file)
562
- photo.image_derivatives! # calls derivatives processor and uploads results
563
- photo.save
568
+
569
+ if photo.valid?
570
+ photo.image_derivatives! if photo.image_changed? # create derivatives
571
+ photo.save
572
+ end
564
573
  ```
565
574
 
566
- If you're allowing the attached file to be updated later on, in your update
567
- route make sure to trigger derivatives creation for new attachments:
575
+ You can then retrieve the URL of a processed derivative:
568
576
 
569
577
  ```rb
570
- photo.image_derivatives! if photo.image_changed?
578
+ photo.image_url(:large) #=> "https://s3.amazonaws.com/path/to/large.jpg"
571
579
  ```
572
580
 
573
- After the processed files are uploaded, their data is saved into the
574
- `<attachment>_data` column. You can then retrieve the derivatives as
575
- [`Shrine::UploadedFile`][uploaded file] objects:
581
+ The derivatives data is stored in the `<attachment>_data` column, and you can
582
+ retrieve them as [`Shrine::UploadedFile`][uploaded file] objects:
576
583
 
577
584
  ```rb
578
- photo.image(:large) #=> #<Shrine::UploadedFile ...>
579
- photo.image(:large).url #=> "/uploads/store/lg043.jpg"
580
- photo.image(:large).size #=> 5825949
581
- photo.image(:large).mime_type #=> "image/jpeg"
585
+ photo.image(:large) #=> #<Shrine::UploadedFile id="path/to/large.jpg" storage=:store metadata={...}>
586
+ photo.image(:large).url #=> "https://s3.amazonaws.com/path/to/large.jpg"
587
+ photo.image(:large).size #=> 5825949
588
+ photo.image(:large).mime_type #=> "image/jpeg"
582
589
  ```
583
590
 
584
- For more details, see the [`derivatives`][derivatives plugin] plugin
585
- documentation and the [File Processing] guide.
591
+ For more details, see the [File Processing] guide and the
592
+ [`derivatives`][derivatives plugin] plugin documentation.
586
593
 
587
- ### Processing on-the-fly
594
+ ### On-the-fly processing
588
595
 
589
596
  On-the-fly processing is provided by the
590
597
  [`derivation_endpoint`][derivation_endpoint plugin] plugin. To set it up, we
@@ -597,24 +604,28 @@ processing we want to perform:
597
604
  gem "image_processing", "~> 1.8"
598
605
  ```
599
606
  ```rb
600
- # config/initializers/shrine.rb (Rails)
607
+ # config/initializers/rails.rb (Rails)
608
+ # ...
609
+ Shrine.plugin :derivation_endpoint, secret_key: "<YOUR_SECRET_KEY>"
610
+ ```
611
+ ```rb
601
612
  require "image_processing/mini_magick"
602
613
 
603
- Shrine.plugin :derivation_endpoint,
604
- secret_key: "<YOUR SECRET KEY>",
605
- prefix: "derivations" # needs to match the mount point in routes
614
+ class ImageUploader < Shrine
615
+ plugin :derivation_endpoint, prefix: "derivations/image" # matches mount point
606
616
 
607
- Shrine.derivation :thumbnail do |file, width, height|
608
- ImageProcessing::MiniMagick
609
- .source(file)
610
- .resize_to_limit!(width.to_i, height.to_i)
617
+ derivation :thumbnail do |file, width, height|
618
+ ImageProcessing::MiniMagick
619
+ .source(file)
620
+ .resize_to_limit!(width.to_i, height.to_i)
621
+ end
611
622
  end
612
623
  ```
613
624
  ```rb
614
625
  # config/routes.rb (Rails)
615
626
  Rails.application.routes.draw do
616
627
  # ...
617
- mount Shrine.derivation_endpoint => "/derivations"
628
+ mount ImageUploader.derivation_endpoint => "/derivations/image"
618
629
  end
619
630
  ```
620
631
 
@@ -623,7 +634,7 @@ processing:
623
634
 
624
635
  ```rb
625
636
  photo.image.derivation_url(:thumbnail, 600, 400)
626
- #=> "/derivations/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
637
+ #=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
627
638
  ```
628
639
 
629
640
  The on-the-fly processing feature is highly customizable, see the
@@ -661,36 +672,85 @@ For more details, see the [File Validation] guide and
661
672
 
662
673
  ## Location
663
674
 
664
- Shrine automatically generated random locations before uploading files. By
665
- default the hierarchy is flat, meaning all files are stored in the root
666
- directory of the storage. The [`pretty_location`][pretty_location plugin]
667
- plugin provides a good default hierarchy, but you can also override
668
- `#generate_location` with a custom implementation:
675
+ Shrine automatically generates random locations before uploading files. By
676
+ default, the hierarchy is flat, meaning all files are stored in the root
677
+ directory of the storage.
678
+
679
+ ```
680
+ 024d9fe83bf4fafb.jpg
681
+ 768a336bf54de219.jpg
682
+ adfaa363629f7fc5.png
683
+ ...
684
+ ```
685
+
686
+ The [`pretty_location`][pretty_location plugin] plugin provides a good default
687
+ hierarchy:
688
+
689
+ ```rb
690
+ Shrine.plugin :pretty_location
691
+ ```
692
+ ```
693
+ user/
694
+ 564/
695
+ avatar/
696
+ aa3e0cd715.jpg
697
+ thumb-493g82jf23.jpg
698
+ photo/
699
+ 123/
700
+ image/
701
+ 13f8a7bc18.png
702
+ thumb-9be62da67e.png
703
+ ...
704
+ ```
705
+
706
+ Buy you can also override `Shrine#generate_location` with a custom
707
+ implementation, for example:
669
708
 
670
709
  ```rb
671
710
  class ImageUploader < Shrine
672
711
  def generate_location(io, record: nil, derivative: nil, **)
673
- type = record.class.name.downcase if record
674
- style = derivative ? "thumbs" : "originals"
675
- name = super # the default unique identifier
712
+ return super unless record
676
713
 
677
- [type, style, name].compact.join("/")
714
+ table = record.class.table_name
715
+ id = record.id
716
+ prefix = derivative || "original"
717
+
718
+ "uploads/#{table}/#{id}/#{prefix}-#{super}"
678
719
  end
679
720
  end
680
721
  ```
681
722
  ```
682
723
  uploads/
683
724
  photos/
684
- originals/
685
- la98lda74j3g.jpg
686
- thumbs/
687
- 95kd8kafg80a.jpg
688
- ka8agiaf9gk4.jpg
725
+ 123/
726
+ original-afe929b8b4.jpg
727
+ small-ad61f25883.jpg
728
+ medium-41b75c42bb.jpg
729
+ large-73e67abe50.jpg
730
+ ...
689
731
  ```
690
732
 
691
- Note that there should always be a random component in the location, so that
692
- the ORM dirty tracking is detected properly. Inside `#generate_location` you
693
- can also access the extracted metadata through the `:metadata` option.
733
+ > There should always be a random component in the location, so that the ORM
734
+ dirty tracking is detected properly.
735
+
736
+ The `Shrine#generate_location` method contains a lot of useful context for the
737
+ upcoming upload:
738
+
739
+ ```rb
740
+ class ImageUploader < Shrine
741
+ def generate_location(io, record: nil, name: nil, derivative: nil, metadata: {}, **options)
742
+ storage_key #=> :cache, :store, ...
743
+ io #=> #<File>, #<Shrine::UploadedFile>, ...
744
+ record #=> #<Photo>, #<User>, ...
745
+ name #=> :image, :avatar, ...
746
+ derivative #=> :small, :medium, :large, ... (derivatives plugin)
747
+ metadata #=> { "filename" => "nature.jpg", "mime_type" => "image/jpeg", "size" => 18573, ... }
748
+ options #=> { ... other uploader options ... }
749
+
750
+ # ...
751
+ end
752
+ end
753
+ ```
694
754
 
695
755
  ## Direct uploads
696
756
 
@@ -725,7 +785,7 @@ Shrine.plugin :upload_endpoint
725
785
  # config/routes.rb (Rails)
726
786
  Rails.application.routes.draw do
727
787
  # ...
728
- mount ImageUploader.upload_endpoint(:cache) => "/images/upload" # POST /images/upload
788
+ mount Shrine.upload_endpoint(:cache) => "/upload" # POST /upload
729
789
  end
730
790
  ```
731
791
 
@@ -852,7 +912,7 @@ end
852
912
  class PromoteJob
853
913
  include Sidekiq::Worker
854
914
 
855
- def perform(attacher_class, record_class, record.id, name, file_data)
915
+ def perform(attacher_class, record_class, record_id, name, file_data)
856
916
  attacher_class = Object.const_get(attacher_class)
857
917
  record = Object.const_get(record_class).find(record_id) # if using Active Record
858
918
 
@@ -896,6 +956,8 @@ s3 = Shrine.storages[:cache]
896
956
  s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 } # delete files older than 1 week
897
957
  ```
898
958
 
959
+ For S3, it may be easier and cheaper to use [S3 bucket lifecycle expiration rules](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html) instead.
960
+
899
961
  ## Logging
900
962
 
901
963
  The [`instrumentation`][instrumentation plugin] plugin sends and logs events for
@@ -946,7 +1008,6 @@ In tests you might want to tell Shrine to log only warnings:
946
1008
  Shrine.logger.level = Logger::WARN
947
1009
  ```
948
1010
 
949
- [Advantages of Shrine]: https://shrinerb.com/docs/advantages
950
1011
  [Creating Plugins]: https://shrinerb.com/docs/creating-plugins
951
1012
  [Creating Storages]: https://shrinerb.com/docs/creating-storages
952
1013
  [Direct Uploads to S3]: https://shrinerb.com/docs/direct-s3
@@ -957,24 +1018,19 @@ Shrine.logger.level = Logger::WARN
957
1018
  [Using Attacher]: https://shrinerb.com/docs/attacher
958
1019
  [FileSystem]: https://shrinerb.com/docs/storage/file-system
959
1020
  [S3]: https://shrinerb.com/docs/storage/s3
1021
+ [Memory]: https://shrinerb.com/docs/storage/memory
1022
+ [Testing with Shrine]: https://shrinerb.com/docs/testing
960
1023
  [`Shrine::UploadedFile`]: https://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
961
1024
 
962
1025
  [attacher]: #attacher
963
1026
  [attachment]: #attaching
964
- [backgrounding]: #backgrounding
965
1027
  [direct uploads]: #direct-uploads
966
1028
  [io abstraction]: #io-abstraction
967
1029
  [location]: #location
968
1030
  [metadata]: #metadata
969
- [up front]: #processing-up-front
970
- [on-the-fly]: #processing-on-the-fly
971
- [plugin system]: #plugin-system
972
- [simple upload]: #simple-direct-upload
973
1031
  [presigned upload]: #presigned-direct-upload
974
- [resumable upload]: #resumable-direct-upload
975
1032
  [storage]: #storage
976
1033
  [uploaded file]: #uploaded-file
977
- [uploading]: #uploading
978
1034
  [uploader]: #uploader
979
1035
  [validation]: #validation
980
1036