shrine 3.0.0.rc → 3.0.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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -66
  3. data/README.md +39 -1061
  4. data/doc/advantages.md +151 -148
  5. data/doc/attacher.md +12 -30
  6. data/doc/carrierwave.md +150 -115
  7. data/doc/changing_derivatives.md +5 -11
  8. data/doc/changing_location.md +8 -5
  9. data/doc/changing_storage.md +5 -2
  10. data/doc/creating_persistence_plugins.md +9 -6
  11. data/doc/creating_plugins.md +42 -22
  12. data/doc/creating_storages.md +4 -1
  13. data/doc/design.md +7 -5
  14. data/doc/direct_s3.md +9 -4
  15. data/doc/external/articles.md +50 -0
  16. data/doc/external/extensions.md +46 -0
  17. data/doc/external/misc.md +17 -0
  18. data/doc/getting_started.md +1038 -0
  19. data/doc/metadata.md +5 -3
  20. data/doc/multiple_files.md +55 -29
  21. data/doc/paperclip.md +206 -163
  22. data/doc/plugins/activerecord.md +26 -6
  23. data/doc/plugins/add_metadata.md +4 -2
  24. data/doc/plugins/atomic_helpers.md +4 -2
  25. data/doc/plugins/backgrounding.md +83 -44
  26. data/doc/plugins/cached_attachment_data.md +4 -2
  27. data/doc/plugins/column.md +4 -2
  28. data/doc/plugins/data_uri.md +10 -6
  29. data/doc/plugins/default_storage.md +5 -3
  30. data/doc/plugins/default_url.md +4 -2
  31. data/doc/plugins/delete_raw.md +4 -2
  32. data/doc/plugins/derivation_endpoint.md +63 -39
  33. data/doc/plugins/derivatives.md +13 -50
  34. data/doc/plugins/determine_mime_type.md +6 -4
  35. data/doc/plugins/download_endpoint.md +6 -3
  36. data/doc/plugins/dynamic_storage.md +4 -2
  37. data/doc/plugins/entity.md +6 -4
  38. data/doc/plugins/form_assign.md +4 -2
  39. data/doc/plugins/included.md +4 -2
  40. data/doc/plugins/infer_extension.md +6 -4
  41. data/doc/plugins/instrumentation.md +5 -3
  42. data/doc/plugins/keep_files.md +9 -2
  43. data/doc/plugins/metadata_attributes.md +5 -3
  44. data/doc/plugins/mirroring.md +4 -2
  45. data/doc/plugins/model.md +6 -4
  46. data/doc/plugins/module_include.md +4 -2
  47. data/doc/plugins/multi_cache.md +4 -2
  48. data/doc/plugins/persistence.md +5 -3
  49. data/doc/plugins/presign_endpoint.md +6 -2
  50. data/doc/plugins/pretty_location.md +5 -3
  51. data/doc/plugins/processing.md +4 -2
  52. data/doc/plugins/rack_file.md +8 -2
  53. data/doc/plugins/rack_response.md +6 -2
  54. data/doc/plugins/recache.md +4 -2
  55. data/doc/plugins/refresh_metadata.md +5 -3
  56. data/doc/plugins/remote_url.md +26 -5
  57. data/doc/plugins/remove_attachment.md +4 -2
  58. data/doc/plugins/remove_invalid.md +10 -2
  59. data/doc/plugins/restore_cached_data.md +9 -3
  60. data/doc/plugins/sequel.md +26 -6
  61. data/doc/plugins/signature.md +6 -4
  62. data/doc/plugins/store_dimensions.md +6 -4
  63. data/doc/plugins/tempfile.md +4 -2
  64. data/doc/plugins/upload_endpoint.md +6 -2
  65. data/doc/plugins/upload_options.md +6 -4
  66. data/doc/plugins/url_options.md +4 -2
  67. data/doc/plugins/validation.md +7 -3
  68. data/doc/plugins/validation_helpers.md +13 -10
  69. data/doc/plugins/versions.md +4 -8
  70. data/doc/processing.md +27 -9
  71. data/doc/refile.md +119 -127
  72. data/doc/release_notes/1.0.0.md +4 -0
  73. data/doc/release_notes/1.1.0.md +4 -0
  74. data/doc/release_notes/1.2.0.md +4 -0
  75. data/doc/release_notes/1.3.0.md +4 -0
  76. data/doc/release_notes/1.4.0.md +4 -0
  77. data/doc/release_notes/1.4.1.md +4 -0
  78. data/doc/release_notes/1.4.2.md +4 -0
  79. data/doc/release_notes/2.0.0.md +4 -0
  80. data/doc/release_notes/2.0.1.md +4 -0
  81. data/doc/release_notes/2.1.0.md +4 -0
  82. data/doc/release_notes/2.1.1.md +4 -0
  83. data/doc/release_notes/2.10.0.md +4 -0
  84. data/doc/release_notes/2.10.1.md +4 -0
  85. data/doc/release_notes/2.11.0.md +4 -0
  86. data/doc/release_notes/2.12.0.md +4 -0
  87. data/doc/release_notes/2.13.0.md +4 -0
  88. data/doc/release_notes/2.14.0.md +5 -1
  89. data/doc/release_notes/2.15.0.md +10 -6
  90. data/doc/release_notes/2.16.0.md +4 -0
  91. data/doc/release_notes/2.17.0.md +4 -0
  92. data/doc/release_notes/2.18.0.md +4 -0
  93. data/doc/release_notes/2.19.0.md +7 -4
  94. data/doc/release_notes/2.2.0.md +4 -0
  95. data/doc/release_notes/2.3.0.md +4 -0
  96. data/doc/release_notes/2.3.1.md +4 -0
  97. data/doc/release_notes/2.4.0.md +4 -0
  98. data/doc/release_notes/2.4.1.md +4 -0
  99. data/doc/release_notes/2.5.0.md +4 -0
  100. data/doc/release_notes/2.6.0.md +4 -0
  101. data/doc/release_notes/2.6.1.md +4 -0
  102. data/doc/release_notes/2.7.0.md +4 -0
  103. data/doc/release_notes/2.8.0.md +4 -0
  104. data/doc/release_notes/2.9.0.md +4 -0
  105. data/doc/release_notes/3.0.0.md +120 -38
  106. data/doc/retrieving_uploads.md +4 -1
  107. data/doc/securing_uploads.md +4 -1
  108. data/doc/storage/file_system.md +12 -4
  109. data/doc/storage/s3.md +4 -2
  110. data/doc/testing.md +27 -41
  111. data/doc/upgrading_to_3.md +105 -26
  112. data/doc/validation.md +8 -6
  113. data/lib/shrine/attacher.rb +2 -2
  114. data/lib/shrine/attachment.rb +7 -10
  115. data/lib/shrine/plugins/activerecord.rb +10 -10
  116. data/lib/shrine/plugins/add_metadata.rb +1 -3
  117. data/lib/shrine/plugins/atomic_helpers.rb +6 -8
  118. data/lib/shrine/plugins/backgrounding.rb +4 -6
  119. data/lib/shrine/plugins/cached_attachment_data.rb +1 -3
  120. data/lib/shrine/plugins/column.rb +2 -4
  121. data/lib/shrine/plugins/data_uri.rb +1 -3
  122. data/lib/shrine/plugins/default_storage.rb +1 -3
  123. data/lib/shrine/plugins/default_url.rb +1 -3
  124. data/lib/shrine/plugins/delete_raw.rb +1 -3
  125. data/lib/shrine/plugins/derivation_endpoint.rb +3 -4
  126. data/lib/shrine/plugins/derivatives.rb +2 -4
  127. data/lib/shrine/plugins/determine_mime_type.rb +1 -3
  128. data/lib/shrine/plugins/download_endpoint.rb +1 -3
  129. data/lib/shrine/plugins/dynamic_storage.rb +1 -3
  130. data/lib/shrine/plugins/entity.rb +25 -9
  131. data/lib/shrine/plugins/form_assign.rb +1 -3
  132. data/lib/shrine/plugins/included.rb +1 -3
  133. data/lib/shrine/plugins/infer_extension.rb +1 -3
  134. data/lib/shrine/plugins/instrumentation.rb +1 -3
  135. data/lib/shrine/plugins/keep_files.rb +1 -3
  136. data/lib/shrine/plugins/metadata_attributes.rb +1 -3
  137. data/lib/shrine/plugins/mirroring.rb +2 -1
  138. data/lib/shrine/plugins/model.rb +2 -4
  139. data/lib/shrine/plugins/module_include.rb +1 -3
  140. data/lib/shrine/plugins/multi_cache.rb +3 -3
  141. data/lib/shrine/plugins/presign_endpoint.rb +1 -3
  142. data/lib/shrine/plugins/pretty_location.rb +1 -3
  143. data/lib/shrine/plugins/processing.rb +1 -3
  144. data/lib/shrine/plugins/rack_file.rb +1 -3
  145. data/lib/shrine/plugins/rack_response.rb +1 -3
  146. data/lib/shrine/plugins/recache.rb +1 -3
  147. data/lib/shrine/plugins/refresh_metadata.rb +1 -3
  148. data/lib/shrine/plugins/remote_url.rb +1 -3
  149. data/lib/shrine/plugins/remove_attachment.rb +1 -3
  150. data/lib/shrine/plugins/remove_invalid.rb +1 -3
  151. data/lib/shrine/plugins/restore_cached_data.rb +1 -3
  152. data/lib/shrine/plugins/sequel.rb +10 -12
  153. data/lib/shrine/plugins/signature.rb +1 -3
  154. data/lib/shrine/plugins/store_dimensions.rb +1 -3
  155. data/lib/shrine/plugins/tempfile.rb +1 -3
  156. data/lib/shrine/plugins/upload_endpoint.rb +1 -3
  157. data/lib/shrine/plugins/upload_options.rb +1 -3
  158. data/lib/shrine/plugins/url_options.rb +1 -3
  159. data/lib/shrine/plugins/validation.rb +1 -3
  160. data/lib/shrine/plugins/validation_helpers.rb +1 -3
  161. data/lib/shrine/plugins/versions.rb +1 -3
  162. data/lib/shrine/storage/file_system.rb +1 -1
  163. data/lib/shrine/storage/linter.rb +1 -1
  164. data/lib/shrine/storage/memory.rb +2 -1
  165. data/lib/shrine/storage/s3.rb +3 -3
  166. data/lib/shrine/version.rb +1 -1
  167. metadata +8 -4
@@ -1,6 +1,13 @@
1
- # Writing a Plugin
1
+ ---
2
+ id: creating-plugins
3
+ title: Writing a Plugin
4
+ ---
5
+
6
+ Shrine has a lot of plugins built-in, but you can use Shrine's plugin system to
7
+ create your own.
8
+
9
+ ## Definition
2
10
 
3
- Shrine has a lot of plugins built-in, but you can also easily create your own.
4
11
  Simply put, a plugin is a module:
5
12
 
6
13
  ```rb
@@ -11,9 +18,9 @@ end
11
18
  Shrine.plugin MyPlugin
12
19
  ```
13
20
 
14
- If you would like to load plugins with a symbol, like you already load plugins
15
- that ship with Shrine, you need to put the plugin in
16
- `shrine/plugins/my_plugin.rb` in the load path, and register it:
21
+ If you would like to load plugins with a symbol (like you already do with
22
+ plugins that ship with Shrine), you need to put the plugin in
23
+ `shrine/plugins/my_plugin.rb` in your load path and register it:
17
24
 
18
25
  ```rb
19
26
  # shrine/plugins/my_plugin.rb
@@ -31,6 +38,8 @@ end
31
38
  Shrine.plugin :my_plugin
32
39
  ```
33
40
 
41
+ ## Methods
42
+
34
43
  The way to make plugins actually extend Shrine's core classes is by defining
35
44
  special modules inside the plugin. Here's a list of all "special" modules:
36
45
 
@@ -51,7 +60,7 @@ uploading:
51
60
  ```rb
52
61
  module MyPlugin
53
62
  module InstanceMethods
54
- def upload(io, context)
63
+ def upload(io, **options)
55
64
  time = Time.now
56
65
  result = super
57
66
  duration = Time.now - time
@@ -61,40 +70,51 @@ module MyPlugin
61
70
  end
62
71
  ```
63
72
 
64
- Notice that we can call `super` to get the original behaviour. In addition to
65
- these modules, you can also make your plugin configurable:
73
+ Notice that we can call `super` to get the original behaviour.
66
74
 
67
- ```rb
68
- Shrine.plugin :my_plugin, foo: "bar"
69
- ```
75
+ ## Configuration
70
76
 
71
- You can do this by adding a `.configure` method to your plugin, which will be
72
- given any passed in arguments or blocks. Typically you'll want to save these
73
- options into Shrine's `opts`, so that you can access them inside of Shrine's
74
- methods.
77
+ You'll likely want to make your plugin configurable. You can do that by
78
+ overriding the `.configure` class method and storing received options into
79
+ `Shrine.opts`:
75
80
 
76
81
  ```rb
77
82
  module MyPlugin
78
- def self.configure(uploader, options = {})
79
- uploader # The uploader class which called `.plugin`
80
- uploader.opts[:my_plugin_options] = options
83
+ def self.configure(uploader, **opts)
84
+ uploader.opts[:my_plugin] ||= {}
85
+ uploader.opts[:my_plugin].merge!(opts)
81
86
  end
82
87
 
83
88
  module InstanceMethods
84
- def foo
85
- opts[:my_plugin_options] #=> {foo: "bar"}
89
+ def upload(io, **options)
90
+ opts[:my_plugin] #=> { ... }
91
+ # ...
86
92
  end
87
93
  end
88
94
  end
89
95
  ```
90
96
 
97
+ Users can now pass these configuration options when loading your plugin:
98
+
99
+ ```rb
100
+ Shrine.plugin :my_plugin, foo: "bar"
101
+ ```
102
+
103
+ ## Dependencies
104
+
91
105
  If your plugin depends on other plugins, you can load them inside of
92
- `.load_dependencies` (which is given the same arguments as `.configure`):
106
+ `.load_dependencies`:
93
107
 
94
108
  ```rb
95
109
  module MyPlugin
96
- def self.load_dependencies(uploader, *)
110
+ def self.load_dependencies(uploader, **opts)
97
111
  uploader.plugin :derivatives # depends on the derivatives plugin
98
112
  end
99
113
  end
100
114
  ```
115
+
116
+ The dependencies will get loaded before your plugin, allowing you to override
117
+ methods of your dependencies in your method modules.
118
+
119
+ The same configuration options passed to `.configure` are passed to
120
+ `.load_dependencies` as well.
@@ -1,4 +1,7 @@
1
- # Writing a Storage
1
+ ---
2
+ id: creating-storages
3
+ title: Writing a Storage
4
+ ---
2
5
 
3
6
  Shrine ships with the FileSystem and S3 storages, but it's also easy to create
4
7
  your own. A storage is a class which needs to implement `#upload`, `#url`,
@@ -1,4 +1,6 @@
1
- # The Design of Shrine
1
+ ---
2
+ title: The Design of Shrine
3
+ ---
2
4
 
3
5
  *If you want an in-depth walkthrough through the Shrine codebase, see [Notes on study of shrine implementation] article by Jonathan Rochkind.*
4
6
 
@@ -187,8 +189,8 @@ Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url
187
189
 
188
190
  # equivalents
189
191
  Shrine::Attachment.new(:image)
192
+ Shrine::Attachment[:image]
190
193
  Shrine::Attachment(:image)
191
- Shrine[:image]
192
194
  ```
193
195
 
194
196
  We can include this module to a model:
@@ -206,7 +208,7 @@ photo.image_url # shorthand for `photo.image_attacher.url`
206
208
  photo.image_attacher #=> #<Shrine::Attacher>
207
209
  ```
208
210
 
209
- When an ORM plugin is loaded, the `Shrine::Attachment` module also
211
+ When a persistence plugin is loaded, the `Shrine::Attachment` module also
210
212
  automatically:
211
213
 
212
214
  * syncs Shrine's validation errors with the record
@@ -214,7 +216,7 @@ automatically:
214
216
  * deletes the uploaded file if attachment was replaced/removed or the record
215
217
  destroyed
216
218
 
217
- [Using Attacher]: /doc/attacher.md#readme
219
+ [Using Attacher]: https://shrinerb.com/docs/attacher
218
220
  [Notes on study of shrine implementation]: https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/
219
- [Creating a New Plugin]: /doc/creating_plugins.md#readme
221
+ [Creating a New Plugin]: https://shrinerb.com/docs/creating-plugins
220
222
  [Plugin system of Sequel and Roda]: https://twin.github.io/the-plugin-system-of-sequel-and-roda/
@@ -1,4 +1,7 @@
1
- # Direct Uploads to S3
1
+ ---
2
+ id: direct-s3
3
+ title: Direct Uploads to S3
4
+ ---
2
5
 
3
6
  Shrine gives you the ability to upload files directly to Amazon S3 (or any
4
7
  other storage service that accepts direct uploads). Uploading directly to a
@@ -23,12 +26,14 @@ storage service is beneficial for several reasons:
23
26
  request-response lifecycle might not be able to finish before the request
24
27
  times out.
25
28
 
29
+ ## Storage
30
+
26
31
  To start, let's set both temporary and permanent storage to S3, with the
27
32
  temporary storage uploading to the `cache/` prefix:
28
33
 
29
34
  ```rb
30
35
  # Gemfile
31
- gem "shrine", "~> 2.11"
36
+ gem "shrine", "~> 3.0"
32
37
  gem "aws-sdk-s3", "~> 1.14"
33
38
  ```
34
39
  ```rb
@@ -375,6 +380,6 @@ setup] guide.
375
380
  [lifecycle Console]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
376
381
  [lifecycle API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_lifecycle_configuration-instance_method
377
382
  [Minio]: https://minio.io
378
- [minio setup]: /doc/testing.md#minio
379
- [metadata direct uploads]: /doc/metadata.md#direct-uploads
383
+ [minio setup]: https://shrinerb.com/docs/testing#minio
384
+ [metadata direct uploads]: https://shrinerb.com/docs/metadata#direct-uploads
380
385
  [content_disposition]: https://github.com/shrinerb/content_disposition
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: Articles
3
+ ---
4
+
5
+ ## Official articles
6
+
7
+ * [Introducing Shrine](http://twin.github.io/introducing-shrine)
8
+ * [Asynchronous File Uploads](http://twin.github.io/file-uploads-asynchronous-world)
9
+ * [Shrine 2.0 Released](https://twin.github.io/shrine-2-0-released/)
10
+ * [Shrine meets Transloadit](https://twin.github.io/shrine-meets-transloadit/)
11
+ * [Resumable File Uploads in Ruby](https://twin.github.io/resumable-file-uploads-in-ruby/)
12
+ * [Adding Direct S3 Uploads to a Roda App with Shrine](https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads)
13
+ * [Adding Resumable Uploads to a Roda App with Shrine](https://github.com/shrinerb/shrine/wiki/Adding-Resumable-Uploads)
14
+ * [Better File Uploads with Shrine: Motivation](https://twin.github.io/better-file-uploads-with-shrine-motivation/)
15
+ * [Better File Uploads with Shrine: Uploader](https://twin.github.io/better-file-uploads-with-shrine-uploader/)
16
+ * [Better File Uploads with Shrine: Attachment](https://twin.github.io/better-file-uploads-with-shrine-attachment/)
17
+ * [Better File Uploads with Shrine: Processing](https://twin.github.io/better-file-uploads-with-shrine-processing/)
18
+ * [Better File Uploads with Shrine: Metadata](https://twin.github.io/better-file-uploads-with-shrine-metadata/)
19
+ * [Better File Uploads with Shrine: Direct Uploads](https://twin.github.io/better-file-uploads-with-shrine-direct-uploads)
20
+
21
+ ## Other Articles
22
+
23
+ * [Uploading files with Shrine in Hanami](http://katafrakt.me/2016/02/04/shrine-hanami-uploads/)
24
+ * [Rails File Uploading You Can Believe in with Shrine](http://www.sitepoint.com/rails-file-uploading-you-can-believe-in-with-shrine/)
25
+ * [Uploading Files With Rails and Shrine](https://code.tutsplus.com/tutorials/uploading-files-with-rails-and-shrine--cms-27596)
26
+ * [Upload images using Shrine.rb and Dropzone.js](https://codyeatworld.com/2017/04/18/rails-uploading-images-confidently-with-shrine-rb/)
27
+ * [Background file uploads to S3 using Shrine](http://elixirator.com/blog/2017/background-file-uploads-to-s3-using-shrine.html)
28
+ * [Rails + Shrine + DropzoneJS](https://stephencodes.com/rails-5-shrine-dropzonejs/)
29
+ * [Uploading Files with Rails and ActionCable](https://scotch.io/tutorials/uploading-files-with-rails-and-actioncable)
30
+ * [Creating Streaming Radio With Rails and Icecast](https://scotch.io/tutorials/creating-online-streaming-radio-with-rails-and-icecast)
31
+ * [Multiple Photo Upload using Shrine](https://github.com/pyksoft/multi-photo-upload#multiple-photo-upload-using-shrine)
32
+ * [Store Your Files on S3: Setup & Configuration](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-1.html)
33
+ * [Store Your Files on S3: Direct Uploads](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-2.html)
34
+ * [Store Your Files on S3: Uploading from a URL](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-3.html)
35
+ * [How to use Trix and Shrine for WYSIWYG Editing with Drag-and-Drop Image Uploading](http://headway.io/blog/how-to-use-trix-and-shrine-for-wysiwyg-editing-with-drag-and-drop-image-uploading/)
36
+ * [Happy users uploading files with Rails 5, Shrine, and Vue.js](https://itnext.io/happy-users-uploading-files-with-rails-5-shrine-and-vue-js-bbcc470a327f)
37
+ * [Notes on study of shrine implementation](https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/)
38
+ * [GraphQL file upload with Shrine](https://blog.stanko.io/graphql-file-upload-with-shrine-45fa26463c68)
39
+
40
+ ## Videos
41
+
42
+ * [wroc_love.rb – Handling file uploads for a modern developer](https://www.youtube.com/watch?v=fP2JGjTZU2s)
43
+ * [File Uploads in Rails with Shrine (GoRails)](https://gorails.com/episodes/file-uploading-with-shrine?autoplay=1)
44
+ * [Direct File Uploads to S3: Part 1 (GoRails)](https://gorails.com/episodes/direct-file-uploads-to-s3-part-1?autoplay=1)
45
+ * [Direct File Uploads to S3: Part 2 (GoRails)](https://gorails.com/episodes/direct-file-uploads-to-s3-part-2?autoplay=1)
46
+ * [Direct File Uploads to S3: Part 3 (GoRails)](https://gorails.com/episodes/direct-file-uploads-to-s3-part-3?autoplay=1)
47
+ * [Backgrounding and Video Transcoding (GoRails)](https://gorails.com/episodes/shrine-background-and-video-transcoding?autoplay=1)
48
+ * [Multiple File Uploads with Shrine (GoRails)](https://gorails.com/episodes/multiple-file-uploads-with-shrine?autoplay=1)
49
+ * [Trix WYSIWYG Editor And File Uploads (GoRails)](https://gorails.com/episodes/trix-editor?autoplay=1)
50
+ * [Uploading Files to DigitalOcean Spaces (GoRails)](https://gorails.com/episodes/digital-ocean-spaces-with-rails?autoplay=1)
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: Extensions
3
+ ---
4
+
5
+ ## Storages
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)
26
+
27
+ ## Plugins
28
+
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-tus](https://github.com/shrinerb/shrine-tus)
40
+
41
+ ## Libraries
42
+
43
+ * [ckeditor](https://github.com/galetahub/ckeditor)
44
+ * [imgproxy](https://github.com/imgproxy/imgproxy.rb)
45
+ * [rails_admin](https://github.com/sferik/rails_admin)
46
+ * [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart)
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Miscellaneous
3
+ ---
4
+
5
+ ## Demos
6
+
7
+ * [Dropzone demo](https://github.com/codyeatworld/example-shrine-dropzone)
8
+ * [Hanami demo](https://github.com/katafrakt/hanami-shrine-example)
9
+ * [Rails demo](https://github.com/erikdahlstrand/shrine-rails-example)
10
+ * [Resumable uploads demo](https://github.com/shrinerb/shrine-tus-demo)
11
+ * [Roda demo (official)](https://github.com/shrinerb/shrine/tree/master/demo)
12
+ * [ROM & dry-rb demo](https://github.com/shrinerb/shrine-rom/tree/master/demo)
13
+ * [Transloadit demo](https://github.com/shrinerb/shrine-transloadit/tree/master/demo)
14
+
15
+ ## Projects
16
+
17
+ * [Cortex CMS](https://docs.cortexcms.org)
@@ -0,0 +1,1038 @@
1
+ ---
2
+ id: getting-started
3
+ title: Getting Started
4
+ ---
5
+
6
+ ## Quick start
7
+
8
+ Add Shrine to the Gemfile and write an initializer which sets up the storage
9
+ and loads integration for your persistence library:
10
+
11
+ ```rb
12
+ # Gemfile
13
+ gem "shrine", "~> 3.0"
14
+ ```
15
+
16
+ ```rb
17
+ require "shrine"
18
+ require "shrine/storage/file_system"
19
+
20
+ Shrine.storages = {
21
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
22
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"), # permanent
23
+ }
24
+
25
+ Shrine.plugin :sequel # or :activerecord
26
+ Shrine.plugin :cached_attachment_data # for retaining the cached file across form redisplays
27
+ Shrine.plugin :restore_cached_data # re-extract metadata when attaching a cached file
28
+ Shrine.plugin :rack_file # for non-Rails apps
29
+ ```
30
+
31
+ Next decide how you will name the attachment attribute on your model, and run a
32
+ migration that adds an `<attachment>_data` text or JSON column, which Shrine
33
+ will use to store all information about the attachment:
34
+
35
+ <!--DOCUSAURUS_CODE_TABS-->
36
+ <!--Sequel-->
37
+ ```rb
38
+ Sequel.migration do
39
+ change do
40
+ add_column :photos, :image_data, :text # or :jsonb
41
+ end
42
+ end
43
+ ```
44
+ <!--ActiveRecord-->
45
+ ```rb
46
+ class AddImageDataToPhotos < ActiveRecord::Migration
47
+ def change
48
+ add_column :photos, :image_data, :text # or :jsonb
49
+ end
50
+ end
51
+ ```
52
+ <!--Rails-->
53
+ ```sh
54
+ $ rails generate migration add_image_data_to_photos image_data:text
55
+ ```
56
+ <!--END_DOCUSAURUS_CODE_TABS-->
57
+
58
+ Now you can create an uploader class for the type of files you want to upload,
59
+ and add a virtual attribute for handling attachments using this uploader to
60
+ your model. If you do not care about adding plugins or additional processing,
61
+ you can use `Shrine::Attachment`.
62
+
63
+ ```rb
64
+ class ImageUploader < Shrine
65
+ # plugins and uploading logic
66
+ end
67
+ ```
68
+ <!--DOCUSAURUS_CODE_TABS-->
69
+ <!--Sequel-->
70
+ ```rb
71
+ class Photo < Sequel::Model
72
+ include ImageUploader::Attachment(:image) # adds an `image` virtual attribute
73
+ end
74
+ ```
75
+ <!--ActiveRecord-->
76
+ ```rb
77
+ class Photo < ActiveRecord::Base
78
+ include ImageUploader::Attachment(:image) # adds an `image` virtual attribute
79
+ end
80
+ ```
81
+ <!--END_DOCUSAURUS_CODE_TABS-->
82
+
83
+ Let's now add the form fields which will use this virtual attribute (NOT the
84
+ `<attachment>_data` column attribute). We need (1) a file field for choosing
85
+ files, and (2) a hidden field for retaining the uploaded file in case of
86
+ validation errors and for potential [direct uploads].
87
+
88
+ <!--DOCUSAURUS_CODE_TABS-->
89
+ <!--Rails form builder-->
90
+ ```rb
91
+ form_for @photo do |f|
92
+ f.hidden_field :image, value: @photo.cached_image_data
93
+ f.file_field :image
94
+ f.submit
95
+ end
96
+ ```
97
+ <!--Simple Form-->
98
+ ```rb
99
+ simple_form_for @photo do |f|
100
+ f.input :image, as: :hidden, input_html: { value: @photo.cached_image_data }
101
+ f.input :image, as: :file
102
+ f.button :submit
103
+ end
104
+ ```
105
+ <!--Forme-->
106
+ ```rb
107
+ form @photo, action: "/photos", enctype: "multipart/form-data" do |f|
108
+ f.input :image, type: :hidden, value: @photo.cached_image_data
109
+ f.input :image, type: :file
110
+ f.button "Create"
111
+ end
112
+ ```
113
+ <!--END_DOCUSAURUS_CODE_TABS-->
114
+
115
+ Note that the file field needs to go *after* the hidden field, so that
116
+ selecting a new file can always override the cached file in the hidden field.
117
+ Also notice the `enctype="multipart/form-data"` HTML attribute, which is
118
+ required for submitting files through the form (the Rails form builder
119
+ will automatically generate this for you).
120
+
121
+ When the form is submitted, in your router/controller you can assign the file
122
+ from request params to the attachment attribute on the model.
123
+
124
+ <!--DOCUSAURUS_CODE_TABS-->
125
+ <!--Rails-->
126
+ ```rb
127
+ class PhotosController < ApplicationController
128
+ def create
129
+ Photo.create(photo_params)
130
+ # ...
131
+ end
132
+
133
+ private
134
+
135
+ def photo_params
136
+ params.require(:photo).permit(:image)
137
+ end
138
+ end
139
+ ```
140
+ <!--Sinatra-->
141
+ ```rb
142
+ post "/photos" do
143
+ Photo.create(params[:photo])
144
+ # ...
145
+ end
146
+ ```
147
+ <!--END_DOCUSAURUS_CODE_TABS-->
148
+
149
+ Once a file is uploaded and attached to the record, you can retrieve a URL to
150
+ the uploaded file with `#<attachment>_url` and display it on the page:
151
+
152
+ <!--DOCUSAURUS_CODE_TABS-->
153
+ <!--Rails-->
154
+ ```erb
155
+ <%= image_tag @photo.image_url %>
156
+ ```
157
+ <!--HTML-->
158
+ ```erb
159
+ <img src="<%= @photo.image_url %>" />
160
+ ```
161
+ <!--END_DOCUSAURUS_CODE_TABS-->
162
+
163
+ ## Storage
164
+
165
+ A "storage" in Shrine is an object that encapsulates communication with a
166
+ specific storage service, by implementing a common public interface. Storage
167
+ instances are registered under an identifier in `Shrine.storages`, so that they
168
+ can later be used by [uploaders][uploader].
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]).
173
+
174
+ ```rb
175
+ # Gemfile
176
+ gem "aws-sdk-s3", "~> 1.14" # for AWS S3 storage
177
+ ```
178
+ ```rb
179
+ require "shrine/storage/s3"
180
+
181
+ s3_options = {
182
+ bucket: "<YOUR BUCKET>", # required
183
+ access_key_id: "<YOUR ACCESS KEY ID>",
184
+ secret_access_key: "<YOUR SECRET ACCESS KEY>",
185
+ region: "<YOUR REGION>",
186
+ }
187
+
188
+ Shrine.storages = {
189
+ cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
190
+ store: Shrine::Storage::S3.new(**s3_options),
191
+ }
192
+ ```
193
+
194
+ The above example sets up S3 for both temporary and permanent storage, which is
195
+ suitable for [direct uploads][presigned upload]. The `:cache` and `:store`
196
+ names are special only in terms that the [attacher] will automatically pick
197
+ them up, you can also register more storage objects under different names.
198
+
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].
202
+
203
+ ## Uploader
204
+
205
+ Uploaders are subclasses of `Shrine`, and they wrap the actual upload to the
206
+ storage. They perform common tasks around upload that aren't related to a
207
+ particular storage.
208
+
209
+ ```rb
210
+ class MyUploader < Shrine
211
+ # image attachment logic
212
+ end
213
+ ```
214
+
215
+ It's common to create an uploader for each type of file that you want to handle
216
+ (`ImageUploader`, `VideoUploader`, `AudioUploader` etc), but really you can
217
+ organize them in any way you like.
218
+
219
+ ### Uploading
220
+
221
+ The main method of the uploader is `Shrine.upload`, which takes an [IO-like
222
+ object][io abstraction] and a storage identifier on the input, and returns a
223
+ representation of the [uploaded file] on the output.
224
+
225
+ ```rb
226
+ MyUploader.upload(file, :store) #=> #<Shrine::UploadedFile>
227
+ ```
228
+
229
+ Internally this instantiates the uploader with the storage and calls
230
+ `Shrine#upload`:
231
+
232
+ ```rb
233
+ uploader = MyUploader.new(:store)
234
+ uploader.upload(file) #=> #<Shrine::UploadedFile>
235
+ ```
236
+
237
+ Some of the tasks performed by `#upload` include:
238
+
239
+ * extracting [metadata]
240
+ * generating [location]
241
+ * uploading (this is where the [storage] is called)
242
+ * closing the uploaded file
243
+
244
+ The second argument is a "context" hash which is forwarded to places like
245
+ metadata extraction and location generation, but it has a few special options:
246
+
247
+ ```rb
248
+ uploader.upload(io, metadata: { "foo" => "bar" }) # add metadata
249
+ uploader.upload(io, location: "path/to/file") # specify custom location
250
+ uploader.upload(io, upload_options: { acl: "public-read" }) # add options to Storage#upload
251
+ ```
252
+
253
+ ### IO abstraction
254
+
255
+ Shrine is able to upload any IO-like object that implement methods [`#read`],
256
+ [`#rewind`], [`#eof?`] and [`#close`] whose behaviour matches the [`IO`] class.
257
+ This includes built-in IO and IO-like objects like File, Tempfile and StringIO.
258
+
259
+ When a file is uploaded to a Rails app, in request params it will be
260
+ represented by an `ActionDispatch::Http::UploadedFile` object, which is also an
261
+ IO-like object accepted by Shrine. In other Rack applications the uploaded file
262
+ will be represented as a Hash, but it can be converted into an IO-like object
263
+ with the [`rack_file`][rack_file plugin] plugin.
264
+
265
+ Here are some examples of various IO-like objects that can be uploaded:
266
+
267
+ ```rb
268
+ uploader.upload File.open("/path/to/file", binmode: true) # upload from disk
269
+ uploader.upload StringIO.new("file content") # upload from memory
270
+ uploader.upload ActionDispatch::Http::UploadedFile.new(...) # upload from Rails controller
271
+ uploader.upload Shrine.rack_file({ tempfile: tempfile }) # upload from Rack controller
272
+ uploader.upload Rack::Test::UploadedFile.new(...) # upload from rack-test
273
+ uploader.upload Down.open("https://example.org/file") # upload from internet
274
+ uploader.upload Shrine::UploadedFile.new(...) # upload from Shrine storage
275
+ ```
276
+
277
+ ## Uploaded file
278
+
279
+ The `Shrine::UploadedFile` object represents the file that was uploaded to a
280
+ storage, and it's what's returned from `Shrine#upload` or when retrieving a
281
+ record [attachment]. It contains the following information:
282
+
283
+ | Key | Description |
284
+ | :------- | :---------- |
285
+ | `id` | location of the file on the storage |
286
+ | `storage` | identifier of the storage the file was uploaded to |
287
+ | `metadata` | file [metadata] that was extracted before upload |
288
+
289
+ ```rb
290
+ uploaded_file = uploader.upload(file)
291
+ uploaded_file.data #=> {"id"=>"949sdjg834.jpg","storage"=>"store","metadata"=>{...}}
292
+
293
+ uploaded_file.id #=> "949sdjg834.jpg"
294
+ uploaded_file.storage #=> #<Shrine::Storage::S3>
295
+ uploaded_file.metadata #=> {...}
296
+ ```
297
+
298
+ It comes with many convenient methods that delegate to the storage:
299
+
300
+ ```rb
301
+ uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
302
+ uploaded_file.open { |io| ... } # opens the uploaded file stream
303
+ uploaded_file.download { |file| ... } # downloads the uploaded file to disk
304
+ uploaded_file.stream(destination) # streams uploaded content into a writable destination
305
+ uploaded_file.exists? #=> true
306
+ uploaded_file.delete # deletes the uploaded file from the storage
307
+ ```
308
+
309
+ It also implements the IO-like interface that conforms to Shrine's [IO
310
+ abstraction][io abstraction], which allows it to be uploaded again to other
311
+ storages.
312
+
313
+ ```rb
314
+ uploaded_file.read # returns content of the uploaded file
315
+ uploaded_file.eof? # returns true if the whole IO was read
316
+ uploaded_file.rewind # rewinds the IO
317
+ uploaded_file.close # closes the IO
318
+ ```
319
+
320
+ For more details, see the [Retrieving Uploads] guide and
321
+ [`Shrine::UploadedFile`] API docs.
322
+
323
+ ## Attaching
324
+
325
+ To attach uploaded files to database records, Shrine offers an attachment
326
+ interface built on top of uploaders and uploaded files. There are integrations
327
+ for various persistence libraries ([ActiveRecord][activerecord plugin],
328
+ [Sequel][sequel plugin], [ROM][rom plugin], [Hanami][hanami plugin],
329
+ [Mongoid][mongoid plugin]), but you can also attach files to plain structs
330
+ ([mutable][model plugin] or [immutable][entity plugin]).
331
+
332
+ ```rb
333
+ Shrine.plugin :sequel # :activerecord
334
+ ```
335
+
336
+ ### Attachment module
337
+
338
+ The easiest way to attach files is with the `Shrine::Attachment` module:
339
+
340
+ ```rb
341
+ class Photo < Sequel::Model # ActiveRecord::Base
342
+ include ImageUploader::Attachment.new(:image) #
343
+ include ImageUploader::Attachment[:image] # use a preferred syntax
344
+ include ImageUploader::Attachment(:image) #
345
+ end
346
+ ```
347
+
348
+ The included module will add attachment methods for the specified attribute:
349
+
350
+ | Method | Description |
351
+ | :----- | :---------- |
352
+ | `#image=` | uploads the file to temporary storage and serializes the result into `image_data` |
353
+ | `#image` | returns [`Shrine::UploadedFile`][uploaded file] instantiated from `image_data` |
354
+ | `#image_url` | calls `url` on the attachment if it's present, otherwise returns nil |
355
+ | `#image_attacher` | returns instance of [`Shrine::Attacher`][attacher] which handles the attaching |
356
+
357
+ The persistence plugin we loaded will add callbacks that ensure cached files
358
+ are automatically promoted to permanent storage on when record is saved, and
359
+ that attachments are deleted when the record is destroyed.
360
+
361
+ ```rb
362
+ # no file is attached
363
+ photo.image #=> nil
364
+
365
+ # the assigned file is cached to temporary storage and written to `image_data` column
366
+ photo.image = File.open("waterfall.jpg")
367
+ photo.image #=> #<Shrine::UploadedFile @data={...}>
368
+ photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg"
369
+ photo.image_data #=> '{"id":"0sdfllasfi842.jpg","storage":"cache","metadata":{...}}'
370
+
371
+ # the cached file is promoted to permanent storage and saved to `image_data` column
372
+ photo.save
373
+ photo.image #=> #<Shrine::UploadedFile @data={...}>
374
+ photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg"
375
+ photo.image_data #=> '{"id":"l02kladf8jlda.jpg","storage":"store","metadata":{...}}'
376
+
377
+ # the attached file is deleted with the record
378
+ photo.destroy
379
+ photo.image.exists? #=> false
380
+ ```
381
+
382
+ If there is already a file attached and a new file is attached, the previous
383
+ attachment will get deleted when the record gets saved.
384
+
385
+ ```rb
386
+ photo.update(image: new_file) # changes the attachment and deletes previous
387
+ photo.update(image: nil) # removes the attachment and deletes previous
388
+ ```
389
+
390
+ ### Attacher
391
+
392
+ The methods and callbacks added by the `Shrine::Attachment` module just
393
+ delegate the behaviour to an underlying `Shrine::Attacher` object.
394
+
395
+ ```rb
396
+ photo.image_attacher #=> #<Shrine::Attacher>
397
+ ```
398
+
399
+ The `Shrine::Attacher` object can be instantiated and used directly:
400
+
401
+ ```rb
402
+ attacher = ImageUploader::Attacher.from_model(photo, :image)
403
+
404
+ attacher.assign(file) # equivalent to `photo.image = file`
405
+ attacher.file # equivalent to `photo.image`
406
+ attacher.url # equivalent to `photo.image_url`
407
+ ```
408
+
409
+ The attacher is what drives attaching files to model instances; you can use it
410
+ as a more explicit alternative to models' attachment interface, or simply when
411
+ you need something that's not available through the attachment methods.
412
+
413
+ You can do things such as change the temporary and permanent storage the
414
+ attacher uses, or upload files directly to permanent storage. See the [Using
415
+ Attacher] guide for more details.
416
+
417
+ ### Temporary storage
418
+
419
+ Shrine uses temporary storage to support retaining uploaded files across form
420
+ redisplays and [direct uploads]. But you can disable this behaviour, and have
421
+ files go straight to permanent storage:
422
+
423
+ ```rb
424
+ Shrine.plugin :model, cache: false
425
+ ```
426
+ <!--DOCUSAURUS_CODE_TABS-->
427
+ <!--Attachment-->
428
+ ```rb
429
+ photo.image = File.open("waterfall.jpg")
430
+ photo.image.storage_key #=> :store
431
+ ```
432
+ <!--Attacher-->
433
+ ```rb
434
+ attacher.attach File.open("waterfall.jpg")
435
+ attacher.file.storage_key #=> :store
436
+ ```
437
+ <!--END_DOCUSAURUS_CODE_TABS-->
438
+
439
+ ## Plugin system
440
+
441
+ By default Shrine comes with a small core which provides only the essential
442
+ functionality. All additional features are available via [plugins], which also
443
+ ship with Shrine. This way you can choose exactly what and how much Shrine does
444
+ for you, and you load the code only for features that you use.
445
+
446
+ ```rb
447
+ Shrine.plugin :instrumentation # adds instrumentation
448
+ ```
449
+
450
+ Plugins add behaviour by extending Shrine core classes via module inclusion, and
451
+ many of them also accept configuration options. The plugin system respects
452
+ inheritance, so you can choose to load a plugin globally or per uploader.
453
+
454
+ ```rb
455
+ class ImageUploader < Shrine
456
+ plugin :store_dimensions # extract image dimensions only for this uploader and its descendants
457
+ end
458
+ ```
459
+
460
+ If you want to extend Shrine functionality with custom behaviour, you can also
461
+ [create your own plugin][Creating Plugins]. There are also additional [external
462
+ plugins] created by others.
463
+
464
+ ## Metadata
465
+
466
+ Shrine automatically extracts some basic file metadata and saves them to the
467
+ `Shrine::UploadedFile`. You can access them through the `#metadata` hash or via
468
+ metadata methods:
469
+
470
+ ```rb
471
+ uploaded_file.metadata #=>
472
+ # {
473
+ # "filename" => "matrix.mp4",
474
+ # "mime_type" => "video/mp4",
475
+ # "size" => 345993,
476
+ # }
477
+
478
+ uploaded_file.original_filename #=> "matrix.mp4"
479
+ uploaded_file.extension #=> "mp4"
480
+ uploaded_file.mime_type #=> "video/mp4"
481
+ uploaded_file.size #=> 345993
482
+ ```
483
+
484
+ ### MIME type
485
+
486
+ By default, `mime_type` metadata will be set from the `#content_type` attribute
487
+ of the uploaded file (if it exists), which is generally not secure and will
488
+ trigger a warning. You can load the [`determine_mime_type`][determine_mime_type
489
+ plugin] plugin to have MIME type extracted from file *content* instead.
490
+
491
+ ```rb
492
+ # Gemfile
493
+ gem "marcel", "~> 0.3"
494
+ ```
495
+ ```rb
496
+ Shrine.plugin :determine_mime_type, analyzer: :marcel
497
+ ```
498
+ ```rb
499
+ photo = Photo.create(image: StringIO.new("<?php ... ?>"))
500
+ photo.image.mime_type #=> "application/x-php"
501
+ ```
502
+
503
+ ### Other metadata
504
+
505
+ In addition to basic metadata, you can also extract [image
506
+ dimensions][store_dimensions plugin], calculate [signatures][signature plugin],
507
+ and in general extract any [custom metadata][add_metadata plugin]. Check out
508
+ the [Extracting Metadata] guide for more details.
509
+
510
+ ## Processing
511
+
512
+ Shrine allows you to process attached files up front or on-the-fly. For
513
+ example, if your app is accepting image uploads, you can generate a predefined
514
+ set of of thumbnails when the image is attached to a record, or you can have
515
+ thumbnails generated dynamically as they're needed.
516
+
517
+ For image processing, it's recommended to use the **[ImageProcessing]** gem,
518
+ which is a high-level wrapper for processing with
519
+ [MiniMagick][ImageProcessing::MiniMagick] and [libvips][ImageProcessing::Vips].
520
+
521
+ ```sh
522
+ $ brew install imagemagick vips
523
+ ```
524
+
525
+ ### Processing up front
526
+
527
+ You can use the [`derivatives`][derivatives plugin] plugin to generate a set of
528
+ pre-defined processed files:
529
+
530
+ ```rb
531
+ # Gemfile
532
+ gem "image_processing", "~> 1.8"
533
+ ```
534
+ ```rb
535
+ Shrine.plugin :derivatives
536
+ ```
537
+ ```rb
538
+ require "image_processing/mini_magick"
539
+
540
+ class ImageUploader < Shrine
541
+ Attacher.derivatives_processor do |original|
542
+ magick = ImageProcessing::MiniMagick.source(original)
543
+
544
+ {
545
+ large: magick.resize_to_limit!(800, 800),
546
+ medium: magick.resize_to_limit!(500, 500),
547
+ small: magick.resize_to_limit!(300, 300),
548
+ }
549
+ end
550
+ end
551
+ ```
552
+ ```rb
553
+ photo = Photo.new(image: file)
554
+ photo.image_derivatives! # calls derivatives processor and uploads results
555
+ photo.save
556
+ ```
557
+
558
+ If you're allowing the attached file to be updated later on, in your update
559
+ route make sure to trigger derivatives creation for new attachments:
560
+
561
+ ```rb
562
+ photo.image_derivatives! if photo.image_changed?
563
+ ```
564
+
565
+ After the processed files are uploaded, their data is saved into the
566
+ `<attachment>_data` column. You can then retrieve the derivatives as
567
+ [`Shrine::UploadedFile`][uploaded file] objects:
568
+
569
+ ```rb
570
+ photo.image(:large) #=> #<Shrine::UploadedFile ...>
571
+ photo.image(:large).url #=> "/uploads/store/lg043.jpg"
572
+ photo.image(:large).size #=> 5825949
573
+ photo.image(:large).mime_type #=> "image/jpeg"
574
+ ```
575
+
576
+ For more details, see the [`derivatives`][derivatives plugin] plugin
577
+ documentation and the [File Processing] guide.
578
+
579
+ ### Processing on-the-fly
580
+
581
+ On-the-fly processing is provided by the
582
+ [`derivation_endpoint`][derivation_endpoint plugin] plugin. It comes with a
583
+ [mountable][Mounting Endpoints] Rack app which applies processing on request
584
+ and returns processed files.
585
+
586
+ To set it up, we mount the Rack app in our router on a chosen path prefix,
587
+ configure the plugin with a secret key and that path prefix, and define
588
+ processing we want to perform:
589
+
590
+ ```rb
591
+ # Gemfile
592
+ gem "image_processing", "~> 1.8"
593
+ ```
594
+ ```rb
595
+ # config/routes.rb (Rails)
596
+ Rails.application.routes.draw do
597
+ # ...
598
+ mount ImageUploader.derivation_endpoint => "/derivations/image"
599
+ end
600
+ ```
601
+ ```rb
602
+ require "image_processing/mini_magick"
603
+
604
+ class ImageUploader < Shrine
605
+ plugin :derivation_endpoint,
606
+ secret_key: "<YOUR SECRET KEY>",
607
+ prefix: "derivations/image" # needs to match the mount point in routes
608
+
609
+ derivation :thumbnail do |file, width, height|
610
+ ImageProcessing::MiniMagick
611
+ .source(file)
612
+ .resize_to_limit!(width.to_i, height.to_i)
613
+ end
614
+ end
615
+ ```
616
+
617
+ Now we can generate URLs from attached files that will perform the desired
618
+ processing:
619
+
620
+ ```rb
621
+ photo.image.derivation_url(:thumbnail, 600, 400)
622
+ #=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
623
+ ```
624
+
625
+ The on-the-fly processing feature is highly customizable, see the
626
+ [`derivation_endpoint`][derivation_endpoint plugin] plugin documentation for
627
+ more details.
628
+
629
+ ## Validation
630
+
631
+ The [`validation`][validation plugin] plugin allows performing validation for
632
+ attached files. For common validations, the
633
+ [`validation_helpers`][validation_helpers plugin] plugin provides useful
634
+ validators for built in metadata:
635
+
636
+ ```rb
637
+ Shrine.plugin :validation_helpers
638
+ ```
639
+ ```rb
640
+ class DocumentUploader < Shrine
641
+ Attacher.validate do
642
+ validate_max_size 5*1024*1024, message: "is too large (max is 5 MB)"
643
+ validate_mime_type %w[application/pdf]
644
+ end
645
+ end
646
+ ```
647
+
648
+ ```rb
649
+ user = User.new
650
+ user.cv = File.open("cv.pdf", "rb")
651
+ user.valid? #=> false
652
+ user.errors.to_hash #=> {:cv=>["is too large (max is 5 MB)"]}
653
+ ```
654
+
655
+ For more details, see the [File Validation] guide and
656
+ [`validation_helpers`][validation_helpers plugin] plugin docs.
657
+
658
+ ## Location
659
+
660
+ Shrine automatically generated random locations before uploading files. By
661
+ default the hierarchy is flat, meaning all files are stored in the root
662
+ directory of the storage. The [`pretty_location`][pretty_location plugin]
663
+ plugin provides a good default hierarchy, but you can also override
664
+ `#generate_location` with a custom implementation:
665
+
666
+ ```rb
667
+ class ImageUploader < Shrine
668
+ def generate_location(io, record: nil, derivative: nil, **)
669
+ type = record.class.name.downcase if record
670
+ style = derivative ? "thumbs" : "originals"
671
+ name = super # the default unique identifier
672
+
673
+ [type, style, name].compact.join("/")
674
+ end
675
+ end
676
+ ```
677
+ ```plaintext
678
+ uploads/
679
+ photos/
680
+ originals/
681
+ la98lda74j3g.jpg
682
+ thumbs/
683
+ 95kd8kafg80a.jpg
684
+ ka8agiaf9gk4.jpg
685
+ ```
686
+
687
+ Note that there should always be a random component in the location, so that
688
+ the ORM dirty tracking is detected properly. Inside `#generate_location` you
689
+ can also access the extracted metadata through the `:metadata` option.
690
+
691
+ ## Direct uploads
692
+
693
+ To improve the user experience, it's recommended to upload files asynchronously
694
+ as soon as the user selects them. The direct uploads would go to temporary
695
+ storage, just like in the synchronous flow. Then, instead of attaching a raw
696
+ file to your model, you assign the cached file JSON data.
697
+
698
+ ```rb
699
+ # in the regular synchronous flow
700
+ photo.image = file
701
+
702
+ # in the direct upload flow
703
+ photo.image = '{"id":"...","storage":"cache","metadata":{...}}'
704
+ ```
705
+
706
+ On the client side it's highly recommended to use **[Uppy]**, a very flexible
707
+ modern JavaScript file upload library that happens to integrate nicely with
708
+ Shrine.
709
+
710
+ ### Simple direct upload
711
+
712
+ The simplest approach is to upload directly to an endpoint in your app, which
713
+ forwards uploads to the specified storage. The
714
+ [`upload_endpoint`][upload_endpoint plugin] Shrine plugin provides a
715
+ [mountable][Mounting Endpoints] Rack app that implements this endpoint:
716
+
717
+ ```rb
718
+ Shrine.plugin :upload_endpoint
719
+ ```
720
+ ```rb
721
+ # config/routes.rb (Rails)
722
+ Rails.application.routes.draw do
723
+ # ...
724
+ mount ImageUploader.upload_endpoint(:cache) => "/images/upload" # POST /images/upload
725
+ end
726
+ ```
727
+
728
+ Then you can configure Uppy's [XHR Upload][uppy xhr-upload] plugin to upload to
729
+ this endpoint. See [this walkthrough][Adding Direct App Uploads] for adding
730
+ simple direct uploads from scratch, it includes a complete JavaScript example
731
+ (there is also the [Roda][roda demo] / [Rails][rails demo] demo app).
732
+
733
+ ### Presigned direct upload
734
+
735
+ For better performance, you can also upload files directly to your cloud
736
+ storage service (AWS S3, Google Cloud Storage etc). For this, your temporary
737
+ storage needs to be your cloud service:
738
+
739
+ ```rb
740
+ require "shrine/storage/s3"
741
+
742
+ Shrine.storages = {
743
+ cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
744
+ store: Shrine::Storage::S3.new(**s3_options)
745
+ }
746
+ ```
747
+
748
+ In this flow, the client needs to first fetch upload parameters from the
749
+ server, and then use these parameters for the upload to the cloud service.
750
+ The [`presign_endpoint`][presign_endpoint plugin] Shrine plugin provides a
751
+ [mountable][Mounting Endpoints] Rack app that generates upload parameters:
752
+
753
+ ```rb
754
+ Shrine.plugin :presign_endpoint
755
+ ```
756
+ ```rb
757
+ # config/routes.rb (Rails)
758
+ Rails.application.routes.draw do
759
+ # ...
760
+ mount Shrine.presign_endpoint(:cache) => "/s3/params" # GET /s3/params
761
+ end
762
+ ```
763
+
764
+ Then you can configure Uppy's [AWS S3][uppy aws-s3] plugin to fetch params from
765
+ your endpoint before uploading to S3. See [this walkthrough][Adding Direct S3
766
+ Uploads] for adding direct uploads to S3 from scratch, it includes a complete
767
+ JavaScript example (there is also the [Roda][roda demo] / [Rails][rails demo]
768
+ demo). See also the [Direct Uploads to S3] guide for more details.
769
+
770
+ ### Resumable direct upload
771
+
772
+ If your app is accepting large uploads, you can improve resilience by making
773
+ the uploads **resumable**. This can significantly improve experience for users
774
+ on slow and flaky internet connections.
775
+
776
+ #### Uppy S3 Multipart
777
+
778
+ You can achieve resumable uploads directly to S3 with the [AWS S3
779
+ Multipart][uppy aws-s3-multipart] Uppy plugin, accompanied with
780
+ `uppy_s3_multipart` Shrine plugin provided by the [uppy-s3_multipart] gem.
781
+
782
+ ```rb
783
+ # Gemfile
784
+ gem "uppy-s3_multipart", "~> 0.3"
785
+ ```
786
+ ```rb
787
+ Shrine.plugin :uppy_s3_multipart
788
+ ```
789
+ ```rb
790
+ # config/routes.rb (Rails)
791
+ Rails.application.routes.draw do
792
+ # ...
793
+ mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart"
794
+ end
795
+ ```
796
+
797
+ See the [uppy-s3_multipart] docs for more details.
798
+
799
+ #### Tus protocol
800
+
801
+ If you want a more generic approach, you can build your resumable uploads on
802
+ **[tus]** – an open resumable upload protocol. On the server side you can use
803
+ the [tus-ruby-server] gem, on the client side Uppy's [Tus][uppy tus] plugin,
804
+ and the [shrine-tus] gem for the glue.
805
+
806
+ ```rb
807
+ # Gemfile
808
+ gem "tus-server", "~> 2.0"
809
+ gem "shrine-tus", "~> 2.1"
810
+ ```
811
+ ```rb
812
+ require "shrine/storage/tus"
813
+
814
+ Shrine.storages = {
815
+ cache: Shrine::Storage::Tus.new, # tus server acts as temporary storage
816
+ store: ..., # your permanent storage
817
+ }
818
+ ```
819
+ ```rb
820
+ # config/routes.rb (Rails)
821
+ Rails.application.routes.draw do
822
+ # ...
823
+ mount Tus::Server => "/files"
824
+ end
825
+ ```
826
+
827
+ See [this walkthrough][Adding Resumable Uploads] for adding tus-powered
828
+ resumable uploads from scratch, it includes a complete JavaScript example
829
+ (there is also a [demo app][resumable demo]). See also [shrine-tus] and
830
+ [tus-ruby-server] docs for more details.
831
+
832
+ ## Backgrounding
833
+
834
+ The [`backgrounding`][backgrounding plugin] allows you to move file promotion
835
+ and deletion into a background job, using the backgrounding library [of your
836
+ choice][Backgrounding Libraries]:
837
+
838
+ ```rb
839
+ Shrine.plugin :backgrounding
840
+ Shrine::Attacher.promote_block do
841
+ PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
842
+ end
843
+ Shrine::Attacher.destroy_block do
844
+ DestroyJob.perform_async(self.class.name, data)
845
+ end
846
+ ```
847
+ ```rb
848
+ class PromoteJob
849
+ include Sidekiq::Worker
850
+
851
+ def perform(attacher_class, record_class, record.id, name, file_data)
852
+ attacher_class = Object.const_get(attacher_class)
853
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
854
+
855
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
856
+ attacher.atomic_promote
857
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
858
+ # attachment has changed or the record has been deleted, nothing to do
859
+ end
860
+ end
861
+ ```
862
+ ```rb
863
+ class DestroyJob
864
+ include Sidekiq::Worker
865
+
866
+ def perform(attacher_class, data)
867
+ attacher_class = Object.const_get(attacher_class)
868
+
869
+ attacher = attacher_class.from_data(data)
870
+ attacher.destroy
871
+ end
872
+ end
873
+ ```
874
+
875
+ ## Clearing cache
876
+
877
+ Shrine doesn't automatically delete files uploaded to temporary storage, instead
878
+ you should set up a separate recurring task that will automatically delete old
879
+ cached files.
880
+
881
+ Most Shrine storage classes come with a `#clear!` method, which you can call in
882
+ a recurring script. For FileSystem and S3 storage it would look like this:
883
+
884
+ ```rb
885
+ # FileSystem storage
886
+ file_system = Shrine.storages[:cache]
887
+ file_system.clear! { |path| path.mtime < Time.now - 7*24*60*60 } # delete files older than 1 week
888
+ ```
889
+ ```rb
890
+ # S3 storage
891
+ s3 = Shrine.storages[:cache]
892
+ s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 } # delete files older than 1 week
893
+ ```
894
+
895
+ ## Logging
896
+
897
+ The [`instrumentation`][instrumentation plugin] plugin sends and logs events for
898
+ important operations:
899
+
900
+ ```rb
901
+ Shrine.plugin :instrumentation, notifications: ActiveSupport::Notifications
902
+
903
+ uploaded_file = Shrine.upload(io, :store)
904
+ uploaded_file.exists?
905
+ uploaded_file.download
906
+ uploaded_file.delete
907
+ ```
908
+ ```plaintext
909
+ Metadata (32ms) – {:storage=>:store, :io=>StringIO, :uploader=>Shrine}
910
+ Upload (1523ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :io=>StringIO, :upload_options=>{}, :uploader=>Shrine}
911
+ Exists (755ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
912
+ Download (1002ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :download_options=>{}, :uploader=>Shrine}
913
+ Delete (700ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
914
+ ```
915
+
916
+ Some plugins add their own instrumentation as well when they detect that the
917
+ `instrumentation` plugin has been loaded. For that to work, the
918
+ `instrumentation` plugin needs to be loaded *before* any of these plugins.
919
+
920
+ | Plugin | Instrumentation |
921
+ | :----- | :-------------- |
922
+ | `derivation_endpoint` | instruments file processing |
923
+ | `derivatives` | instruments file processing |
924
+ | `determine_mime_type` | instruments analyzing MIME type |
925
+ | `store_dimensions` | instruments extracting image dimensions |
926
+ | `signature` | instruments calculating signature |
927
+ | `infer_extension` | instruments inferring extension |
928
+ | `remote_url` | instruments remote URL downloading |
929
+ | `data_uri` | instruments data URI parsing |
930
+
931
+ For instrumentation, warnings, and other logging, Shrine uses its internal
932
+ logger. You can tell Shrine to use a different logger. For example, if you're
933
+ using Rails, you might want to tell it to use the Rails logger:
934
+
935
+ ```rb
936
+ Shrine.logger = Rails.logger
937
+ ```
938
+
939
+ In tests you might want to tell Shrine to log only warnings:
940
+
941
+ ```rb
942
+ Shrine.logger.level = Logger::WARN
943
+ ```
944
+
945
+ [Advantages of Shrine]: https://shrinerb.com/docs/advantages
946
+ [Creating Plugins]: https://shrinerb.com/docs/creating-plugins
947
+ [Creating Storages]: https://shrinerb.com/docs/creating-storages
948
+ [Direct Uploads to S3]: https://shrinerb.com/docs/direct-s3
949
+ [Extracting Metadata]: https://shrinerb.com/docs/metadata
950
+ [File Processing]: https://shrinerb.com/docs/processing
951
+ [File Validation]: https://shrinerb.com/docs/validation
952
+ [Retrieving Uploads]: https://shrinerb.com/docs/retrieving-uploads
953
+ [Using Attacher]: https://shrinerb.com/docs/attacher
954
+ [FileSystem]: https://shrinerb.com/docs/storage/file-system
955
+ [S3]: https://shrinerb.com/docs/storage/s3
956
+ [`Shrine::UploadedFile`]: https://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
957
+
958
+ [attacher]: #attacher
959
+ [attachment]: #attachment
960
+ [backgrounding]: #backgrounding
961
+ [direct uploads]: #direct-uploads
962
+ [io abstraction]: #io-abstraction
963
+ [location]: #location
964
+ [metadata]: #metadata
965
+ [up front]: #processing-up-front
966
+ [on-the-fly]: #processing-on-the-fly
967
+ [plugin system]: #plugin-system
968
+ [simple upload]: #simple-direct-upload
969
+ [presigned upload]: #presigned-direct-upload
970
+ [resumable upload]: #resumable-direct-upload
971
+ [storage]: #storage
972
+ [uploaded file]: #uploaded-file
973
+ [uploading]: #uploading
974
+ [uploader]: #uploader
975
+ [validation]: #validation
976
+
977
+ [Adding Direct App Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
978
+ [Adding Resumable Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Resumable-Uploads
979
+ [Adding Direct S3 Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
980
+ [Backgrounding Libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries
981
+ [Mounting Endpoints]: https://github.com/shrinerb/shrine/wiki/Mounting-Endpoints
982
+
983
+ [AWS S3]: https://aws.amazon.com/s3/
984
+ [MinIO]: https://min.io/
985
+ [DigitalOcean Spaces]: https://www.digitalocean.com/products/spaces/
986
+ [Cloudinary]: https://github.com/shrinerb/shrine-cloudinary
987
+ [GCS]: https://github.com/renchap/shrine-google_cloud_storage
988
+ [uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
989
+ [tus-ruby-server]: https://github.com/janko/tus-ruby-server
990
+
991
+ [Uppy]: https://uppy.io
992
+ [shrine-tus]: https://github.com/shrinerb/shrine-tus
993
+ [tus]: https://tus.io
994
+ [uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3-multipart/
995
+ [uppy aws-s3]: https://uppy.io/docs/aws-s3/
996
+ [uppy tus]: https://uppy.io/docs/tus/
997
+ [uppy xhr-upload]: https://uppy.io/docs/xhr-upload/
998
+
999
+ [ImageProcessing]: https://github.com/janko/image_processing
1000
+ [ImageProcessing::MiniMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
1001
+ [ImageProcessing::Vips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
1002
+ [`file`]: http://linux.die.net/man/1/file
1003
+
1004
+ [activerecord plugin]: https://shrinerb.com/docs/plugins/activerecord
1005
+ [add_metadata plugin]: https://shrinerb.com/docs/plugins/add_metadata
1006
+ [backgrounding plugin]: https://shrinerb.com/docs/plugins/backgrounding
1007
+ [derivation_endpoint plugin]: https://shrinerb.com/docs/plugins/derivation_endpoint
1008
+ [derivatives plugin]: https://shrinerb.com/docs/plugins/derivatives
1009
+ [determine_mime_type plugin]: https://shrinerb.com/docs/plugins/determine_mime_type
1010
+ [instrumentation plugin]: https://shrinerb.com/docs/plugins/instrumentation
1011
+ [hanami plugin]: https://github.com/katafrakt/hanami-shrine
1012
+ [model plugin]: https://shrinerb.com/docs/plugins/model
1013
+ [entity plugin]: https://shrinerb.com/docs/plugins/entity
1014
+ [mongoid plugin]: https://github.com/shrinerb/shrine-mongoid
1015
+ [presign_endpoint plugin]: https://shrinerb.com/docs/plugins/presign_endpoint
1016
+ [pretty_location plugin]: https://shrinerb.com/docs/plugins/pretty_location
1017
+ [rack_file plugin]: https://shrinerb.com/docs/plugins/rack_file
1018
+ [rom plugin]: https://github.com/shrinerb/shrine-rom
1019
+ [sequel plugin]: https://shrinerb.com/docs/plugins/sequel
1020
+ [signature plugin]: https://shrinerb.com/docs/plugins/signature
1021
+ [store_dimensions plugin]: https://shrinerb.com/docs/plugins/store_dimensions
1022
+ [upload_endpoint plugin]: https://shrinerb.com/docs/plugins/upload_endpoint
1023
+ [validation_helpers plugin]: https://shrinerb.com/docs/plugins/validation_helpers
1024
+ [validation plugin]: https://shrinerb.com/docs/plugins/validation
1025
+
1026
+ [rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
1027
+ [roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
1028
+ [resumable demo]: https://github.com/shrinerb/shrine-tus-demo
1029
+
1030
+ [`#read`]: https://ruby-doc.org/core/IO.html#method-i-read
1031
+ [`#eof?`]: https://ruby-doc.org/core/IO.html#method-i-eof
1032
+ [`#rewind`]: https://ruby-doc.org/core/IO.html#method-i-rewind
1033
+ [`#close`]: https://ruby-doc.org/core/IO.html#method-i-close
1034
+ [`IO`]: https://ruby-doc.org/core/IO.html
1035
+
1036
+ [storages]: https://shrinerb.com/docs/external/extensions#storages
1037
+ [plugins]: https://shrinerb.com/plugins
1038
+ [external plugins]: https://shrinerb.com/docs/external/extensions#plugins