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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +523 -41
- data/LICENSE.txt +1 -1
- data/README.md +83 -979
- data/doc/advantages.md +231 -204
- data/doc/attacher.md +304 -153
- data/doc/carrierwave.md +297 -226
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +103 -21
- data/doc/changing_storage.md +110 -0
- data/doc/creating_persistence_plugins.md +132 -0
- data/doc/creating_plugins.md +43 -23
- data/doc/creating_storages.md +19 -5
- data/doc/design.md +147 -97
- data/doc/direct_s3.md +38 -28
- data/doc/external/articles.md +63 -0
- data/doc/external/extensions.md +53 -0
- data/doc/external/misc.md +32 -0
- data/doc/getting_started.md +1156 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +93 -30
- data/doc/paperclip.md +384 -262
- data/doc/plugins/activerecord.md +177 -46
- data/doc/plugins/add_metadata.md +139 -38
- data/doc/plugins/atomic_helpers.md +217 -0
- data/doc/plugins/backgrounding.md +156 -98
- data/doc/plugins/cached_attachment_data.md +7 -5
- data/doc/plugins/column.md +121 -0
- data/doc/plugins/data_uri.md +23 -22
- data/doc/plugins/default_storage.md +36 -10
- data/doc/plugins/default_url.md +30 -13
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +186 -101
- data/doc/plugins/derivatives.md +839 -0
- data/doc/plugins/determine_mime_type.md +4 -2
- data/doc/plugins/download_endpoint.md +64 -8
- data/doc/plugins/dynamic_storage.md +5 -3
- data/doc/plugins/entity.md +263 -0
- data/doc/plugins/form_assign.md +55 -0
- data/doc/plugins/included.md +31 -8
- data/doc/plugins/infer_extension.md +21 -10
- data/doc/plugins/instrumentation.md +38 -16
- data/doc/plugins/keep_files.md +16 -17
- data/doc/plugins/metadata_attributes.md +42 -13
- data/doc/plugins/mirroring.md +118 -0
- data/doc/plugins/model.md +210 -0
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +24 -0
- data/doc/plugins/persistence.md +101 -0
- data/doc/plugins/presign_endpoint.md +9 -4
- data/doc/plugins/pretty_location.md +16 -3
- data/doc/plugins/processing.md +4 -2
- data/doc/plugins/rack_file.md +8 -2
- data/doc/plugins/rack_response.md +6 -2
- data/doc/plugins/recache.md +4 -2
- data/doc/plugins/refresh_metadata.md +49 -9
- data/doc/plugins/remote_url.md +84 -47
- data/doc/plugins/remove_attachment.md +27 -6
- data/doc/plugins/remove_invalid.md +21 -6
- data/doc/plugins/restore_cached_data.md +11 -3
- data/doc/plugins/sequel.md +159 -35
- data/doc/plugins/signature.md +16 -5
- data/doc/plugins/store_dimensions.md +14 -2
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +13 -13
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
- data/doc/plugins/validation.md +97 -0
- data/doc/plugins/validation_helpers.md +16 -13
- data/doc/plugins/versions.md +15 -19
- data/doc/processing.md +438 -221
- data/doc/refile.md +188 -170
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +6 -2
- data/doc/release_notes/1.2.0.md +4 -0
- data/doc/release_notes/1.3.0.md +4 -0
- data/doc/release_notes/1.4.0.md +4 -0
- data/doc/release_notes/1.4.1.md +4 -0
- data/doc/release_notes/1.4.2.md +4 -0
- data/doc/release_notes/2.0.0.md +4 -0
- data/doc/release_notes/2.0.1.md +4 -0
- data/doc/release_notes/2.1.0.md +5 -1
- data/doc/release_notes/2.1.1.md +4 -0
- data/doc/release_notes/2.10.0.md +4 -0
- data/doc/release_notes/2.10.1.md +4 -0
- data/doc/release_notes/2.11.0.md +4 -0
- data/doc/release_notes/2.12.0.md +4 -0
- data/doc/release_notes/2.13.0.md +4 -0
- data/doc/release_notes/2.14.0.md +5 -1
- data/doc/release_notes/2.15.0.md +11 -7
- data/doc/release_notes/2.16.0.md +4 -0
- data/doc/release_notes/2.17.0.md +4 -0
- data/doc/release_notes/2.18.0.md +4 -0
- data/doc/release_notes/2.19.0.md +6 -3
- data/doc/release_notes/2.2.0.md +4 -0
- data/doc/release_notes/2.3.0.md +4 -0
- data/doc/release_notes/2.3.1.md +4 -0
- data/doc/release_notes/2.4.0.md +4 -0
- data/doc/release_notes/2.4.1.md +4 -0
- data/doc/release_notes/2.5.0.md +4 -0
- data/doc/release_notes/2.6.0.md +4 -0
- data/doc/release_notes/2.6.1.md +4 -0
- data/doc/release_notes/2.7.0.md +4 -0
- data/doc/release_notes/2.8.0.md +4 -0
- data/doc/release_notes/2.9.0.md +4 -0
- data/doc/release_notes/3.0.0.md +981 -0
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/release_notes/3.5.0.md +63 -0
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/retrieving_uploads.md +5 -2
- data/doc/securing_uploads.md +60 -37
- data/doc/storage/file_system.md +20 -3
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +122 -78
- data/doc/testing.md +141 -133
- data/doc/upgrading_to_3.md +708 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +292 -169
- data/lib/shrine/attachment.rb +13 -46
- data/lib/shrine/plugins/_persistence.rb +93 -0
- data/lib/shrine/plugins/activerecord.rb +77 -34
- data/lib/shrine/plugins/add_metadata.rb +25 -17
- data/lib/shrine/plugins/atomic_helpers.rb +119 -0
- data/lib/shrine/plugins/backgrounding.rb +77 -113
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
- data/lib/shrine/plugins/column.rb +102 -0
- data/lib/shrine/plugins/data_uri.rb +38 -36
- data/lib/shrine/plugins/default_storage.rb +45 -15
- data/lib/shrine/plugins/default_url.rb +12 -24
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +10 -16
- data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
- data/lib/shrine/plugins/derivatives.rb +645 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +118 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +158 -0
- data/lib/shrine/plugins/form_assign.rb +108 -0
- data/lib/shrine/plugins/included.rb +6 -6
- data/lib/shrine/plugins/infer_extension.rb +17 -20
- data/lib/shrine/plugins/instrumentation.rb +59 -43
- data/lib/shrine/plugins/keep_files.rb +3 -15
- data/lib/shrine/plugins/metadata_attributes.rb +28 -19
- data/lib/shrine/plugins/mirroring.rb +142 -0
- data/lib/shrine/plugins/model.rb +160 -0
- data/lib/shrine/plugins/module_include.rb +3 -3
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/presign_endpoint.rb +27 -28
- data/lib/shrine/plugins/pretty_location.rb +15 -9
- data/lib/shrine/plugins/processing.rb +22 -9
- data/lib/shrine/plugins/rack_file.rb +2 -42
- data/lib/shrine/plugins/rack_response.rb +21 -10
- data/lib/shrine/plugins/recache.rb +6 -5
- data/lib/shrine/plugins/refresh_metadata.rb +13 -11
- data/lib/shrine/plugins/remote_url.rb +49 -49
- data/lib/shrine/plugins/remove_attachment.rb +12 -6
- data/lib/shrine/plugins/remove_invalid.rb +19 -8
- data/lib/shrine/plugins/restore_cached_data.rb +13 -7
- data/lib/shrine/plugins/sequel.rb +86 -36
- data/lib/shrine/plugins/signature.rb +10 -16
- data/lib/shrine/plugins/store_dimensions.rb +35 -40
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +28 -24
- data/lib/shrine/plugins/upload_options.rb +14 -15
- data/lib/shrine/plugins/url_options.rb +31 -0
- data/lib/shrine/plugins/validation.rb +80 -0
- data/lib/shrine/plugins/validation_helpers.rb +35 -58
- data/lib/shrine/plugins/versions.rb +107 -87
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/storage/file_system.rb +46 -64
- data/lib/shrine/storage/linter.rb +42 -7
- data/lib/shrine/storage/memory.rb +49 -0
- data/lib/shrine/storage/s3.rb +173 -160
- data/lib/shrine/uploaded_file.rb +32 -32
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +87 -150
- data/shrine.gemspec +11 -12
- metadata +92 -82
- data/doc/migrating_storage.md +0 -76
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- data/lib/shrine/plugins/parsed_json.rb +0 -29
data/doc/metadata.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
114
|
-
It adds `width` and `height` metadata values, and
|
|
115
|
-
`#height`, and `#dimensions` methods to the
|
|
116
|
-
|
|
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
|
-
|
|
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
|
|
137
|
-
`Shrine#extract_metadata`).
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
`Shrine::UploadedFile` object.
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
want
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
282
|
-
next unless context[:action] == :store # this will be the case during promotion
|
|
334
|
+
### Combining foreground and background
|
|
283
335
|
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
311
|
-
|
|
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
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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]: /
|
|
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
|
data/doc/multiple_files.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
126
|
+
include ImageUploader::Attachment(:image)
|
|
108
127
|
end
|
|
128
|
+
```
|
|
109
129
|
|
|
110
|
-
|
|
130
|
+
</TabItem>
|
|
131
|
+
<TabItem value="activerecord" label="Active Record">
|
|
132
|
+
|
|
133
|
+
```rb
|
|
111
134
|
class Photo < ActiveRecord::Base
|
|
112
|
-
include ImageUploader::Attachment
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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`]: /
|
|
298
|
-
[`presign_endpoint`]: /
|
|
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
|