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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -0
- data/README.md +20 -16
- data/doc/creating_storages.md +0 -21
- data/doc/design.md +1 -0
- data/doc/direct_s3.md +26 -15
- data/doc/metadata.md +67 -22
- data/doc/multiple_files.md +3 -3
- data/doc/processing.md +1 -1
- data/doc/retrieving_uploads.md +184 -0
- data/lib/shrine.rb +268 -900
- data/lib/shrine/attacher.rb +271 -0
- data/lib/shrine/attachment.rb +97 -0
- data/lib/shrine/plugins.rb +29 -0
- data/lib/shrine/plugins/_urlsafe_serialization.rb +182 -0
- data/lib/shrine/plugins/activerecord.rb +16 -14
- data/lib/shrine/plugins/add_metadata.rb +58 -24
- data/lib/shrine/plugins/backgrounding.rb +6 -1
- data/lib/shrine/plugins/cached_attachment_data.rb +9 -9
- data/lib/shrine/plugins/copy.rb +12 -8
- data/lib/shrine/plugins/data_uri.rb +23 -20
- data/lib/shrine/plugins/default_url_options.rb +5 -4
- data/lib/shrine/plugins/determine_mime_type.rb +24 -23
- data/lib/shrine/plugins/download_endpoint.rb +61 -73
- data/lib/shrine/plugins/migration_helpers.rb +17 -17
- data/lib/shrine/plugins/module_include.rb +9 -8
- data/lib/shrine/plugins/presign_endpoint.rb +13 -7
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/rack_response.rb +128 -36
- data/lib/shrine/plugins/refresh_metadata.rb +20 -5
- data/lib/shrine/plugins/remote_url.rb +8 -8
- data/lib/shrine/plugins/remove_attachment.rb +9 -9
- data/lib/shrine/plugins/sequel.rb +21 -18
- data/lib/shrine/plugins/tempfile.rb +68 -0
- data/lib/shrine/plugins/upload_endpoint.rb +3 -2
- data/lib/shrine/plugins/upload_options.rb +7 -6
- data/lib/shrine/plugins/validation_helpers.rb +2 -1
- data/lib/shrine/storage/file_system.rb +20 -17
- data/lib/shrine/storage/linter.rb +0 -7
- data/lib/shrine/storage/s3.rb +159 -50
- data/lib/shrine/uploaded_file.rb +258 -0
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +7 -19
- 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
|
data/lib/shrine/version.rb
CHANGED
data/shrine.gemspec
CHANGED
@@ -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.
|
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",
|
40
|
+
gem.add_development_dependency "rack", "~> 2.0"
|
40
41
|
gem.add_development_dependency "mimemagic", ">= 0.3.2"
|
41
|
-
gem.add_development_dependency "marcel"
|
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
|
-
|
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.
|
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
|
+
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:
|
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:
|
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.
|
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
|
-
|
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
|