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
data/doc/metadata.md CHANGED
@@ -1,4 +1,6 @@
1
- # Extracting Metadata
1
+ ---
2
+ title: Extracting Metadata
3
+ ---
2
4
 
3
5
  Before a file is uploaded, Shrine automatically extracts metadata from it, and
4
6
  stores them in the `Shrine::UploadedFile` object.
@@ -13,6 +15,18 @@ uploaded_file.metadata #=>
13
15
  # }
14
16
  ```
15
17
 
18
+ Under the hood, `Shrine#upload` calls `Shrine#extract_metadata`, which you can
19
+ also use directly to extract metadata from any IO object:
20
+
21
+ ```rb
22
+ uploader.extract_metadata(io) #=>
23
+ # {
24
+ # "size" => 345993,
25
+ # "filename" => "matrix.mp4",
26
+ # "mime_type" => "video/mp4",
27
+ # }
28
+ ```
29
+
16
30
  The following metadata is extracted by default:
17
31
 
18
32
  | Key | Default source |
@@ -21,7 +35,9 @@ The following metadata is extracted by default:
21
35
  | `mime_type` | extracted from `io.content_type` |
22
36
  | `size` | extracted from `io.size` |
23
37
 
24
- You can access extracted metadata in three ways:
38
+ ## Accessing metadata
39
+
40
+ You can access the stored metadata in three ways:
25
41
 
26
42
  ```rb
27
43
  # via methods (if they're defined)
@@ -40,17 +56,7 @@ uploaded_file["filename"]
40
56
  uploaded_file["mime_type"]
41
57
  ```
42
58
 
43
- Under the hood, `Shrine#upload` calls `Shrine#extract_metadata`, which you can
44
- also use directly to extract metadata from any IO object:
45
-
46
- ```rb
47
- uploader.extract_metadata(io) #=>
48
- # {
49
- # "size" => 345993,
50
- # "filename" => "matrix.mp4",
51
- # "mime_type" => "video/mp4",
52
- # }
53
- ```
59
+ ## Controlling extraction
54
60
 
55
61
  `Shrine#upload` accepts a `:metadata` option which accepts the following values:
56
62
 
@@ -83,11 +89,11 @@ By default, the `mime_type` metadata will be copied over from the
83
89
  `#content_type` attribute of the input file (if present). However, since
84
90
  `#content_type` value comes from the `Content-Type` header of the upload
85
91
  request, it's *not guaranteed* to hold the actual MIME type of the file (browser
86
- determines this header based on file extension). Moreover, only
87
- `ActionDispatch::Http::UploadedFile`, `Shrine::Plugins::RackFile::UploadedFile`,
88
- and `Shrine::Plugins::DataUri::DataFile` objects have `#content_type` defined,
89
- so, when uploading simple file objects, `mime_type` will be nil. That makes
90
- relying on `#content_type` both a security risk and limiting.
92
+ determines this header based on file extension).
93
+
94
+ Moreover, only `ActionDispatch::Http::UploadedFile`, `Shrine::RackFile`, and
95
+ `Shrine::DataFile` objects have `#content_type` defined, so when uploading
96
+ objects such as `File`, the `mime_type` value will be nil by default.
91
97
 
92
98
  To remedy that, Shrine comes with a
93
99
  [`determine_mime_type`][determine_mime_type] plugin which is able to extract
@@ -110,14 +116,17 @@ You can choose different analyzers, and even mix-and-match them. See the
110
116
 
111
117
  ## Image Dimensions
112
118
 
113
- Shrine comes with a `store_dimensions` plugin for extracting image dimensions.
114
- It adds `width` and `height` metadata values, and also adds `#width`,
115
- `#height`, and `#dimensions` methods to the `Shrine::UploadedFile` object. By
116
- default, the plugin uses [FastImage] to analyze dimensions, but you can also
117
- have it use [MiniMagick] or [ruby-vips]:
119
+ Shrine comes with a [`store_dimensions`][store_dimensions] plugin for
120
+ extracting image dimensions. It adds `width` and `height` metadata values, and
121
+ also adds `#width`, `#height`, and `#dimensions` methods to the
122
+ `Shrine::UploadedFile` object.
118
123
 
119
124
  ```rb
120
- Shrine.plugin :store_dimensions, analyzer: :mini_magick
125
+ # Gemfile
126
+ gem "fastimage" # default analyzer
127
+ ```
128
+ ```rb
129
+ Shrine.plugin :store_dimensions
121
130
  ```
122
131
  ```rb
123
132
  uploaded_file = uploader.upload(image)
@@ -130,26 +139,31 @@ uploaded_file.height #=> 900
130
139
  uploaded_file.dimensions #=> [1600, 900]
131
140
  ```
132
141
 
142
+ By default, the plugin uses [FastImage] to analyze dimensions, but you can also
143
+ have it use [MiniMagick] or [ruby-vips]. See the
144
+ [`store_dimensions`][store_dimensions] plugin docs for more details.
145
+
133
146
  ## Custom metadata
134
147
 
135
148
  In addition to the built-in metadata, Shrine allows you to extract and store
136
- any custom metadata, using the `add_metadata` plugin (which extends
137
- `Shrine#extract_metadata`). For example, you might want to extract EXIF data
138
- from images:
149
+ any custom metadata, using the [`add_metadata`][add_metadata] plugin (which
150
+ internally extends `Shrine#extract_metadata`).
151
+
152
+ For example, you might want to extract EXIF data from images:
139
153
 
140
154
  ```rb
141
- require "mini_magick"
155
+ # Gemfile
156
+ gem "exiftool"
157
+ ```
158
+ ```rb
159
+ require "exiftool"
142
160
 
143
161
  class ImageUploader < Shrine
144
162
  plugin :add_metadata
145
163
 
146
164
  add_metadata :exif do |io, context|
147
165
  Shrine.with_file(io) do |file|
148
- begin
149
- MiniMagick::Image.new(file.path).exif
150
- rescue MiniMagick::Error
151
- # not a valid image
152
- end
166
+ Exiftool.new(file.path).to_hash
153
167
  end
154
168
  end
155
169
  end
@@ -161,8 +175,12 @@ uploaded_file.exif #=> {...}
161
175
  ```
162
176
 
163
177
  Or, if you're uploading videos, you might want to extract some video-specific
164
- meatadata:
178
+ metadata:
165
179
 
180
+ ```rb
181
+ # Gemfile
182
+ gem "streamio-ffmpeg"
183
+ ```
166
184
  ```rb
167
185
  require "streamio-ffmpeg"
168
186
 
@@ -192,11 +210,21 @@ uploaded_file.metadata #=>
192
210
  ```
193
211
 
194
212
  The yielded `io` object will not always be an object that responds to `#path`.
195
- If you're using the `data_uri` plugin, the `io` will be a `StringIO` wrapper.
196
- With `restore_cached_data` or `refresh_metadata` plugins, `io` might be a
197
- `Shrine::UploadedFile` object. If you're using a metadata analyzer that
198
- requires the source file to be on disk, you can use `Shrine.with_file` to
199
- ensure you have a file object.
213
+ For example, with the `data_uri` plugin the `io` can be a `StringIO` wrapper,
214
+ while with `restore_cached_data` or `refresh_metadata` plugins the `io` might
215
+ be a `Shrine::UploadedFile` object. So, we're using `Shrine.with_file` to
216
+ ensure we have a file object.
217
+
218
+ ### Adding metadata
219
+
220
+ If you wish to add metadata to an already attached file, you can do it as
221
+ follows:
222
+
223
+ ```rb
224
+ photo.image_attacher.add_metadata("foo" => "bar")
225
+ photo.image.metadata #=> { ..., "foo" => "bar" }
226
+ photo.save # persist changes
227
+ ```
200
228
 
201
229
  ## Metadata columns
202
230
 
@@ -216,20 +244,23 @@ photo.image_type #=> "image/jpeg"
216
244
  When attaching files that were uploaded directly to the cloud or a [tus
217
245
  server], Shrine won't automatically extract metadata from them, instead it will
218
246
  copy any existing metadata that was set on the client side. The reason why this
219
- is the default behaviour is because extracting the metadata would require (at
220
- least partially) retrieving file content from the storage, which could
221
- potentially be expensive depending on the storage and the type of metadata
222
- being extracted.
247
+ is the default behaviour is because metadata extraction requires (at least
248
+ partially) retrieving file content from the storage, which could potentially be
249
+ expensive depending on the storage and the type of metadata being extracted.
250
+
251
+ ```rb
252
+ # no additional metadata will be extracted in this assignment by default
253
+ photo.image = '{"id":"9e6581a4ea1.jpg","storage":"cache","metadata":{...}}'
254
+ ```
255
+
256
+ ### Extracting on attachment
223
257
 
224
- There are two ways of extracting metadata from directly uploaded files. If you
225
- want metadata to be automatically extracted on assignment (which is useful if
226
- you want to validate the extracted metadata or have it immediately available
227
- for any other reason), you can load the `restore_cached_data` plugin:
258
+ If you want metadata to be automatically extracted on assignment (which is
259
+ useful if you want to validate the extracted metadata or have it immediately
260
+ available for any other reason), you can load the `restore_cached_data` plugin:
228
261
 
229
262
  ```rb
230
- class ImageUploader < Shrine
231
- plugin :restore_cached_data # automatically extract metadata from cached files on assignment
232
- end
263
+ Shrine.plugin :restore_cached_data # automatically extract metadata from cached files on assignment
233
264
  ```
234
265
  ```rb
235
266
  photo.image = '{"id":"ks9elsd.jpg","storage":"cache","metadata":{}}' # metadata is extracted
@@ -241,100 +272,147 @@ photo.image.metadata #=>
241
272
  # }
242
273
  ```
243
274
 
244
- On the other hand, if you're using backgrounding, you can extract metadata
245
- during background promotion using the `refresh_metadata` plugin (which the
246
- `restore_cached_data` plugin uses internally):
275
+ ### Extracting in the background
276
+
277
+ #### A) Extracting with promotion
278
+
279
+ If you're using [backgrounding], you can extract metadata during background
280
+ promotion using the `refresh_metadata` plugin (which the `restore_cached_data`
281
+ plugin uses internally):
247
282
 
248
283
  ```rb
249
- class ImageUploader < Shrine
250
- plugin :refresh_metadata
251
- plugin :processing
284
+ Shrine.plugin :refresh_metadata # allow re-extracting metadata
285
+ Shrine.plugin :backgrounding
286
+
287
+ Shrine::Attacher.promote_block do
288
+ PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
289
+ end
290
+ ```
291
+ ```rb
292
+ class PromoteJob
293
+ include Sidekiq::Worker
252
294
 
253
- # this will be called in the background if using backgrounding plugin
254
- process(:store) do |io, context|
255
- io.refresh_metadata!(context) # extracts metadata and updates `io.metadata`
256
- io
295
+ def perform(attacher_class, record_class, record_id, name, file_data)
296
+ attacher_class = Object.const_get(attacher_class)
297
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
298
+
299
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
300
+ attacher.refresh_metadata! # extract metadata
301
+ attacher.atomic_promote
257
302
  end
258
303
  end
259
304
  ```
260
305
 
261
- If you have metadata that is cheap to extract in the foreground, but also have
262
- additional metadata that can be extracted asynchronously, you can combine the
263
- two approaches. For example, if you're attaching video files, you might want to
264
- extract MIME type upfront and video-specific metadata in a background job, which
265
- can be done as follows (provided that `backgrounding` plugin is used):
306
+ #### B) Extracting separately from promotion
307
+
308
+ You can also extract metadata in the background separately from promotion:
266
309
 
267
310
  ```rb
268
- class MyUploader < Shrine
269
- plugin :determine_mime_type # this will be called in the foreground
270
- plugin :restore_cached_data
271
- plugin :refresh_metadata
272
- plugin :add_metadata
273
- plugin :processing
311
+ MetadataJob.perform_async(
312
+ attacher.class.name,
313
+ attacher.record.class.name,
314
+ attacher.record.id,
315
+ attacher.name,
316
+ attacher.file_data,
317
+ )
318
+ ```
319
+ ```rb
320
+ class MetadataJob
321
+ include Sidekiq::Worker
322
+
323
+ def perform(attacher_class, record_class, record_id, name, file_data)
324
+ attacher_class = Object.const_get(attacher_class)
325
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
274
326
 
275
- # this will be called in the background if using backgrounding plugin
276
- process(:store) do |io, context|
277
- io.refresh_metadata!(context)
278
- io
327
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
328
+ attacher.refresh_metadata!
329
+ attacher.atomic_persist
279
330
  end
331
+ end
332
+ ```
280
333
 
281
- add_metadata do |io, context|
282
- next unless context[:action] == :store # this will be the case during promotion
334
+ ### Combining foreground and background
283
335
 
284
- Shrine.with_file(io) do |file|
285
- # example of metadata extraction
286
- movie = FFMPEG::Movie.new(file.path) # uses the streamio-ffmpeg gem
336
+ If you have some metadata that you want to extract in the foreground and some
337
+ that you want to extract in the background, you can use the uploader context:
287
338
 
288
- { "duration" => movie.duration,
289
- "bitrate" => movie.bitrate,
290
- "resolution" => movie.resolution,
291
- "frame_rate" => movie.frame_rate }
292
- end
339
+ ```rb
340
+ class VideoUploader < Shrine
341
+ plugin :add_metadata
342
+
343
+ add_metadata do |io, **options|
344
+ next unless options[:background] # proceed only when `background: true` was specified
345
+
346
+ # example of metadata extraction
347
+ movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) }
348
+
349
+ { "duration" => movie.duration,
350
+ "bitrate" => movie.bitrate,
351
+ "resolution" => movie.resolution,
352
+ "frame_rate" => movie.frame_rate }
353
+ end
354
+ end
355
+ ```
356
+ ```rb
357
+ class PromoteJob
358
+ include Sidekiq::Worker
359
+
360
+ def perform(attacher_class, record_class, record_id, name, file_data)
361
+ attacher_class = Object.const_get(attacher_class)
362
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
363
+
364
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
365
+ attacher.refresh_metadata!(background: true) # specify the flag
366
+ attacher.atomic_promote
293
367
  end
294
368
  end
295
369
  ```
296
370
 
371
+ Now triggering metadata extraction in the controller on attachment (using
372
+ `restore_cached_data` or `refresh_metadata` plugin) will skip the video
373
+ metadata block, which will be triggered later in the background job.
374
+
375
+ ### Optimizations
376
+
297
377
  If you want to do both metadata extraction and file processing during
298
378
  promotion, you can wrap both in an `UploadedFile#open` block to make
299
379
  sure the file content is retrieved from the storage only once.
300
380
 
301
381
  ```rb
302
- class MyUploader < Shrine
303
- plugin :refresh_metadata
304
- plugin :processing
382
+ class PromoteJob
383
+ include Sidekiq::Worker
384
+
385
+ def perform(attacher_class, record_class, record_id, name, file_data)
386
+ attacher_class = Object.const_get(attacher_class)
387
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
305
388
 
306
- process(:store) do |io, context|
307
- io.open do |io, context|
308
- io.refresh_metadata!(context)
389
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
309
390
 
310
- original = io.download # reuses already open uploaded file
311
- # ... processing ...
391
+ attacher.file.open do
392
+ attacher.refresh_metadata!
393
+ attacher.create_derivatives
312
394
  end
395
+
396
+ attacher.atomic_promote
313
397
  end
314
398
  end
315
399
  ```
316
400
 
317
- If you're dealing with large files, it's recommended to also use the `tempfile`
318
- plugin to make sure the same copy of the uploaded file is used for metadata
319
- extraction (`Shrine.with_file`) and processing (`UploadedFile#tempfile`).
401
+ If you're dealing with large files and have metadata extractors that use
402
+ `Shrine.with_file`, you might want to use the `tempfile` plugin to make sure
403
+ the same copy of the uploaded file is reused for both metadata extraction and
404
+ file processing.
320
405
 
321
406
  ```rb
322
407
  Shrine.plugin :tempfile # load it globally so that it overrides `Shrine.with_file`
323
408
  ```
324
409
  ```rb
325
- class MyUploader < Shrine
326
- plugin :refresh_metadata
327
- plugin :processing
328
-
329
- process(:store) do |io, context|
330
- io.open do |io, context|
331
- io.refresh_metadata!(context)
332
-
333
- original = io.tempfile # used the cached tempfile
334
- # ... processing ...
335
- end
336
- end
410
+ # ...
411
+ attacher.file.open do
412
+ attacher.refresh_metadata!
413
+ attacher.create_derivatives(attacher.file.tempfile)
337
414
  end
415
+ # ...
338
416
  ```
339
417
 
340
418
  [`file`]: http://linux.die.net/man/1/file
@@ -344,4 +422,7 @@ end
344
422
  [MiniMagick]: https://github.com/minimagick/minimagick
345
423
  [ruby-vips]: https://github.com/libvips/ruby-vips
346
424
  [tus server]: https://github.com/janko/tus-ruby-server
347
- [determine_mime_type]: /doc/plugins/determine_mime_type.md#readme
425
+ [determine_mime_type]: https://shrinerb.com/docs/plugins/determine_mime_type
426
+ [store_dimensions]: https://shrinerb.com/docs/plugins/store_dimensions
427
+ [add_metadata]: https://shrinerb.com/docs/plugins/add_metadata
428
+ [backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
@@ -1,4 +1,10 @@
1
- # Multiple Files
1
+ ---
2
+ id: multiple-files
3
+ title: Multiple Files
4
+ ---
5
+
6
+ import Tabs from '@theme/Tabs';
7
+ import TabItem from '@theme/TabItem';
2
8
 
3
9
  There are times when you want to allow users to attach multiple files to a
4
10
  single resource, like an album having many photos or a playlist having many
@@ -64,24 +70,32 @@ files (or attachments) table will be the photos table.
64
70
  Let's create a table for the main resource and attachments, and add a foreign
65
71
  key in the attachment table for the main table:
66
72
 
73
+ <Tabs>
74
+ <TabItem value="sequel" label="Sequel">
75
+
67
76
  ```rb
68
- # with Sequel:
69
77
  Sequel.migration do
70
78
  change do
71
79
  create_table :albums do
72
80
  primary_key :id
73
- column :title, :text
81
+
82
+ String :title
74
83
  end
75
84
 
76
85
  create_table :photos do
77
86
  primary_key :id
78
87
  foreign_key :album_id, :albums
79
- column :image_data, :text
88
+
89
+ String :image_data
80
90
  end
81
91
  end
82
92
  end
93
+ ```
94
+
95
+ </TabItem>
96
+ <TabItem value="activerecord" label="Active Record">
83
97
 
84
- # with Active Record:
98
+ ```rb
85
99
  class CreateAlbumsAndPhotos < ActiveRecord::Migration
86
100
  def change
87
101
  create_table :albums do |t|
@@ -98,21 +112,33 @@ class CreateAlbumsAndPhotos < ActiveRecord::Migration
98
112
  end
99
113
  ```
100
114
 
115
+ </TabItem>
116
+ </Tabs>
117
+
101
118
  In the Photo model, create a Shrine attachment attribute named `image`
102
119
  (`:image` matches the `_data` column prefix above):
103
120
 
121
+ <Tabs>
122
+ <TabItem value="sequel" label="Sequel">
123
+
104
124
  ```rb
105
- # with Sequel:
106
125
  class Photo < Sequel::Model
107
- include ImageUploader::Attachment.new(:image)
126
+ include ImageUploader::Attachment(:image)
108
127
  end
128
+ ```
109
129
 
110
- # with Active Record:
130
+ </TabItem>
131
+ <TabItem value="activerecord" label="Active Record">
132
+
133
+ ```rb
111
134
  class Photo < ActiveRecord::Base
112
- include ImageUploader::Attachment.new(:image)
135
+ include ImageUploader::Attachment(:image)
113
136
  end
114
137
  ```
115
138
 
139
+ </TabItem>
140
+ </Tabs>
141
+
116
142
  ### 2. Declare nested attributes
117
143
 
118
144
  Using nested attributes is the easiest way to implement any dynamic
@@ -120,8 +146,10 @@ Using nested attributes is the easiest way to implement any dynamic
120
146
  relationship to the photos table, and allow it to directly accept attributes
121
147
  for the associated photo records by enabling nested attributes:
122
148
 
149
+ <Tabs>
150
+ <TabItem value="sequel" label="Sequel">
151
+
123
152
  ```rb
124
- # with Sequel:
125
153
  class Album < Sequel::Model
126
154
  one_to_many :photos
127
155
  plugin :association_dependencies, photos: :destroy # destroy photos when album is destroyed
@@ -129,14 +157,32 @@ class Album < Sequel::Model
129
157
  plugin :nested_attributes
130
158
  nested_attributes :photos, destroy: true
131
159
  end
160
+ ```
132
161
 
133
- # with Active Record:
162
+ </TabItem>
163
+ <TabItem value="activerecord" label="Active Record">
164
+
165
+ ```rb
134
166
  class Album < ActiveRecord::Base
135
167
  has_many :photos, dependent: :destroy
136
168
  accepts_nested_attributes_for :photos, allow_destroy: true
137
169
  end
138
170
  ```
139
171
 
172
+ </TabItem>
173
+ <TabItem value="mongoid" label="Mongoid">
174
+
175
+ ```rb
176
+ class Album
177
+ include Mongoid::Document
178
+ embeds_many :photos
179
+ accepts_nested_attributes_for :photos
180
+ end
181
+ ```
182
+
183
+ </TabItem>
184
+ </Tabs>
185
+
140
186
  Documentation on nested attributes:
141
187
 
142
188
  * [`Sequel::Model.nested_attributes`]
@@ -152,8 +198,26 @@ already created photos, so that the same form can be used for updating the
152
198
  album/photos as well (they will be submitted under the
153
199
  `album[photos_attributes]` parameter).
154
200
 
201
+ <Tabs>
202
+ <TabItem value="rails" label="Rails form builder">
203
+
204
+ ```rb
205
+ form_for @album, html: { enctype: "multipart/form-data" } do |f|
206
+ f.text_field :title
207
+ f.fields_for :photos do |p| # adds new `album[photos_attributes]` parameter
208
+ p.hidden_field :image, value: p.object.cached_image_data, id: nil
209
+ p.file_field :image
210
+ p.check_box :_destroy unless p.object.new_record?
211
+ end
212
+ file_field_tag "files[]", multiple: true
213
+ f.submit "Create"
214
+ end
215
+ ```
216
+
217
+ </TabItem>
218
+ <TabItem value="forme" label="Forme">
219
+
155
220
  ```rb
156
- # with Forme:
157
221
  form @album, action: "/photos", enctype: "multipart/form-data" do |f|
158
222
  f.input :title
159
223
  f.subform :photos do # adds new `album[photos_attributes]` parameter
@@ -164,20 +228,11 @@ form @album, action: "/photos", enctype: "multipart/form-data" do |f|
164
228
  f.input "files[]", type: :file, attr: { multiple: true }, obj: nil
165
229
  f.button "Create"
166
230
  end
167
-
168
- # with Rails form builder:
169
- form_for @album, html: { enctype: "multipart/form-data" } do |f|
170
- f.text_field :title
171
- f.fields_for :photos do |p| # adds new `album[photos_attributes]` parameter
172
- p.hidden_field :image, value: p.object.cached_image_data
173
- p.file_field :image
174
- p.check_box :_destroy unless p.object.new_record?
175
- end
176
- file_field_tag "files[]", multiple: true
177
- f.submit "Create"
178
- end
179
231
  ```
180
232
 
233
+ </TabItem>
234
+ </Tabs>
235
+
181
236
  In your controller you should still be able to assign all the attributes to the
182
237
  album, just remember to whitelist the new parameter for the nested attributes,
183
238
  in this case `album[photos_attributes]`.
@@ -203,7 +258,7 @@ photos_attributes = album_params[:photos_attributes].to_h.merge(new_photos_attri
203
258
  album_attributes = album_params.merge(photos_attributes: photos_attributes)
204
259
 
205
260
  # create the album with photos
206
- Album.create(album_params)
261
+ Album.create(album_attributes)
207
262
  ```
208
263
 
209
264
  In this case you need to make sure your form tag has
@@ -257,22 +312,30 @@ class ImageUploader < Shrine
257
312
 
258
313
  Attacher.validate do
259
314
  validate_max_size 10*1024*1024
260
- validate_mime_type_inclusion %w[image/jpeg image/png]
315
+ validate_mime_type %w[image/jpeg image/png image/webp]
261
316
  end
262
317
  end
263
318
  ```
319
+ <Tabs>
320
+ <TabItem value="sequel" label="Sequel">
321
+
264
322
  ```rb
265
- # with Sequel:
266
323
  class Album < Sequel::Model
267
324
  # ... (nested_attributes already enables validating associated photos) ...
268
325
  end
326
+ ```
327
+
328
+ </TabItem>
329
+ <TabItem value="activerecord" label="Active Record">
269
330
 
270
- # with ActiveRecord:
331
+ ```rb
271
332
  class Album < ActiveRecord::Base
272
333
  # ...
273
334
  validates_associated :photos
274
335
  end
275
336
  ```
337
+ </TabItem>
338
+ </Tabs>
276
339
 
277
340
  Note that by default only metadata set on the client side will be available for
278
341
  validations. Shrine will not automatically run metadata extraction for directly
@@ -294,8 +357,8 @@ attributes feature gives you for free.
294
357
  [`Sequel::Model.nested_attributes`]: http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html
295
358
  [`ActiveRecord::Base.accepts_nested_attributes_for`]: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
296
359
  [`Mongoid::Document.accepts_nested_attributes_for`]: https://docs.mongodb.com/mongoid/master/tutorials/mongoid-nested-attributes/
297
- [`upload_endpoint`]: /doc/plugins/upload_endpoint.md#readme
298
- [`presign_endpoint`]: /doc/plugins/presign_endpoint.md#readme
360
+ [`upload_endpoint`]: https://shrinerb.com/docs/plugins/upload_endpoint
361
+ [`presign_endpoint`]: https://shrinerb.com/docs/plugins/presign_endpoint
299
362
  [Uppy]: https://uppy.io
300
363
  [direct app uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
301
364
  [direct S3 uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads