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.

@@ -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
- ```erb
431
- <%= form_for @user do |form| %>
432
- <%= form.attachment_field :profile_image %>
433
- <% end %>
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
- ```erb
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 %>
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
- ```erb
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 %>
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
- ```erb
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 %>
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]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
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]: http://shrinerb.com/rdoc/files/doc/multiple_files_md.html
490
+ [Multiple Files]: https://shrinerb.com/rdoc/files/doc/multiple_files_md.html
@@ -36,16 +36,12 @@ end
36
36
 
37
37
  ## Storage
38
38
 
39
- If you're using an external storage in development, it is common in tests to
40
- switch to a filesystem storage. However, that means that you'll also have to
41
- clean up the test directory between tests, and writing to filesystem can affect
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
- Alternatively, if you're using Amazon S3 storage, in tests you can use
62
- [aws-sdk-ruby stubs].
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
@@ -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
@@ -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 from
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 uses #open to stream the underlying IO to a Tempfile.
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
- open(*args) { |io| IO.copy_stream(io, tempfile) }
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 :pages do |io, context|
11
- # PDF::Reader.new(io.path).page_count
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 "pages" to the metadata hash, and also create the
15
- # `#pages` reader method on Shrine::UploadedFile.
18
+ # The above will add "exif" to the metadata hash, and also create the
19
+ # `#exif` reader method on Shrine::UploadedFile.
16
20
  #
17
- # document.metadata["pages"]
21
+ # image.metadata["exif"]
18
22
  # # or
19
- # document.pages
23
+ # image.exif
20
24
  #
21
25
  # You can also extract multiple metadata values at once, by using
22
- # `add_metadata` without an argument, and returning a hash of metadata.
26
+ # `add_metadata` without an argument and returning a hash of metadata.
23
27
  #
24
28
  # add_metadata do |io, context|
25
- # movie = FFMPEG::Movie.new(io.path)
29
+ # begin
30
+ # data = Exif::Data.new(io)
31
+ # rescue Exif::NotReadable # not a valid image
32
+ # next {}
33
+ # end
26
34
  #
27
- # { "duration" => movie.duration,
28
- # "bitrate" => movie.bitrate,
29
- # "resolution" => movie.resolution,
30
- # "frame_rate" => movie.frame_rate }
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
- # `metadata_method`.
43
+ # `#metadata_method`.
36
44
  #
37
- # metadata_method :duration, :bitrate, :resolution, :frame_rate
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] = []