shrine 2.10.1 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +25 -1
- data/README.md +241 -393
- data/doc/advantages.md +346 -0
- data/doc/attacher.md +1 -1
- data/doc/carrierwave.md +9 -9
- data/doc/creating_storages.md +172 -84
- data/doc/design.md +1 -1
- data/doc/direct_s3.md +98 -85
- data/doc/metadata.md +213 -0
- data/doc/migrating_storage.md +1 -1
- data/doc/multiple_files.md +4 -3
- data/doc/paperclip.md +4 -4
- data/doc/processing.md +415 -0
- data/doc/refile.md +23 -23
- data/doc/testing.md +47 -51
- data/doc/validation.md +148 -0
- data/lib/shrine.rb +45 -4
- data/lib/shrine/plugins/add_metadata.rb +35 -14
- data/lib/shrine/plugins/determine_mime_type.rb +7 -5
- data/lib/shrine/plugins/direct_upload.rb +3 -1
- data/lib/shrine/plugins/infer_extension.rb +1 -1
- data/lib/shrine/plugins/metadata_attributes.rb +2 -2
- data/lib/shrine/plugins/presign_endpoint.rb +27 -17
- data/lib/shrine/plugins/rack_response.rb +4 -4
- data/lib/shrine/plugins/signature.rb +1 -1
- data/lib/shrine/plugins/store_dimensions.rb +10 -18
- data/lib/shrine/plugins/upload_endpoint.rb +22 -0
- data/lib/shrine/plugins/versions.rb +10 -14
- data/lib/shrine/storage/linter.rb +11 -0
- data/lib/shrine/storage/s3.rb +57 -30
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +3 -3
- metadata +11 -7
data/doc/design.md
CHANGED
data/doc/direct_s3.md
CHANGED
@@ -23,11 +23,12 @@ storage service is beneficial for several reasons:
|
|
23
23
|
request-response lifecycle might not be able to finish before the request
|
24
24
|
times out.
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
To start, let's set both temporary and permanent storage to S3, with the
|
27
|
+
temporary storage uploading to the `cache/` directory:
|
28
28
|
|
29
29
|
```rb
|
30
30
|
# Gemfile
|
31
|
+
gem "shrine", "~> 2.11"
|
31
32
|
gem "aws-sdk-s3", "~> 1.2"
|
32
33
|
```
|
33
34
|
```rb
|
@@ -42,7 +43,7 @@ s3_options = {
|
|
42
43
|
|
43
44
|
Shrine.storages = {
|
44
45
|
cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
|
45
|
-
store: Shrine::Storage::S3.new(
|
46
|
+
store: Shrine::Storage::S3.new(**s3_options),
|
46
47
|
}
|
47
48
|
```
|
48
49
|
|
@@ -69,7 +70,7 @@ client.put_bucket_cors(
|
|
69
70
|
cors_configuration: {
|
70
71
|
cors_rules: [{
|
71
72
|
allowed_headers: ["Authorization", "Content-Type", "Origin"],
|
72
|
-
allowed_methods: ["GET", "POST"],
|
73
|
+
allowed_methods: ["GET", "POST", "PUT"],
|
73
74
|
allowed_origins: ["*"],
|
74
75
|
max_age_seconds: 3000,
|
75
76
|
}]
|
@@ -80,27 +81,6 @@ client.put_bucket_cors(
|
|
80
81
|
Note that due to DNS propagation it may take some time for the CORS update to
|
81
82
|
be applied.
|
82
83
|
|
83
|
-
## File hash
|
84
|
-
|
85
|
-
After direct S3 uploads we'll need to manually construct Shrine's JSON
|
86
|
-
representation of an uploaded file:
|
87
|
-
|
88
|
-
```rb
|
89
|
-
{
|
90
|
-
"id": "349234854924394", # requied
|
91
|
-
"storage": "cache", # required
|
92
|
-
"metadata": {
|
93
|
-
"size": 45461, # optional, but recommended
|
94
|
-
"filename": "foo.jpg", # optional
|
95
|
-
"mime_type": "image/jpeg" # optional
|
96
|
-
}
|
97
|
-
}
|
98
|
-
```
|
99
|
-
|
100
|
-
* `id` – location of the file on S3 (minus the `:prefix`)
|
101
|
-
* `storage` – direct uploads typically use the `:cache` storage
|
102
|
-
* `metadata` – hash of metadata extracted from the file
|
103
|
-
|
104
84
|
## Strategy A (dynamic)
|
105
85
|
|
106
86
|
* Best user experience
|
@@ -113,7 +93,7 @@ upload the file to S3. The `presign_endpoint` plugin gives us this presign
|
|
113
93
|
route, so we just need to mount it in our application:
|
114
94
|
|
115
95
|
```rb
|
116
|
-
Shrine.plugin :presign_endpoint
|
96
|
+
Shrine.plugin :presign_endpoint, presign_options: { method: :put }
|
117
97
|
```
|
118
98
|
```rb
|
119
99
|
# config.ru (Rack)
|
@@ -129,37 +109,31 @@ Rails.application.routes.draw do
|
|
129
109
|
end
|
130
110
|
```
|
131
111
|
|
132
|
-
The above will create a `GET /presign` route, which
|
133
|
-
|
134
|
-
|
112
|
+
The above will create a `GET /presign` route, which internally calls
|
113
|
+
[`Shrine::Storage::S3#presign`], returning the HTTP verb (PUT) and the S3 URL
|
114
|
+
to which the file should be uploaded, along with the required parameters (will
|
115
|
+
only be present for POST presigns) and request headers.
|
135
116
|
|
136
117
|
```rb
|
137
118
|
# GET /presign
|
138
119
|
{
|
139
|
-
"
|
140
|
-
"
|
141
|
-
|
142
|
-
"policy": "eyJleHBpcmF0aW9uIjoiMjAxNS0QwMToxMToyOVoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaHJpbmUtdGVzdGluZyJ9LHsia2V5IjoiYjdkNTc1ODUwYmE2MWI0NGU3Y2M4YTliZmY4OGU5ZGZkYjE2NTQ0ZDk4OGNkYzI1ZjhkZDEyMTAwNGM4In0seyJ4LWFtei1jcmVkZW50aWFsIjoiQUtJQUlKRjU1VE1aWlk0NVVUNlEvMjAxNTEwMjQvZXUtd2VzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotYWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsieC1hbXotZGF0ZSI6IjIwMTUxMDI0VDAwMTEyOVoifV19",
|
143
|
-
"x-amz-credential": "AKIAIJF55TMZYT6Q/20151024/eu-west-1/s3/aws4_request",
|
144
|
-
"x-amz-algorithm": "AWS4-HMAC-SHA256",
|
145
|
-
"x-amz-date": "20151024T001129Z",
|
146
|
-
"x-amz-signature": "c1eb634f83f96b69bd675f535b3ff15ae184b102fcba51e4db5f4959b4ae26f4"
|
147
|
-
},
|
120
|
+
"method": "put",
|
121
|
+
"url": "https://my-bucket.s3.eu-central-1.amazonaws.com/cache/my-key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMDH2HTSB3RKB4WQ%2F20180424%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20180424T212022Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=1036b9cefe52f0b46c1f257f6817fc3c55cd8d9004f87a38cf86177762359375",
|
122
|
+
"fields": {},
|
148
123
|
"headers": {}
|
149
124
|
}
|
150
125
|
```
|
151
126
|
|
152
|
-
On the client side you can
|
153
|
-
|
127
|
+
On the client side you can make it so that, when the user selects a file,
|
128
|
+
upload parameters are fetched from presign endpoint, and are used to upload
|
154
129
|
the selected file directly to S3. It's recommended to use [Uppy] for this.
|
155
130
|
|
156
131
|
Once the file has been uploaded, you can generate a JSON representation of the
|
157
|
-
uploaded file on the client-side, and write it to the hidden attachment field
|
158
|
-
|
159
|
-
`:prefix`.
|
132
|
+
uploaded file on the client-side, and write it to the hidden attachment field
|
133
|
+
(or send it directly in an AJAX request).
|
160
134
|
|
161
|
-
```
|
162
|
-
|
135
|
+
```rb
|
136
|
+
{
|
163
137
|
"id": "302858ldg9agjad7f3ls.jpg",
|
164
138
|
"storage": "cache",
|
165
139
|
"metadata": {
|
@@ -167,12 +141,18 @@ The `id` field needs to be equal to the `key` presign field minus the storage
|
|
167
141
|
"filename": "nature.jpg",
|
168
142
|
"mime_type": "image/jpeg",
|
169
143
|
}
|
170
|
-
}
|
144
|
+
}
|
171
145
|
```
|
172
146
|
|
173
|
-
|
174
|
-
|
175
|
-
|
147
|
+
* `id` – location of the file on S3 (minus the `:prefix`)
|
148
|
+
* `storage` – direct uploads typically use the `:cache` storage
|
149
|
+
* `metadata` – hash of metadata extracted from the file
|
150
|
+
|
151
|
+
Once submitted this JSON will then be assigned to the attachment attribute
|
152
|
+
instead of the raw file. See [this walkthrough][direct S3 upload walkthrough]
|
153
|
+
for adding dynamic direct S3 uploads from scratch using [Uppy], as well as the
|
154
|
+
[Roda][roda demo] or [Rails][rails demo] demo app for a complete example of
|
155
|
+
multiple direct S3 uploads.
|
176
156
|
|
177
157
|
## Strategy B (static)
|
178
158
|
|
@@ -182,22 +162,22 @@ implementation of multiple direct S3 uploads.
|
|
182
162
|
|
183
163
|
An alternative to the previous strategy is to generate an S3 upload form on
|
184
164
|
page render. The user can then select a file and submit it directly to S3. For
|
185
|
-
generating the form
|
186
|
-
|
187
|
-
|
188
|
-
```
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
165
|
+
generating the form can use [`Shrine::Storage::S3#presign`], which returns URL
|
166
|
+
and form fields that should be used for the upload.
|
167
|
+
|
168
|
+
```rb
|
169
|
+
presigned_data = Shrine.storages[:cache].presign(
|
170
|
+
SecureRandom.hex,
|
171
|
+
success_action_redirect: new_album_url
|
172
|
+
)
|
173
|
+
|
174
|
+
Forme.form(action: presigned_data[:url], method: "post", enctype: "multipart/form-data") do |f|
|
175
|
+
presigned_data[:fields].each do |name, value|
|
176
|
+
f.input :hidden, name: name, value: value
|
177
|
+
end
|
178
|
+
f.input :file, name: "file"
|
179
|
+
f.input :submit, value: "Upload"
|
180
|
+
end
|
201
181
|
```
|
202
182
|
|
203
183
|
Note the additional `:success_action_redirect` option which tells S3 where to
|
@@ -206,30 +186,30 @@ builder to generate this form, you might need to also tell S3 to ignore the
|
|
206
186
|
additional `utf8` and `authenticity_token` fields that Rails generates:
|
207
187
|
|
208
188
|
```rb
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
189
|
+
presigned_data = Shrine.storages[:cache].presign(
|
190
|
+
SecureRandom.hex,
|
191
|
+
allow_any: ["utf8", "authenticity_token"],
|
192
|
+
success_action_redirect: new_album_url
|
193
|
+
)
|
194
|
+
|
195
|
+
# ...
|
214
196
|
```
|
215
197
|
|
216
198
|
Let's assume we specified the redirect URL to be a page which renders the form
|
217
199
|
for a new record. S3 will include some information about the upload in form of
|
218
200
|
GET parameters in the URL, out of which we only need the `key` parameter:
|
219
201
|
|
220
|
-
```
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
}
|
227
|
-
%>
|
202
|
+
```rb
|
203
|
+
cached_file = {
|
204
|
+
storage: "cache",
|
205
|
+
id: request.params[:key][/cache\/(.+)/, 1], # we subtract the storage prefix
|
206
|
+
metadata: {},
|
207
|
+
}
|
228
208
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
209
|
+
Forme.form(@album, action: "/albums", method: "post") do |f|
|
210
|
+
f.input :image, type: :hidden, value: cached_file.to_json
|
211
|
+
f.button "Save"
|
212
|
+
end
|
233
213
|
```
|
234
214
|
|
235
215
|
## Object data
|
@@ -278,15 +258,35 @@ following trick:
|
|
278
258
|
```rb
|
279
259
|
class MyUploader < Shrine
|
280
260
|
plugin :processing
|
261
|
+
plugin :refresh_metadata
|
281
262
|
|
282
263
|
process(:store) do |io, context|
|
283
|
-
|
284
|
-
io.metadata.update(real_metadata)
|
264
|
+
io.refresh_metadata!
|
285
265
|
io # return the same cached IO
|
286
266
|
end
|
287
267
|
end
|
288
268
|
```
|
289
269
|
|
270
|
+
## Checksum
|
271
|
+
|
272
|
+
To have AWS S3 verify the integrity of the uploaded data, you can use a
|
273
|
+
checksum. For that you first need to tell AWS S3 that you're going to be
|
274
|
+
including the `Content-MD5` request header in the upload request, by adding
|
275
|
+
the `:content_md5` presign option.
|
276
|
+
|
277
|
+
```rb
|
278
|
+
Shrine.plugin :presign_endpoint, presign_options: -> (request) do
|
279
|
+
{
|
280
|
+
content_md5: request.params["checksum"],
|
281
|
+
method: :put,
|
282
|
+
}
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
With the above setup, you can pass the MD5 hash of the file via the `checksum`
|
287
|
+
query parameter in the request to the presign endpoint. See [this
|
288
|
+
walkthrough][checksum walkthrough] for a complete JavaScript solution.
|
289
|
+
|
290
290
|
## Clearing cache
|
291
291
|
|
292
292
|
Directly uploaded files won't automatically be deleted from your temporary
|
@@ -353,11 +353,24 @@ Shrine::Attacher.promote do |data|
|
|
353
353
|
end
|
354
354
|
```
|
355
355
|
|
356
|
+
## Testing
|
357
|
+
|
358
|
+
To avoid network requests in your test and development environment, you can use
|
359
|
+
[Minio]. Minio is an open source object storage server with AWS S3 compatible
|
360
|
+
API which you can run locally. See how to set it up in the [Testing][minio
|
361
|
+
setup] guide.
|
362
|
+
|
363
|
+
[`Shrine::Storage::S3#presign`]: https://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html#method-i-presign
|
356
364
|
[`Aws::S3::PresignedPost`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#presigned_post-instance_method
|
357
|
-
[
|
365
|
+
[direct S3 upload walkthrough]: https://gist.github.com/janko-m/9aea154d72eb85b1fbfa16e1d77946e5#adding-direct-s3-uploads-to-a-roda--sequel-app-with-shrine
|
366
|
+
[checksum walkthrough]: https://gist.github.com/janko-m/4470b5fb0737c5c1f8bcfe8cdc3fd296#using-checksums-to-verify-integrity-of-direct-uploads-with-shrine--uppy
|
367
|
+
[roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
|
368
|
+
[rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
|
358
369
|
[Uppy]: https://uppy.io
|
359
370
|
[Amazon S3 Data Consistency Model]: http://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyMode
|
360
371
|
[CORS guide]: http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html
|
361
372
|
[CORS API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_cors-instance_method
|
362
373
|
[lifecycle Console]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
|
363
374
|
[lifecycle API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_lifecycle_configuration-instance_method
|
375
|
+
[Minio]: https://minio.io
|
376
|
+
[minio setup]: https://shrinerb.com/rdoc/files/doc/testing_md.html#label-Minio
|
data/doc/metadata.md
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
# Extracting Metadata
|
2
|
+
|
3
|
+
Before a file is uploaded, Shrine automatically extracts metadata from it, and
|
4
|
+
stores them in the `Shrine::UploadedFile` object. By default it extracts
|
5
|
+
`size`, `filename` and `mime_type`.
|
6
|
+
|
7
|
+
```rb
|
8
|
+
uploaded_file = uploader.upload(file)
|
9
|
+
uploaded_file.metadata #=>
|
10
|
+
# {
|
11
|
+
# "size" => 345993,
|
12
|
+
# "filename" => "matrix.mp4",
|
13
|
+
# "mime_type" => "video/mp4",
|
14
|
+
# }
|
15
|
+
```
|
16
|
+
|
17
|
+
You can also use `Shrine#extract_metadata` directly to extract metadata from
|
18
|
+
any IO object.
|
19
|
+
|
20
|
+
```rb
|
21
|
+
uploader.extract_metadata(io) #=>
|
22
|
+
# {
|
23
|
+
# "size" => 345993,
|
24
|
+
# "filename" => "matrix.mp4",
|
25
|
+
# "mime_type" => "video/mp4",
|
26
|
+
# }
|
27
|
+
```
|
28
|
+
|
29
|
+
## MIME type
|
30
|
+
|
31
|
+
By default, the `mime_type` metadata will be copied over from the
|
32
|
+
`#content_type` attribute of the input file, if present. However, since
|
33
|
+
`#content_type` value comes from the `Content-Type` header of the upload
|
34
|
+
request, it's *not guaranteed* to hold the actual MIME type of the file (browser
|
35
|
+
determines this header based on file extension). Moreover, only
|
36
|
+
`ActionDispatch::Http::UploadedFile` and `Shrine::Plugins::RackFile::UploadedFile`
|
37
|
+
objects have `#content_type` defined, so when uploading simple file objects
|
38
|
+
`mime_type` will be nil. That makes relying on `#content_type` both a security
|
39
|
+
risk and limiting.
|
40
|
+
|
41
|
+
To remedy that, Shrine comes with a `determine_mime_type` plugin which is able
|
42
|
+
to extract the MIME type from IO *content*. When you load it, the `mime_type`
|
43
|
+
plugin will now be determined using the UNIX [`file`] command.
|
44
|
+
|
45
|
+
```rb
|
46
|
+
Shrine.plugin :determine_mime_type
|
47
|
+
```
|
48
|
+
```rb
|
49
|
+
uploaded_file = uploader.upload StringIO.new("<?php ... ?>")
|
50
|
+
uploaded_file.mime_type #=> "text/x-php"
|
51
|
+
```
|
52
|
+
|
53
|
+
The `file` command won't correctly determine the MIME type in all cases, that's
|
54
|
+
why the `determine_mime_type` plugin comes with different MIME type analyzers.
|
55
|
+
So, instead of the `file` command you can use gems like [MimeMagic] or
|
56
|
+
[Marcel], as well as mix-and-match the analyzers to suit your needs. See the
|
57
|
+
plugin documentation for more details.
|
58
|
+
|
59
|
+
## Image Dimensions
|
60
|
+
|
61
|
+
Shrine comes with a `store_dimensions` plugin for extracting image dimensions.
|
62
|
+
It adds `width` and `height` metadata values, and also adds `#width`,
|
63
|
+
`#height`, and `#dimensions` methods to the `Shrine::UploadedFile` object. By
|
64
|
+
default, the plugin uses [FastImage] to analyze dimensions, but you can also
|
65
|
+
have it use [MiniMagick] or [ruby-vips]:
|
66
|
+
|
67
|
+
```rb
|
68
|
+
Shrine.plugin :store_dimensions, analyzer: :mini_magick
|
69
|
+
```
|
70
|
+
```rb
|
71
|
+
uploaded_file = uploader.upload(image)
|
72
|
+
uploaded_file.metadata["width"] #=> 1600
|
73
|
+
uploaded_file.metadata["height"] #=> 900
|
74
|
+
|
75
|
+
# convenience methods
|
76
|
+
uploaded_file.width #=> 1600
|
77
|
+
uploaded_file.height #=> 900
|
78
|
+
uploaded_file.dimensions #=> [1600, 900]
|
79
|
+
```
|
80
|
+
|
81
|
+
## Custom metadata
|
82
|
+
|
83
|
+
In addition to the built-in metadata, Shrine allows you to extract and store
|
84
|
+
any custom metadata, using the `add_metadata` plugin (which extends
|
85
|
+
`Shrine#extract_metadata`). For example, you might want to extract EXIF data
|
86
|
+
from images:
|
87
|
+
|
88
|
+
```rb
|
89
|
+
require "mini_magick"
|
90
|
+
|
91
|
+
class ImageUploader < Shrine
|
92
|
+
plugin :add_metadata
|
93
|
+
|
94
|
+
add_metadata :exif do |io|
|
95
|
+
Shrine.with_file(io) do |file|
|
96
|
+
begin
|
97
|
+
MiniMagick::Image.new(file.path).exif
|
98
|
+
rescue MiniMagick::Error
|
99
|
+
# not a valid image
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
```rb
|
106
|
+
uploaded_file = uploader.upload(image)
|
107
|
+
uploaded_file.metadata["exif"] #=> {...}
|
108
|
+
uploaded_file.exif #=> {...}
|
109
|
+
```
|
110
|
+
|
111
|
+
Of, if you're uploading videos, you might want to extract some video-specific
|
112
|
+
meatadata:
|
113
|
+
|
114
|
+
```rb
|
115
|
+
require "streamio-ffmpeg"
|
116
|
+
|
117
|
+
class VideoUploader < Shrine
|
118
|
+
plugin :add_metadata
|
119
|
+
|
120
|
+
add_metadata do |io, context|
|
121
|
+
movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) }
|
122
|
+
|
123
|
+
{ "duration" => movie.duration,
|
124
|
+
"bitrate" => movie.bitrate,
|
125
|
+
"resolution" => movie.resolution,
|
126
|
+
"frame_rate" => movie.frame_rate }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
```rb
|
131
|
+
uploaded_file = uploader.upload(video)
|
132
|
+
uploaded_file.metadata #=>
|
133
|
+
# {
|
134
|
+
# ...
|
135
|
+
# "duration" => 7.5,
|
136
|
+
# "bitrate" => 481,
|
137
|
+
# "resolution" => "640x480",
|
138
|
+
# "frame_rate" => 16.72
|
139
|
+
# }
|
140
|
+
```
|
141
|
+
|
142
|
+
The yielded `io` object will not always be an object that responds to `#path`.
|
143
|
+
If you're using the `data_uri` plugin, the `io` will be a `StringIO` wrapper.
|
144
|
+
When the `restore_cached_data` plugin is loaded, any assigned cached file will
|
145
|
+
get their metadata extracted, and `io` will be a `Shrine::UploadedFile` object.
|
146
|
+
If you're using a metadata analyzer that requires the source file to be on
|
147
|
+
disk, you can use `Shrine.with_file` to ensure you have a file object.
|
148
|
+
|
149
|
+
Also, be aware that metadata is extracted before file validation, so you'll
|
150
|
+
need to handle the cases where the file is not of expected type.
|
151
|
+
|
152
|
+
## Metadata columns
|
153
|
+
|
154
|
+
If you want to write any of the metadata values into a separate database column
|
155
|
+
on the record, you can use the `metadata_attributes` plugin.
|
156
|
+
|
157
|
+
```rb
|
158
|
+
Shrine.plugin :metadata_attributes, :mime_type => :type
|
159
|
+
```
|
160
|
+
```rb
|
161
|
+
photo = Photo.new(image: file)
|
162
|
+
photo.image_type #=> "image/jpeg"
|
163
|
+
```
|
164
|
+
|
165
|
+
## Refreshing metadata
|
166
|
+
|
167
|
+
When uploading directly to the cloud, the metadata of the original file by
|
168
|
+
default won't get extracted on the server side, because your application never
|
169
|
+
received the file content.
|
170
|
+
|
171
|
+
To have Shrine extra metadata when a cached file is assigned to the attachment
|
172
|
+
attribute, it's recommended to load the `restore_cached_data` plugin.
|
173
|
+
|
174
|
+
```rb
|
175
|
+
Shrine.plugin :restore_cached_data # extract metadata from cached files on assingment
|
176
|
+
```
|
177
|
+
```rb
|
178
|
+
photo.image = '{"id":"ks9elsd.jpg","storage":"cache","metadata":{}}' # metadata is extracted
|
179
|
+
photo.image.metadata #=>
|
180
|
+
# {
|
181
|
+
# "size" => 4593484,
|
182
|
+
# "filename" => "nature.jpg",
|
183
|
+
# "mime_type" => "image/jpeg"
|
184
|
+
# }
|
185
|
+
```
|
186
|
+
|
187
|
+
Extracting metadata from a cached file requires retrieving file content from
|
188
|
+
the storage, which might not be desirable depending on your case, that's why
|
189
|
+
`restore_cached_data` plugin is not loaded by default. However, Shrine will not
|
190
|
+
download the whole file from the storage, instead, it will open a connection to
|
191
|
+
the storage, and the metadata analyzers will download how much of the file they
|
192
|
+
need. Most MIME type analyzers and the FastImage dimensions analyzer need only
|
193
|
+
the first few kilobytes.
|
194
|
+
|
195
|
+
You can also extract metadata from an uploaded file explicitly using the
|
196
|
+
`refresh_metadata` plugin (which the `restore_cached_data` plugin uses
|
197
|
+
internally).
|
198
|
+
|
199
|
+
```rb
|
200
|
+
Shrine.plugin :refresh_metadata
|
201
|
+
```
|
202
|
+
```rb
|
203
|
+
uploaded_file.metadata #=> {}
|
204
|
+
uploaded_file.refresh_metadata!
|
205
|
+
uploaded_file.metadata #=> {"filename"=>"nature.jpg","size"=>532894,"mime_type"=>"image/jpeg"}
|
206
|
+
```
|
207
|
+
|
208
|
+
[`file`]: http://linux.die.net/man/1/file
|
209
|
+
[MimeMagic]: https://github.com/minad/mimemagic
|
210
|
+
[Marcel]: https://github.com/basecamp/marcel
|
211
|
+
[FastImage]: https://github.com/sdsykes/fastimage
|
212
|
+
[MiniMagick]: https://github.com/minimagick/minimagick
|
213
|
+
[ruby-vips]: https://github.com/jcupitt/ruby-vips
|