shrine 2.19.3 → 3.6.0

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