shrine 2.10.1 → 2.11.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.
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/refile.md
CHANGED
@@ -427,10 +427,10 @@ No equivalent in Shrine, but take a look at the "[Multiple Files]" guide.
|
|
427
427
|
|
428
428
|
The following Refile code
|
429
429
|
|
430
|
-
```
|
431
|
-
|
432
|
-
|
433
|
-
|
430
|
+
```rb
|
431
|
+
form_for @user do |form|
|
432
|
+
form.attachment_field :profile_image
|
433
|
+
end
|
434
434
|
```
|
435
435
|
|
436
436
|
is equivalent to the following Shrine code
|
@@ -438,11 +438,11 @@ is equivalent to the following Shrine code
|
|
438
438
|
```rb
|
439
439
|
Shrine.plugin :cached_attachment_data
|
440
440
|
```
|
441
|
-
```
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
441
|
+
```rb
|
442
|
+
form_for @user do |form|
|
443
|
+
form.hidden_field :profile_image, value: @user.cached_profile_image_data
|
444
|
+
form.file_field :profile_image
|
445
|
+
end
|
446
446
|
```
|
447
447
|
|
448
448
|
### Model methods
|
@@ -455,12 +455,12 @@ Shrine comes with a `remove_attachment` plugin which adds the same
|
|
455
455
|
```rb
|
456
456
|
Shrine.plugin :remove_attachment
|
457
457
|
```
|
458
|
-
```
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
458
|
+
```rb
|
459
|
+
form_for @user do |form|
|
460
|
+
form.hidden_field :profile_image, value: @user.cached_profile_image_data
|
461
|
+
form.file_field :profile_image
|
462
|
+
form.check_box :remove_profile_image
|
463
|
+
end
|
464
464
|
```
|
465
465
|
|
466
466
|
#### `remote_<attachment>_url`
|
@@ -471,12 +471,12 @@ Shrine comes with a `remote_url` plugin which adds the same
|
|
471
471
|
```rb
|
472
472
|
Shrine.plugin :remote_url
|
473
473
|
```
|
474
|
-
```
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
474
|
+
```rb
|
475
|
+
form_for @user do |form|
|
476
|
+
form.hidden_field :profile_image, value: @user.cached_profile_image_data
|
477
|
+
form.file_field :profile_image
|
478
|
+
form.text_field :profile_image_remote_url
|
479
|
+
end
|
480
480
|
```
|
481
481
|
|
482
482
|
[shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
|
@@ -485,6 +485,6 @@ Shrine.plugin :remote_url
|
|
485
485
|
[Attache]: https://github.com/choonkeat/attache
|
486
486
|
[image_processing]: https://github.com/janko-m/image_processing
|
487
487
|
[Uppy]: https://uppy.io
|
488
|
-
[Direct Uploads to S3]:
|
488
|
+
[Direct Uploads to S3]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
|
489
489
|
[demo app]: https://github.com/shrinerb/shrine/tree/master/demo
|
490
|
-
[Multiple Files]:
|
490
|
+
[Multiple Files]: https://shrinerb.com/rdoc/files/doc/multiple_files_md.html
|
data/doc/testing.md
CHANGED
@@ -36,16 +36,12 @@ end
|
|
36
36
|
|
37
37
|
## Storage
|
38
38
|
|
39
|
-
If you're using
|
40
|
-
switch to
|
41
|
-
clean up
|
42
|
-
the performance of your tests.
|
43
|
-
|
44
|
-
If your tests are run in a single process, instead of filesystem you can use
|
45
|
-
[memory storage][shrine-memory], which is both faster and doesn't require you
|
46
|
-
to clean up anything between tests.
|
39
|
+
If you're using FileSystem storage and your tests run in a single process,
|
40
|
+
you can switch to [memory storage][shrine-memory], which is both faster and
|
41
|
+
doesn't require you to clean up anything between tests.
|
47
42
|
|
48
43
|
```rb
|
44
|
+
# Gemfile
|
49
45
|
gem "shrine-memory"
|
50
46
|
```
|
51
47
|
```rb
|
@@ -58,8 +54,49 @@ Shrine.storages = {
|
|
58
54
|
}
|
59
55
|
```
|
60
56
|
|
61
|
-
|
62
|
-
|
57
|
+
If you're using AWS S3 storage, in you can switch to using [Minio] (explained
|
58
|
+
below), both in test and development environment. Alternatively, you can [stub
|
59
|
+
aws-sdk-s3 requests][aws-sdk-ruby stubs] in tests.
|
60
|
+
|
61
|
+
### Minio
|
62
|
+
|
63
|
+
[Minio] is an open source object storage server with AWS S3 compatible API which
|
64
|
+
you can run locally. The advantage of using Minio for your development and test
|
65
|
+
environments is that all AWS S3 functionality should still continue to work,
|
66
|
+
including direct uploads, so you don't need to update your code.
|
67
|
+
|
68
|
+
If you're on a Mac you can install it with Homebrew:
|
69
|
+
|
70
|
+
```
|
71
|
+
$ brew install minio/stable/minio
|
72
|
+
```
|
73
|
+
|
74
|
+
Afterwards you can start the Minio server and give it a directory where it will
|
75
|
+
store the data:
|
76
|
+
|
77
|
+
```
|
78
|
+
$ minio server data/
|
79
|
+
```
|
80
|
+
|
81
|
+
This command will print out the credentials for the running Minio server, as
|
82
|
+
well as a link to the Minio web interface. Follow that link and create a new
|
83
|
+
bucket. Once you've done that, you can configure `Shrine::Storage::S3` to use
|
84
|
+
your Minio server:
|
85
|
+
|
86
|
+
```rb
|
87
|
+
Shrine::Storage::S3.new(
|
88
|
+
access_key_id: "<MINIO_ACCESS_KEY>", # "AccessKey" value
|
89
|
+
secret_access_key: "<MINIO_SECRET_KEY>", # "SecretKey" value
|
90
|
+
endpoint: "<MINIO_ENDPOINT>", # "Endpoint" value
|
91
|
+
bucket: "<MINIO_BUCKET>", # name of the bucket you created
|
92
|
+
region: "us-east-1",
|
93
|
+
force_path_style: true,
|
94
|
+
)
|
95
|
+
```
|
96
|
+
|
97
|
+
The `:endpoint` option will make aws-sdk-s3 point all URLs to your Minio server
|
98
|
+
(instead of `s3.amazonaws.com`), and `:force_path_style` tells it not to use
|
99
|
+
subdomains when generating URLs.
|
63
100
|
|
64
101
|
## Test data
|
65
102
|
|
@@ -240,47 +277,6 @@ end
|
|
240
277
|
This also has the benefit of allowing you to test `ImageThumbnailsGenerator` in
|
241
278
|
isolation.
|
242
279
|
|
243
|
-
## Direct S3 uploads
|
244
|
-
|
245
|
-
If you want to do direct uploads to Amazon S3 in production, in development and
|
246
|
-
tests you'll probably want to keep using filesystem storage to avoid making
|
247
|
-
network requests.
|
248
|
-
|
249
|
-
The simplest way to do that is to use [Minio]. Minio is an open source object
|
250
|
-
storage server with Amazon S3 compatible API. If you're on a Mac you can
|
251
|
-
install it with Homebrew:
|
252
|
-
|
253
|
-
```
|
254
|
-
$ brew install minio/stable/minio
|
255
|
-
```
|
256
|
-
|
257
|
-
Now you can start the Minio server and give it a directory where it will store
|
258
|
-
the data:
|
259
|
-
|
260
|
-
```
|
261
|
-
$ minio server data/
|
262
|
-
```
|
263
|
-
|
264
|
-
This command will print out the credentials for the running Minio server, as
|
265
|
-
well as a link to the Minio web interface. Follow that link and create a new
|
266
|
-
bucket. Once you've done that, all that's lef to do is configure
|
267
|
-
`Shrine::Storage::S3` with the credentials of your Minio server:
|
268
|
-
|
269
|
-
```rb
|
270
|
-
Shrine::Storage::S3.new(
|
271
|
-
access_key_id: "MINIO_ACCESS_KEY", # "AccessKey" value
|
272
|
-
secret_access_key: "MINIO_SECRET_KEY", # "SecretKey" value
|
273
|
-
endpoint: "MINIO_ENDPOINT", # "Endpoint" value
|
274
|
-
bucket: "MINIO_BUCKET", # name of the bucket you created
|
275
|
-
region: "us-east-1",
|
276
|
-
force_path_style: true,
|
277
|
-
)
|
278
|
-
```
|
279
|
-
|
280
|
-
The `:endpoint` option will make `aws-sdk-s3` point all URLs to your Minio
|
281
|
-
server (instead of `s3.amazonaws.com`), and `:force_path_style` tells it not
|
282
|
-
to use subdomains when generating URLs.
|
283
|
-
|
284
280
|
[DatabaseCleaner]: https://github.com/DatabaseCleaner/database_cleaner
|
285
281
|
[shrine-memory]: https://github.com/shrinerb/shrine-memory
|
286
282
|
[factory_bot]: https://github.com/thoughtbot/factory_bot
|
data/doc/validation.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# File Validation
|
2
|
+
|
3
|
+
Shrine allows validating assigned files based on their metadata. Validation
|
4
|
+
code is defined inside a `Shrine::Attacher.validate` block:
|
5
|
+
|
6
|
+
```rb
|
7
|
+
class ImageUploader < Shrine
|
8
|
+
Attacher.validate do
|
9
|
+
# validations
|
10
|
+
end
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
The validation block is run when a file is assigned to an attachment attribute,
|
15
|
+
afterwards the validation errors are stored in `Shrine::Attacher#errors`. ORM
|
16
|
+
plugins like `sequel` and `activerecord` will automatically merge these
|
17
|
+
validation errors into the `#errors` hash on the model instance.
|
18
|
+
|
19
|
+
```rb
|
20
|
+
photo = Photo.new
|
21
|
+
photo.image = image_file
|
22
|
+
photo.valid? #=> false
|
23
|
+
photo.errors[:image] #=> [...]
|
24
|
+
```
|
25
|
+
|
26
|
+
By default the invalid file will remain assigned to the attachment attribute,
|
27
|
+
but you can have it automatically removed and deleted by loading the
|
28
|
+
`remove_invalid` plugin.
|
29
|
+
|
30
|
+
```rb
|
31
|
+
Shrine.plugin :remove_invalid # remove and delete files that failed validation
|
32
|
+
```
|
33
|
+
|
34
|
+
The validation block is evaluated in the context of a `Shrine::Attacher`
|
35
|
+
instance, so you have access to the original file and the record:
|
36
|
+
|
37
|
+
```rb
|
38
|
+
class ImageUploader < Shrine
|
39
|
+
Attacher.validate do
|
40
|
+
self #=> #<Shrine::Attacher>
|
41
|
+
|
42
|
+
get #=> #<Shrine::UploadedFile>
|
43
|
+
record #=> #<Photo>
|
44
|
+
name #=> :image
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
You can use the attacher context to pass additional parameters you want to use
|
50
|
+
for validation:
|
51
|
+
|
52
|
+
```rb
|
53
|
+
photo.image_attacher.context[:foo] = "bar"
|
54
|
+
```
|
55
|
+
```rb
|
56
|
+
class ImageUploader < Shrine
|
57
|
+
Attacher.validate do
|
58
|
+
context[:foo] #=> "bar"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
## Validation helpers
|
64
|
+
|
65
|
+
The `validation_helpers` plugin provides helper methods for validating common
|
66
|
+
metadata values:
|
67
|
+
|
68
|
+
```rb
|
69
|
+
class ImageUploader < Shrine
|
70
|
+
plugin :validation_helpers
|
71
|
+
|
72
|
+
Attacher.validate do
|
73
|
+
validate_min_size 1, message: "must not be empty"
|
74
|
+
validate_max_size 5*1024*1024, message: "is too large (max is 5 MB)"
|
75
|
+
validate_mime_type_inclusion %w[image/jpeg image/png image/tiff]
|
76
|
+
validate_extension_inclusion %w[jpg jpeg png tiff tif]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
Note that for secure MIME type validation it's recommended to also load
|
82
|
+
`determine_mime_type` and `restore_cached_data` plugins.
|
83
|
+
|
84
|
+
It's also easy to do conditional validations with these helper methods:
|
85
|
+
|
86
|
+
```rb
|
87
|
+
class ImageUploader < Shrine
|
88
|
+
plugin :validation_helpers
|
89
|
+
|
90
|
+
Attacher.validate do
|
91
|
+
# validate dimensions only of the attached file is an image
|
92
|
+
if validate_extension_inclusion %w[jpg jpeg png tiff tif]
|
93
|
+
validate_max_width 5000
|
94
|
+
validate_max_height 5000
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
See the `validation_helpers` plugin documentation for more details.
|
101
|
+
|
102
|
+
## Custom validations
|
103
|
+
|
104
|
+
You might sometimes want to validate custom metadata, or in general do custom
|
105
|
+
validation that the `validation_helpers` plugin does not provide. The
|
106
|
+
`Shrine::Attacher.validate` block is evaluated at instance level, so you're
|
107
|
+
free to write there any code you like and add validation errors onto the
|
108
|
+
`Shrine::Attacher#errors` array.
|
109
|
+
|
110
|
+
For example, if you're uploading images, you might want to validate that the
|
111
|
+
image is processable using the [ImageProcessing] gem:
|
112
|
+
|
113
|
+
```rb
|
114
|
+
require "image_processing/mini_magick"
|
115
|
+
|
116
|
+
class ImageUploader < Shrine
|
117
|
+
plugin :validation_helpers
|
118
|
+
|
119
|
+
Attacher.validate do
|
120
|
+
# validate dimensions only of the attached file is an image
|
121
|
+
if validate_mime_type_inclusion %w[image/jpeg image/png image/tiff]
|
122
|
+
get.download do |tempfile|
|
123
|
+
errors << "is corrupted or invalid" unless ImageProcessing::MiniMagick.valid_image?(tempfile)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
## Inheritance
|
131
|
+
|
132
|
+
Validations are inherited from superclasses, but you need to call them manually
|
133
|
+
when defining more validations:
|
134
|
+
|
135
|
+
```rb
|
136
|
+
class ApplicationUploader < Shrine
|
137
|
+
Attacher.validate { validate_max_size 5.megabytes }
|
138
|
+
end
|
139
|
+
|
140
|
+
class ImageUploader < ApplicationUploader
|
141
|
+
Attacher.validate do
|
142
|
+
super() # empty braces are required
|
143
|
+
validate_mime_type_inclusion %w[image/jpeg image/jpg image/png]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
[ImageProcessing]: https://github.com/janko-m/image_processing
|
data/lib/shrine.rb
CHANGED
@@ -170,6 +170,27 @@ class Shrine
|
|
170
170
|
end
|
171
171
|
end
|
172
172
|
|
173
|
+
# Temporarily converts an IO-like object into a file. If the input IO
|
174
|
+
# object is already a file, it simply yields it to the block, otherwise
|
175
|
+
# it copies IO content into a Tempfile object which is then yielded and
|
176
|
+
# afterwards deleted.
|
177
|
+
#
|
178
|
+
# Shrine.with_file(io) { |file| file.path }
|
179
|
+
def with_file(io)
|
180
|
+
if io.respond_to?(:path)
|
181
|
+
yield io
|
182
|
+
elsif io.is_a?(UploadedFile)
|
183
|
+
io.download { |tempfile| yield tempfile }
|
184
|
+
else
|
185
|
+
Tempfile.create("shrine-file", binmode: true) do |file|
|
186
|
+
IO.copy_stream(io, file.path)
|
187
|
+
io.rewind
|
188
|
+
|
189
|
+
yield file
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
173
194
|
# Prints a deprecation warning to standard error.
|
174
195
|
def deprecation(message)
|
175
196
|
warn "SHRINE DEPRECATION WARNING: #{message}"
|
@@ -751,8 +772,8 @@ class Shrine
|
|
751
772
|
metadata["filename"]
|
752
773
|
end
|
753
774
|
|
754
|
-
# The extension derived from #id if present, otherwise
|
755
|
-
# #original_filename.
|
775
|
+
# The extension derived from #id if present, otherwise it's derived
|
776
|
+
# from #original_filename.
|
756
777
|
def extension
|
757
778
|
result = File.extname(id)[1..-1] || File.extname(original_filename.to_s)[1..-1]
|
758
779
|
result.downcase if result
|
@@ -800,7 +821,7 @@ class Shrine
|
|
800
821
|
end
|
801
822
|
|
802
823
|
# Calls `#download` on the storage if the storage implements it,
|
803
|
-
# otherwise
|
824
|
+
# otherwise streams content into a newly created Tempfile.
|
804
825
|
#
|
805
826
|
# If a block is given, the opened Tempfile object is yielded to the
|
806
827
|
# block, and at the end of the block it's automatically closed and
|
@@ -821,7 +842,7 @@ class Shrine
|
|
821
842
|
tempfile = storage.download(id, *args)
|
822
843
|
else
|
823
844
|
tempfile = Tempfile.new(["shrine", ".#{extension}"], binmode: true)
|
824
|
-
|
845
|
+
stream(tempfile, *args)
|
825
846
|
tempfile.open
|
826
847
|
end
|
827
848
|
|
@@ -830,6 +851,26 @@ class Shrine
|
|
830
851
|
tempfile.close! if ($! || block_given?) && tempfile
|
831
852
|
end
|
832
853
|
|
854
|
+
# Streams uploaded file content into the specified destination. The
|
855
|
+
# destination object is given directly to `IO.copy_stream`, so it can
|
856
|
+
# be either a path on disk or an object that responds to `#write`.
|
857
|
+
#
|
858
|
+
# If the uploaded file is already opened, it will be simply rewinded
|
859
|
+
# after streaming finishes. Otherwise the uploaded file is opened and
|
860
|
+
# then closed after streaming.
|
861
|
+
#
|
862
|
+
# uploaded_file.stream(StringIO.new)
|
863
|
+
# # or
|
864
|
+
# uploaded_file.stream("/path/to/destination")
|
865
|
+
def stream(destination, *args)
|
866
|
+
if @io
|
867
|
+
IO.copy_stream(io, destination)
|
868
|
+
io.rewind
|
869
|
+
else
|
870
|
+
open(*args) { |io| IO.copy_stream(io, destination) }
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
833
874
|
# Part of complying to the IO interface. It delegates to the internally
|
834
875
|
# opened IO object.
|
835
876
|
def read(*args)
|
@@ -7,34 +7,55 @@ class Shrine
|
|
7
7
|
#
|
8
8
|
# plugin :add_metadata
|
9
9
|
#
|
10
|
-
# add_metadata :
|
11
|
-
#
|
10
|
+
# add_metadata :exif do |io, context|
|
11
|
+
# begin
|
12
|
+
# Exif::Data.new(io).to_h
|
13
|
+
# rescue Exif::NotReadable # not a valid image
|
14
|
+
# {}
|
15
|
+
# end
|
12
16
|
# end
|
13
17
|
#
|
14
|
-
# The above will add "
|
15
|
-
# `#
|
18
|
+
# The above will add "exif" to the metadata hash, and also create the
|
19
|
+
# `#exif` reader method on Shrine::UploadedFile.
|
16
20
|
#
|
17
|
-
#
|
21
|
+
# image.metadata["exif"]
|
18
22
|
# # or
|
19
|
-
#
|
23
|
+
# image.exif
|
20
24
|
#
|
21
25
|
# You can also extract multiple metadata values at once, by using
|
22
|
-
# `add_metadata` without an argument
|
26
|
+
# `add_metadata` without an argument and returning a hash of metadata.
|
23
27
|
#
|
24
28
|
# add_metadata do |io, context|
|
25
|
-
#
|
29
|
+
# begin
|
30
|
+
# data = Exif::Data.new(io)
|
31
|
+
# rescue Exif::NotReadable # not a valid image
|
32
|
+
# next {}
|
33
|
+
# end
|
26
34
|
#
|
27
|
-
# {
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
35
|
+
# { date_time: data.date_time,
|
36
|
+
# flash: data.flash,
|
37
|
+
# focal_length: data.focal_length,
|
38
|
+
# exposure_time: data.exposure_time }
|
31
39
|
# end
|
32
40
|
#
|
33
41
|
# In this case Shrine won't automatically create reader methods for the
|
34
42
|
# extracted metadata on Shrine::UploadedFile, but you can create them via
|
35
|
-
#
|
43
|
+
# `#metadata_method`.
|
36
44
|
#
|
37
|
-
# metadata_method :
|
45
|
+
# metadata_method :date_time, :flash
|
46
|
+
#
|
47
|
+
# The `io` might not always be a file object, so if you're using an
|
48
|
+
# analyzer which requires the source file to be on disk, you can use
|
49
|
+
# `Shrine.with_file` to ensure you have a file object.
|
50
|
+
#
|
51
|
+
# add_metadata do |io, context|
|
52
|
+
# movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) }
|
53
|
+
#
|
54
|
+
# { "duration" => movie.duration,
|
55
|
+
# "bitrate" => movie.bitrate,
|
56
|
+
# "resolution" => movie.resolution,
|
57
|
+
# "frame_rate" => movie.frame_rate }
|
58
|
+
# end
|
38
59
|
module AddMetadata
|
39
60
|
def self.configure(uploader)
|
40
61
|
uploader.opts[:metadata] = []
|