shrine 2.13.0 → 2.14.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.

Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +20 -16
  4. data/doc/creating_storages.md +0 -21
  5. data/doc/design.md +1 -0
  6. data/doc/direct_s3.md +26 -15
  7. data/doc/metadata.md +67 -22
  8. data/doc/multiple_files.md +3 -3
  9. data/doc/processing.md +1 -1
  10. data/doc/retrieving_uploads.md +184 -0
  11. data/lib/shrine.rb +268 -900
  12. data/lib/shrine/attacher.rb +271 -0
  13. data/lib/shrine/attachment.rb +97 -0
  14. data/lib/shrine/plugins.rb +29 -0
  15. data/lib/shrine/plugins/_urlsafe_serialization.rb +182 -0
  16. data/lib/shrine/plugins/activerecord.rb +16 -14
  17. data/lib/shrine/plugins/add_metadata.rb +58 -24
  18. data/lib/shrine/plugins/backgrounding.rb +6 -1
  19. data/lib/shrine/plugins/cached_attachment_data.rb +9 -9
  20. data/lib/shrine/plugins/copy.rb +12 -8
  21. data/lib/shrine/plugins/data_uri.rb +23 -20
  22. data/lib/shrine/plugins/default_url_options.rb +5 -4
  23. data/lib/shrine/plugins/determine_mime_type.rb +24 -23
  24. data/lib/shrine/plugins/download_endpoint.rb +61 -73
  25. data/lib/shrine/plugins/migration_helpers.rb +17 -17
  26. data/lib/shrine/plugins/module_include.rb +9 -8
  27. data/lib/shrine/plugins/presign_endpoint.rb +13 -7
  28. data/lib/shrine/plugins/processing.rb +1 -1
  29. data/lib/shrine/plugins/rack_response.rb +128 -36
  30. data/lib/shrine/plugins/refresh_metadata.rb +20 -5
  31. data/lib/shrine/plugins/remote_url.rb +8 -8
  32. data/lib/shrine/plugins/remove_attachment.rb +9 -9
  33. data/lib/shrine/plugins/sequel.rb +21 -18
  34. data/lib/shrine/plugins/tempfile.rb +68 -0
  35. data/lib/shrine/plugins/upload_endpoint.rb +3 -2
  36. data/lib/shrine/plugins/upload_options.rb +7 -6
  37. data/lib/shrine/plugins/validation_helpers.rb +2 -1
  38. data/lib/shrine/storage/file_system.rb +20 -17
  39. data/lib/shrine/storage/linter.rb +0 -7
  40. data/lib/shrine/storage/s3.rb +159 -50
  41. data/lib/shrine/uploaded_file.rb +258 -0
  42. data/lib/shrine/version.rb +1 -1
  43. data/shrine.gemspec +7 -19
  44. metadata +41 -21
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "tempfile"
5
+ require "uri"
6
+
7
+ class Shrine
8
+ # Core class that represents a file uploaded to a storage.
9
+ # Base implementation is defined in InstanceMethods and ClassMethods.
10
+ class UploadedFile
11
+ @shrine_class = ::Shrine
12
+
13
+ module ClassMethods
14
+ # Returns the Shrine class that this file class is namespaced under.
15
+ attr_accessor :shrine_class
16
+
17
+ # Since UploadedFile is anonymously subclassed when Shrine is subclassed,
18
+ # and then assigned to a constant of the Shrine subclass, make inspect
19
+ # reflect the likely name for the class.
20
+ def inspect
21
+ "#{shrine_class.inspect}::UploadedFile"
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+ # The hash of information which defines this uploaded file.
27
+ attr_reader :data
28
+
29
+ # Initializes the uploaded file with the given data hash.
30
+ def initialize(data)
31
+ raise Error, "#{data.inspect} isn't valid uploaded file data" unless data["id"] && data["storage"]
32
+
33
+ @data = data
34
+ @data["metadata"] ||= {}
35
+ storage # ensure storage is registered
36
+ end
37
+
38
+ # The location where the file was uploaded to the storage.
39
+ def id
40
+ @data.fetch("id")
41
+ end
42
+
43
+ # The string identifier of the storage the file is uploaded to.
44
+ def storage_key
45
+ @data.fetch("storage")
46
+ end
47
+
48
+ # A hash of file metadata that was extracted during upload.
49
+ def metadata
50
+ @data.fetch("metadata")
51
+ end
52
+
53
+ # The filename that was extracted from the uploaded file.
54
+ def original_filename
55
+ metadata["filename"]
56
+ end
57
+
58
+ # The extension derived from #id if present, otherwise it's derived
59
+ # from #original_filename.
60
+ def extension
61
+ result = File.extname(id)[1..-1] || File.extname(original_filename.to_s)[1..-1]
62
+ result.sub!(/\?.+$/, "") if result && id =~ URI::regexp # strip query params for shrine-url
63
+ result.downcase if result
64
+ end
65
+
66
+ # The filesize of the uploaded file.
67
+ def size
68
+ (@io && @io.size) || (metadata["size"] && Integer(metadata["size"]))
69
+ end
70
+
71
+ # The MIME type of the uploaded file.
72
+ def mime_type
73
+ metadata["mime_type"]
74
+ end
75
+ alias content_type mime_type
76
+
77
+ # Calls `#open` on the storage to open the uploaded file for reading.
78
+ # Most storages will return a lazy IO object which dynamically
79
+ # retrieves file content from the storage as the object is being read.
80
+ #
81
+ # If a block is given, the opened IO object is yielded to the block,
82
+ # and at the end of the block it's automatically closed. In this case
83
+ # the return value of the method is the block return value.
84
+ #
85
+ # If no block is given, the opened IO object is returned.
86
+ #
87
+ # uploaded_file.open #=> IO object returned by the storage
88
+ # uploaded_file.read #=> "..."
89
+ # uploaded_file.close
90
+ #
91
+ # # or
92
+ #
93
+ # uploaded_file.open { |io| io.read } # the IO is automatically closed
94
+ def open(*args)
95
+ @io.close if @io
96
+ @io = storage.open(id, *args)
97
+
98
+ return @io unless block_given?
99
+
100
+ begin
101
+ yield @io
102
+ ensure
103
+ close
104
+ @io = nil
105
+ end
106
+ end
107
+
108
+ # Streams content into a newly created Tempfile and returns it.
109
+ #
110
+ # If a block is given, the opened Tempfile object is yielded to the
111
+ # block, and at the end of the block it's automatically closed and
112
+ # deleted. In this case the return value of the method is the block
113
+ # return value.
114
+ #
115
+ # If no block is given, the opened Tempfile is returned.
116
+ #
117
+ # uploaded_file.download
118
+ # #=> #<File:/var/folders/.../20180302-33119-1h1vjbq.jpg>
119
+ #
120
+ # # or
121
+ #
122
+ # uploaded_file.download { |tempfile| tempfile.read } # tempfile is deleted
123
+ def download(*args)
124
+ tempfile = Tempfile.new(["shrine", ".#{extension}"], binmode: true)
125
+ stream(tempfile, *args)
126
+ tempfile.open
127
+
128
+ block_given? ? yield(tempfile) : tempfile
129
+ ensure
130
+ tempfile.close! if ($! || block_given?) && tempfile
131
+ end
132
+
133
+ # Streams uploaded file content into the specified destination. The
134
+ # destination object is given directly to `IO.copy_stream`, so it can
135
+ # be either a path on disk or an object that responds to `#write`.
136
+ #
137
+ # If the uploaded file is already opened, it will be simply rewinded
138
+ # after streaming finishes. Otherwise the uploaded file is opened and
139
+ # then closed after streaming.
140
+ #
141
+ # uploaded_file.stream(StringIO.new)
142
+ # # or
143
+ # uploaded_file.stream("/path/to/destination")
144
+ def stream(destination, *args)
145
+ if opened?
146
+ IO.copy_stream(io, destination)
147
+ io.rewind
148
+ else
149
+ open(*args) { |io| IO.copy_stream(io, destination) }
150
+ end
151
+ end
152
+
153
+ # Part of complying to the IO interface. It delegates to the internally
154
+ # opened IO object.
155
+ def read(*args)
156
+ io.read(*args)
157
+ end
158
+
159
+ # Part of complying to the IO interface. It delegates to the internally
160
+ # opened IO object.
161
+ def eof?
162
+ io.eof?
163
+ end
164
+
165
+ # Part of complying to the IO interface. It delegates to the internally
166
+ # opened IO object.
167
+ def rewind
168
+ io.rewind
169
+ end
170
+
171
+ # Part of complying to the IO interface. It delegates to the internally
172
+ # opened IO object.
173
+ def close
174
+ io.close if opened?
175
+ end
176
+
177
+ # Returns whether the file has already been opened.
178
+ def opened?
179
+ !!@io
180
+ end
181
+
182
+ # Calls `#url` on the storage, forwarding any given URL options.
183
+ def url(**options)
184
+ storage.url(id, **options)
185
+ end
186
+
187
+ # Calls `#exists?` on the storage, which checks whether the file exists
188
+ # on the storage.
189
+ def exists?
190
+ storage.exists?(id)
191
+ end
192
+
193
+ # Uploads a new file to this file's location and returns it.
194
+ def replace(io, context = {})
195
+ uploader.upload(io, context.merge(location: id))
196
+ end
197
+
198
+ # Calls `#delete` on the storage, which deletes the file from the
199
+ # storage.
200
+ def delete
201
+ storage.delete(id)
202
+ end
203
+
204
+ # Returns an opened IO object for the uploaded file.
205
+ def to_io
206
+ io
207
+ end
208
+
209
+ # Returns the data hash in the JSON format. Suitable for storing in a
210
+ # database column or passing to a background job.
211
+ def to_json(*args)
212
+ data.to_json(*args)
213
+ end
214
+
215
+ # Conform to ActiveSupport's JSON interface.
216
+ def as_json(*args)
217
+ data
218
+ end
219
+
220
+ # Returns true if the other UploadedFile is uploaded to the same
221
+ # storage and it has the same #id.
222
+ def ==(other)
223
+ other.is_a?(self.class) &&
224
+ self.id == other.id &&
225
+ self.storage_key == other.storage_key
226
+ end
227
+ alias eql? ==
228
+
229
+ # Enables using UploadedFile objects as hash keys.
230
+ def hash
231
+ [id, storage_key].hash
232
+ end
233
+
234
+ # Returns an uploader object for the corresponding storage.
235
+ def uploader
236
+ shrine_class.new(storage_key)
237
+ end
238
+
239
+ # Returns the storage that this file was uploaded to.
240
+ def storage
241
+ shrine_class.find_storage(storage_key)
242
+ end
243
+
244
+ # Returns the Shrine class that this file's class is namespaced under.
245
+ def shrine_class
246
+ self.class.shrine_class
247
+ end
248
+
249
+ private
250
+
251
+ # Returns an opened IO object for the uploaded file by calling `#open`
252
+ # on the storage.
253
+ def io
254
+ @io || open
255
+ end
256
+ end
257
+ end
258
+ end
@@ -7,7 +7,7 @@ class Shrine
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 2
10
- MINOR = 13
10
+ MINOR = 14
11
11
  TINY = 0
12
12
  PRE = nil
13
13
 
@@ -4,7 +4,7 @@ Gem::Specification.new do |gem|
4
4
  gem.name = "shrine"
5
5
  gem.version = Shrine.version
6
6
 
7
- gem.required_ruby_version = ">= 2.1"
7
+ gem.required_ruby_version = ">= 2.3"
8
8
 
9
9
  gem.summary = "Toolkit for file attachments in Ruby applications"
10
10
  gem.description = <<-END
@@ -27,6 +27,7 @@ direct uploads for fully asynchronous user experience.
27
27
  gem.require_path = "lib"
28
28
 
29
29
  gem.add_dependency "down", "~> 4.1"
30
+ gem.add_dependency "content_disposition", "~> 1.0"
30
31
 
31
32
  gem.add_development_dependency "rake", ">= 11.1"
32
33
  gem.add_development_dependency "minitest", "~> 5.8"
@@ -36,11 +37,12 @@ direct uploads for fully asynchronous user experience.
36
37
  gem.add_development_dependency "shrine-memory", ">= 0.2.2"
37
38
 
38
39
  gem.add_development_dependency "roda"
39
- gem.add_development_dependency "rack", (RUBY_VERSION >= "2.2.2" ? "~> 2.0" : "~> 1.6")
40
+ gem.add_development_dependency "rack", "~> 2.0"
40
41
  gem.add_development_dependency "mimemagic", ">= 0.3.2"
41
- gem.add_development_dependency "marcel" if RUBY_VERSION >= "2.2.0"
42
+ gem.add_development_dependency "marcel"
42
43
  gem.add_development_dependency "mime-types"
43
44
  gem.add_development_dependency "mini_mime", "~> 1.0"
45
+ gem.add_development_dependency "ruby-filemagic", "~> 0.7" unless RUBY_ENGINE == "jruby" || ENV["CI"]
44
46
  gem.add_development_dependency "fastimage"
45
47
  gem.add_development_dependency "mini_magick", "~> 4.0" unless ENV["CI"]
46
48
  gem.add_development_dependency "ruby-vips", "~> 2.0" unless ENV["CI"]
@@ -48,21 +50,7 @@ direct uploads for fully asynchronous user experience.
48
50
  gem.add_development_dependency "aws-sdk-core", "~> 3.23"
49
51
  gem.add_development_dependency "http-form_data", "~> 2.0"
50
52
 
51
- unless RUBY_ENGINE == "jruby" || ENV["CI"]
52
- gem.add_development_dependency "ruby-filemagic", "~> 0.7"
53
- end
54
-
55
53
  gem.add_development_dependency "sequel"
56
-
57
- if RUBY_VERSION >= "2.2.2"
58
- gem.add_development_dependency "activerecord", "~> 5.0"
59
- else
60
- gem.add_development_dependency "activerecord", "~> 4.2"
61
- end
62
-
63
- if RUBY_ENGINE == "jruby"
64
- gem.add_development_dependency "activerecord-jdbcsqlite3-adapter", "51"
65
- else
66
- gem.add_development_dependency "sqlite3"
67
- end
54
+ gem.add_development_dependency "activerecord", "~> 5.2.0"
55
+ gem.add_development_dependency "sqlite3" unless RUBY_ENGINE == "jruby"
68
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shrine
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0
4
+ version: 2.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-04 00:00:00.000000000 Z
11
+ date: 2018-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: content_disposition
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +206,20 @@ dependencies:
192
206
  - - "~>"
193
207
  - !ruby/object:Gem::Version
194
208
  version: '1.0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: ruby-filemagic
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '0.7'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '0.7'
195
223
  - !ruby/object:Gem::Dependency
196
224
  name: fastimage
197
225
  requirement: !ruby/object:Gem::Requirement
@@ -276,20 +304,6 @@ dependencies:
276
304
  - - "~>"
277
305
  - !ruby/object:Gem::Version
278
306
  version: '2.0'
279
- - !ruby/object:Gem::Dependency
280
- name: ruby-filemagic
281
- requirement: !ruby/object:Gem::Requirement
282
- requirements:
283
- - - "~>"
284
- - !ruby/object:Gem::Version
285
- version: '0.7'
286
- type: :development
287
- prerelease: false
288
- version_requirements: !ruby/object:Gem::Requirement
289
- requirements:
290
- - - "~>"
291
- - !ruby/object:Gem::Version
292
- version: '0.7'
293
307
  - !ruby/object:Gem::Dependency
294
308
  name: sequel
295
309
  requirement: !ruby/object:Gem::Requirement
@@ -310,14 +324,14 @@ dependencies:
310
324
  requirements:
311
325
  - - "~>"
312
326
  - !ruby/object:Gem::Version
313
- version: '5.0'
327
+ version: 5.2.0
314
328
  type: :development
315
329
  prerelease: false
316
330
  version_requirements: !ruby/object:Gem::Requirement
317
331
  requirements:
318
332
  - - "~>"
319
333
  - !ruby/object:Gem::Version
320
- version: '5.0'
334
+ version: 5.2.0
321
335
  - !ruby/object:Gem::Dependency
322
336
  name: sqlite3
323
337
  requirement: !ruby/object:Gem::Requirement
@@ -365,10 +379,15 @@ files:
365
379
  - doc/processing.md
366
380
  - doc/refile.md
367
381
  - doc/regenerating_versions.md
382
+ - doc/retrieving_uploads.md
368
383
  - doc/securing_uploads.md
369
384
  - doc/testing.md
370
385
  - doc/validation.md
371
386
  - lib/shrine.rb
387
+ - lib/shrine/attacher.rb
388
+ - lib/shrine/attachment.rb
389
+ - lib/shrine/plugins.rb
390
+ - lib/shrine/plugins/_urlsafe_serialization.rb
372
391
  - lib/shrine/plugins/activerecord.rb
373
392
  - lib/shrine/plugins/add_metadata.rb
374
393
  - lib/shrine/plugins/background_helpers.rb
@@ -412,6 +431,7 @@ files:
412
431
  - lib/shrine/plugins/sequel.rb
413
432
  - lib/shrine/plugins/signature.rb
414
433
  - lib/shrine/plugins/store_dimensions.rb
434
+ - lib/shrine/plugins/tempfile.rb
415
435
  - lib/shrine/plugins/upload_endpoint.rb
416
436
  - lib/shrine/plugins/upload_options.rb
417
437
  - lib/shrine/plugins/validation_helpers.rb
@@ -419,6 +439,7 @@ files:
419
439
  - lib/shrine/storage/file_system.rb
420
440
  - lib/shrine/storage/linter.rb
421
441
  - lib/shrine/storage/s3.rb
442
+ - lib/shrine/uploaded_file.rb
422
443
  - lib/shrine/version.rb
423
444
  - shrine.gemspec
424
445
  homepage: https://github.com/shrinerb/shrine
@@ -433,15 +454,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
433
454
  requirements:
434
455
  - - ">="
435
456
  - !ruby/object:Gem::Version
436
- version: '2.1'
457
+ version: '2.3'
437
458
  required_rubygems_version: !ruby/object:Gem::Requirement
438
459
  requirements:
439
460
  - - ">="
440
461
  - !ruby/object:Gem::Version
441
462
  version: '0'
442
463
  requirements: []
443
- rubyforge_project:
444
- rubygems_version: 2.7.6
464
+ rubygems_version: 3.0.1
445
465
  signing_key:
446
466
  specification_version: 4
447
467
  summary: Toolkit for file attachments in Ruby applications