shrine 2.5.0 → 2.6.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/README.md +14 -13
- data/doc/attacher.md +7 -6
- data/doc/carrierwave.md +19 -17
- data/doc/design.md +1 -1
- data/doc/direct_s3.md +8 -5
- data/doc/multiple_files.md +4 -4
- data/doc/paperclip.md +7 -6
- data/doc/refile.md +67 -4
- data/doc/securing_uploads.md +41 -25
- data/doc/testing.md +6 -15
- data/lib/shrine.rb +19 -10
- data/lib/shrine/plugins/activerecord.rb +4 -4
- data/lib/shrine/plugins/add_metadata.rb +7 -3
- data/lib/shrine/plugins/background_helpers.rb +1 -1
- data/lib/shrine/plugins/backgrounding.rb +19 -6
- data/lib/shrine/plugins/cached_attachment_data.rb +4 -4
- data/lib/shrine/plugins/data_uri.rb +105 -31
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/delete_raw.rb +7 -3
- data/lib/shrine/plugins/determine_mime_type.rb +96 -44
- data/lib/shrine/plugins/direct_upload.rb +3 -1
- data/lib/shrine/plugins/download_endpoint.rb +14 -5
- data/lib/shrine/plugins/logging.rb +4 -4
- data/lib/shrine/plugins/metadata_attributes.rb +61 -0
- data/lib/shrine/plugins/migration_helpers.rb +1 -1
- data/lib/shrine/plugins/rack_file.rb +54 -30
- data/lib/shrine/plugins/recache.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +29 -0
- data/lib/shrine/plugins/remote_url.rb +26 -4
- data/lib/shrine/plugins/remove_invalid.rb +5 -4
- data/lib/shrine/plugins/restore_cached_data.rb +10 -13
- data/lib/shrine/plugins/sequel.rb +4 -4
- data/lib/shrine/plugins/signature.rb +146 -0
- data/lib/shrine/plugins/store_dimensions.rb +68 -24
- data/lib/shrine/plugins/validation_helpers.rb +48 -29
- data/lib/shrine/plugins/versions.rb +16 -8
- data/lib/shrine/storage/file_system.rb +27 -16
- data/lib/shrine/storage/s3.rb +99 -58
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +1 -1
- metadata +9 -6
@@ -29,7 +29,7 @@ class Shrine
|
|
29
29
|
def self.configure(uploader, &block)
|
30
30
|
if block
|
31
31
|
uploader.opts[:default_url] = block
|
32
|
-
|
32
|
+
Shrine.deprecation("Passing a block to default_url plugin is deprecated and will probably be removed in future versions of Shrine. Use `Attacher.default_url { ... }` instead.")
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -21,12 +21,16 @@ class Shrine
|
|
21
21
|
# Deletes the file that was uploaded, unless it's an UploadedFile.
|
22
22
|
def copy(io, context)
|
23
23
|
super
|
24
|
-
if io.respond_to?(:
|
25
|
-
|
24
|
+
if io.respond_to?(:path) && io.path && delete_raw?
|
25
|
+
begin
|
26
|
+
File.delete(io.path)
|
27
|
+
rescue Errno::ENOENT
|
28
|
+
# file might already be deleted by the moving plugin
|
29
|
+
end
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
33
|
+
def delete_raw?
|
30
34
|
opts[:delete_raw_storages].nil? ||
|
31
35
|
opts[:delete_raw_storages].include?(storage_key)
|
32
36
|
end
|
@@ -1,16 +1,17 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The `determine_mime_type` plugin
|
4
|
-
#
|
3
|
+
# The `determine_mime_type` plugin allows you to determine and store the
|
4
|
+
# actual MIME type of the file analyzed from file content.
|
5
5
|
#
|
6
6
|
# plugin :determine_mime_type
|
7
7
|
#
|
8
|
-
# By default the UNIX [file] utility is used to determine the MIME type,
|
9
|
-
#
|
8
|
+
# By default the UNIX [file] utility is used to determine the MIME type,
|
9
|
+
# and the result is automatically written to the `mime_type` metadata
|
10
|
+
# field. You can choose a different built-in MIME type analyzer:
|
10
11
|
#
|
11
12
|
# plugin :determine_mime_type, analyzer: :filemagic
|
12
13
|
#
|
13
|
-
# The
|
14
|
+
# The following analyzers are accepted:
|
14
15
|
#
|
15
16
|
# :file
|
16
17
|
# : (Default). Uses the [file] utility to determine the MIME type from file
|
@@ -33,17 +34,32 @@ class Shrine
|
|
33
34
|
# guaranteed to return the actual MIME type of the file.
|
34
35
|
#
|
35
36
|
# :default
|
36
|
-
# : Uses the default way of extracting the MIME type, and that is
|
37
|
-
#
|
38
|
-
# of the file.
|
37
|
+
# : Uses the default way of extracting the MIME type, and that is reading
|
38
|
+
# the `#content_type` attribute of the IO object, which might not hold
|
39
|
+
# the actual MIME type of the file.
|
39
40
|
#
|
40
|
-
#
|
41
|
-
# can build your own analyzer
|
41
|
+
# A single analyzer is not going to properly recognize all types of files,
|
42
|
+
# so you can build your own custom analyzer for your requirements, where
|
43
|
+
# you can combine the built-in analyzers. For example, if you want to
|
44
|
+
# correctly determine MIME type of .css, .js, .json, .csv, .xml, or similar
|
45
|
+
# text-based files, you can combine `file` and `mime_types` analyzers:
|
42
46
|
#
|
43
47
|
# plugin :determine_mime_type, analyzer: ->(io, analyzers) do
|
44
|
-
#
|
48
|
+
# mime_type = analyzers[:file].call(io)
|
49
|
+
# mime_type = analyzers[:mime_types].call(io) if mime_type == "text/plain"
|
50
|
+
# mime_type
|
45
51
|
# end
|
46
52
|
#
|
53
|
+
# You can also use methods for determining the MIME type directly:
|
54
|
+
#
|
55
|
+
# # or YourUploader.determine_mime_type(io)
|
56
|
+
# Shrine.determine_mime_type(io) # calls the defined analyzer
|
57
|
+
# #=> "image/jpeg"
|
58
|
+
#
|
59
|
+
# # or YourUploader.mime_type_analyzers
|
60
|
+
# Shrine.mime_type_analyzers[:file].call(io) # calls a built-in analyzer
|
61
|
+
# #=> "image/jpeg"
|
62
|
+
#
|
47
63
|
# [file]: http://linux.die.net/man/1/file
|
48
64
|
# [Windows equivalent]: http://gnuwin32.sourceforge.net/packages/file.htm
|
49
65
|
# [ruby-filemagic]: https://github.com/blackwinter/ruby-filemagic
|
@@ -52,24 +68,15 @@ class Shrine
|
|
52
68
|
module DetermineMimeType
|
53
69
|
def self.configure(uploader, opts = {})
|
54
70
|
uploader.opts[:mime_type_analyzer] = opts.fetch(:analyzer, uploader.opts.fetch(:mime_type_analyzer, :file))
|
55
|
-
uploader.opts[:mime_type_magic_header] = opts.fetch(:magic_header, uploader.opts.fetch(:mime_type_magic_header, MAGIC_NUMBER))
|
56
71
|
end
|
57
72
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
# If a Shrine::UploadedFile was given, it returns its MIME type, since
|
65
|
-
# that value was already determined by this analyzer. Otherwise it calls
|
66
|
-
# a built-in analyzer or a custom one.
|
67
|
-
def extract_mime_type(io)
|
73
|
+
module ClassMethods
|
74
|
+
# Determines the MIME type of the IO object by calling the specified
|
75
|
+
# analyzer.
|
76
|
+
def determine_mime_type(io)
|
68
77
|
analyzer = opts[:mime_type_analyzer]
|
69
|
-
return super if analyzer == :default
|
70
|
-
|
71
78
|
analyzer = mime_type_analyzers[analyzer] if analyzer.is_a?(Symbol)
|
72
|
-
args
|
79
|
+
args = [io, mime_type_analyzers].take(analyzer.arity.abs)
|
73
80
|
|
74
81
|
mime_type = analyzer.call(*args)
|
75
82
|
io.rewind
|
@@ -77,15 +84,59 @@ class Shrine
|
|
77
84
|
mime_type
|
78
85
|
end
|
79
86
|
|
87
|
+
# Returns a hash of built-in MIME type analyzers, where keys are
|
88
|
+
# analyzer names and values are `#call`-able objects which accepts the
|
89
|
+
# IO object.
|
80
90
|
def mime_type_analyzers
|
81
|
-
|
91
|
+
@mime_type_analyzers ||= MimeTypeAnalyzer::SUPPORTED_TOOLS.inject({}) do |hash, tool|
|
92
|
+
hash.merge!(tool => MimeTypeAnalyzer.new(tool).method(:call))
|
93
|
+
end
|
82
94
|
end
|
95
|
+
end
|
83
96
|
|
84
|
-
|
97
|
+
module InstanceMethods
|
98
|
+
private
|
99
|
+
|
100
|
+
# Calls default behaviour when :default analyzer was specified, which
|
101
|
+
# just reads the `#content_type` attribute, otherwise uses the specified
|
102
|
+
# MIME type analyzer.
|
103
|
+
def extract_mime_type(io)
|
104
|
+
if opts[:mime_type_analyzer] == :default
|
105
|
+
super
|
106
|
+
else
|
107
|
+
self.class.determine_mime_type(io)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns a hash of built-in MIME type analyzers.
|
112
|
+
def mime_type_analyzers
|
113
|
+
self.class.mime_type_analyzers
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class MimeTypeAnalyzer
|
118
|
+
SUPPORTED_TOOLS = [:file, :filemagic, :mimemagic, :mime_types]
|
119
|
+
MAGIC_NUMBER = 256 * 1024
|
120
|
+
|
121
|
+
def initialize(tool)
|
122
|
+
raise ArgumentError, "unsupported mime type analyzer tool: #{tool}" unless SUPPORTED_TOOLS.include?(tool)
|
123
|
+
|
124
|
+
@tool = tool
|
125
|
+
end
|
126
|
+
|
127
|
+
def call(io)
|
128
|
+
mime_type = send(:"extract_with_#{@tool}", io)
|
129
|
+
io.rewind
|
130
|
+
mime_type
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def extract_with_file(io)
|
85
136
|
require "open3"
|
86
137
|
|
87
138
|
cmd = ["file", "--mime-type", "--brief", "-"]
|
88
|
-
options = {stdin_data:
|
139
|
+
options = {stdin_data: io.read(MAGIC_NUMBER), binmode: true}
|
89
140
|
|
90
141
|
begin
|
91
142
|
stdout, stderr, status = Open3.capture3(*cmd, options)
|
@@ -99,26 +150,24 @@ class Shrine
|
|
99
150
|
stdout.strip
|
100
151
|
end
|
101
152
|
|
102
|
-
def
|
103
|
-
require "mimemagic"
|
104
|
-
|
105
|
-
mime = MimeMagic.by_magic(io)
|
106
|
-
io.rewind
|
107
|
-
|
108
|
-
mime.type if mime
|
109
|
-
end
|
110
|
-
|
111
|
-
def _extract_mime_type_with_filemagic(io)
|
153
|
+
def extract_with_filemagic(io)
|
112
154
|
require "filemagic"
|
113
155
|
|
114
156
|
filemagic = FileMagic.new(FileMagic::MAGIC_MIME_TYPE)
|
115
|
-
mime_type = filemagic.buffer(
|
157
|
+
mime_type = filemagic.buffer(io.read(MAGIC_NUMBER))
|
116
158
|
filemagic.close
|
117
159
|
|
118
160
|
mime_type
|
119
161
|
end
|
120
162
|
|
121
|
-
def
|
163
|
+
def extract_with_mimemagic(io)
|
164
|
+
require "mimemagic"
|
165
|
+
|
166
|
+
mime = MimeMagic.by_magic(io)
|
167
|
+
mime.type if mime
|
168
|
+
end
|
169
|
+
|
170
|
+
def extract_with_mime_types(io)
|
122
171
|
begin
|
123
172
|
require "mime/types/columnar"
|
124
173
|
rescue LoadError
|
@@ -131,12 +180,15 @@ class Shrine
|
|
131
180
|
end
|
132
181
|
end
|
133
182
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
137
|
-
|
183
|
+
def extract_filename(io)
|
184
|
+
if io.respond_to?(:original_filename)
|
185
|
+
io.original_filename
|
186
|
+
elsif io.respond_to?(:path)
|
187
|
+
File.basename(io.path)
|
188
|
+
end
|
138
189
|
end
|
139
190
|
end
|
191
|
+
|
140
192
|
end
|
141
193
|
|
142
194
|
register_plugin(:determine_mime_type, DetermineMimeType)
|
@@ -99,10 +99,12 @@ class Shrine
|
|
99
99
|
#
|
100
100
|
# plugin :direct_upload, presign_options: ->(request) do
|
101
101
|
# filename = request.params["filename"]
|
102
|
+
# content_type = Rack::Mime.mime_type(File.extname(filename))
|
102
103
|
#
|
103
104
|
# {
|
104
105
|
# content_length_range: 0..(10*1024*1024), # limit filesize to 10MB
|
105
106
|
# content_disposition: "attachment; filename=\"#{filename}\"", # download with original filename
|
107
|
+
# content_type: content_type, # set correct content type
|
106
108
|
# }
|
107
109
|
# end
|
108
110
|
#
|
@@ -230,7 +232,7 @@ class Shrine
|
|
230
232
|
context = {action: :cache, phase: :cache}
|
231
233
|
|
232
234
|
if name != "upload"
|
233
|
-
|
235
|
+
Shrine.deprecation("The \"POST /:storage/:name\" route of the direct_upload plugin is deprecated, and it will be removed in Shrine 3. Use \"POST /:storage/upload\" instead.")
|
234
236
|
context[:name] = name
|
235
237
|
end
|
236
238
|
|
@@ -41,13 +41,22 @@ class Shrine
|
|
41
41
|
# prompted to download the file when visiting the download URL.
|
42
42
|
# The default is "inline".
|
43
43
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
44
|
+
# Note that streaming the file through your app might impact the request
|
45
|
+
# throughput of your app, because on most popular web servers (Puma,
|
46
|
+
# Unicorn, Passenger) workers handling this endpoint will not be able to
|
47
|
+
# serve new requests until the client has fully downloaded the response
|
48
|
+
# body.
|
49
|
+
#
|
50
|
+
# To prevent download endpoint from impacting your request throughput, use
|
51
|
+
# a web server that handles streaming responses and slow clients well, like
|
52
|
+
# [Thin], [Rainbows] or any other [EventMachine]-based web server that
|
53
|
+
# implements `async.callback`.
|
49
54
|
#
|
50
55
|
# [Roda]: https://github.com/jeremyevans/roda
|
56
|
+
# [Thin]: https://github.com/macournoyer/thin
|
57
|
+
# [Rainbows]: https://rubygems.org/gems/rainbows
|
58
|
+
# [Reel]: https://github.com/celluloid/reel
|
59
|
+
# [EventMachine]: https://github.com/eventmachine
|
51
60
|
module DownloadEndpoint
|
52
61
|
def self.configure(uploader, opts = {})
|
53
62
|
uploader.opts[:download_endpoint_storages] = opts.fetch(:storages, uploader.opts[:download_endpoint_storages])
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "logger"
|
2
|
-
require "benchmark"
|
3
2
|
require "json"
|
4
3
|
|
5
4
|
class Shrine
|
@@ -157,9 +156,10 @@ class Shrine
|
|
157
156
|
end
|
158
157
|
|
159
158
|
def benchmark
|
160
|
-
|
161
|
-
|
162
|
-
|
159
|
+
start = Time.now
|
160
|
+
result = yield
|
161
|
+
finish = Time.now
|
162
|
+
[result, finish - start]
|
163
163
|
end
|
164
164
|
end
|
165
165
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Shrine
|
2
|
+
module Plugins
|
3
|
+
# The `metadata_attributes` plugin allows you to sync attachment metadata
|
4
|
+
# to additional record attributes.
|
5
|
+
#
|
6
|
+
# plugin :metadata_values
|
7
|
+
#
|
8
|
+
# It provides `Attacher.metadata_attributes` method which allows you to
|
9
|
+
# specify mappings between metadata fields on the attachment and attribute
|
10
|
+
# names on the record.
|
11
|
+
#
|
12
|
+
# Attacher.metadata_attributes :size => :size, :mime_type => :type
|
13
|
+
#
|
14
|
+
# The above configuration will sync `size` metadata field to
|
15
|
+
# `<attachment>_size` record attribute, and `mime_type` metadata field to
|
16
|
+
# `<attachment>_type` record attribute.
|
17
|
+
#
|
18
|
+
# user.avatar = image
|
19
|
+
# user.avatar.metadata["size"] #=> 95724
|
20
|
+
# user.avatar_size #=> 95724
|
21
|
+
# user.avatar.metadata["mime_type"] #=> "image/jpeg"
|
22
|
+
# user.avatar_type #=> "image/jpeg"
|
23
|
+
#
|
24
|
+
# user.avatar = nil
|
25
|
+
# user.avatar_size #=> nil
|
26
|
+
# user.avatar_type #=> nil
|
27
|
+
#
|
28
|
+
# If any corresponding metadata attribute doesn't exist on the record, that
|
29
|
+
# metadata sync will be silently skipped.
|
30
|
+
module MetadataAttributes
|
31
|
+
def self.configure(uploader)
|
32
|
+
uploader.opts[:metadata_attributes_mappings] ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
module AttacherClassMethods
|
36
|
+
def metadata_attributes(mappings)
|
37
|
+
shrine_class.opts[:metadata_attributes_mappings].merge!(mappings)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module AttacherMethods
|
42
|
+
def assign(value)
|
43
|
+
super
|
44
|
+
cached_file = get
|
45
|
+
|
46
|
+
shrine_class.opts[:metadata_attributes_mappings].each do |source, destination|
|
47
|
+
next unless record.respond_to?(:"#{name}_#{destination}=")
|
48
|
+
|
49
|
+
if cached_file
|
50
|
+
record.send(:"#{name}_#{destination}=", cached_file.metadata[source.to_s])
|
51
|
+
else
|
52
|
+
record.send(:"#{name}_#{destination}=", nil)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
register_plugin(:metadata_attributes, MetadataAttributes)
|
60
|
+
end
|
61
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
Shrine.deprecation("The migration_helpers plugin is deprecated and will be removed in Shrine 3. Attacher#cached? and Attacher#stored? have been moved to base.")
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
@@ -11,37 +11,44 @@ class Shrine
|
|
11
11
|
# `multipart/form-data` parameter encoding, Rack converts the uploaded file
|
12
12
|
# to a hash.
|
13
13
|
#
|
14
|
-
#
|
14
|
+
# file_hash #=>
|
15
15
|
# # {
|
16
|
-
# # name
|
17
|
-
# # filename
|
18
|
-
# # type
|
19
|
-
# # tempfile
|
20
|
-
# # head
|
16
|
+
# # :name => "file",
|
17
|
+
# # :filename => "cats.png",
|
18
|
+
# # :type => "image/png",
|
19
|
+
# # :tempfile => #<Tempfile:/var/folders/3n/3asd/-Tmp-/RackMultipart201-1476-nfw2-0>,
|
20
|
+
# # :head => "Content-Disposition: form-data; ...",
|
21
21
|
# # }
|
22
22
|
#
|
23
23
|
# Since Shrine only accepts IO objects, you would normally need to fetch
|
24
24
|
# the `:tempfile` object and pass it directly. This plugin enables the
|
25
|
-
#
|
26
|
-
#
|
25
|
+
# attacher to accept the Rack uploaded file hash directly, which is
|
26
|
+
# convenient when doing mass attribute assignment.
|
27
27
|
#
|
28
|
-
#
|
28
|
+
# user.avatar = file_hash
|
29
29
|
# # or
|
30
|
-
# attacher.assign(
|
31
|
-
# # or
|
32
|
-
# user.avatar = params[:file]
|
30
|
+
# attacher.assign(file_hash)
|
33
31
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# metadata.
|
32
|
+
# Internally the Rack uploaded file hash will be converted into an IO
|
33
|
+
# object using `Shrine.rack_file`, which you can also use directly:
|
37
34
|
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
35
|
+
# # or YourUploader.rack_file(file_hash)
|
36
|
+
# io = Shrine.rack_file(file_hash)
|
37
|
+
# io.original_filename #=> "cats.png"
|
38
|
+
# io.content_type #=> "image/png"
|
39
|
+
# io.size #=> 58342
|
41
40
|
#
|
42
41
|
# Note that this plugin is not needed in Rails applications, as Rails
|
43
|
-
# already wraps Rack uploaded
|
42
|
+
# already wraps the Rack uploaded file hash into an
|
43
|
+
# `ActionDispatch::Http::UploadedFile` object.
|
44
44
|
module RackFile
|
45
|
+
module ClassMethods
|
46
|
+
# Accepts a Rack uploaded file hash and wraps it in an IO object.
|
47
|
+
def rack_file(hash)
|
48
|
+
UploadedFile.new(hash)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
45
52
|
module InstanceMethods
|
46
53
|
# If `io` is a Rack uploaded file hash, converts it to an IO-like
|
47
54
|
# object and calls `super`.
|
@@ -62,7 +69,8 @@ class Shrine
|
|
62
69
|
# hash, otherwise returns the value unchanged.
|
63
70
|
def convert_rack_file(value)
|
64
71
|
if rack_file?(value)
|
65
|
-
|
72
|
+
Shrine.deprecation("Passing a Rack uploaded file hash to Shrine#upload is deprecated, use Shrine.rack_file to convert the Rack file hash into an IO object.")
|
73
|
+
self.class.rack_file(value)
|
66
74
|
else
|
67
75
|
value
|
68
76
|
end
|
@@ -75,26 +83,42 @@ class Shrine
|
|
75
83
|
end
|
76
84
|
end
|
77
85
|
|
86
|
+
module AttacherMethods
|
87
|
+
# Checks whether a file is a Rack file hash, and in that case wraps the
|
88
|
+
# hash in an IO-like object.
|
89
|
+
def assign(value)
|
90
|
+
if rack_file?(value)
|
91
|
+
assign(shrine_class.rack_file(value))
|
92
|
+
else
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Returns whether a given value is a Rack uploaded file hash, by
|
100
|
+
# checking whether it's a hash with `:tempfile` and `:name` keys.
|
101
|
+
def rack_file?(value)
|
102
|
+
value.is_a?(Hash) && value.key?(:tempfile) && value.key?(:name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
78
106
|
# This is used to wrap the Rack hash into an IO-like object which Shrine
|
79
107
|
# can upload.
|
80
108
|
class UploadedFile
|
81
|
-
attr_reader :original_filename, :content_type
|
82
|
-
|
109
|
+
attr_reader :tempfile, :original_filename, :content_type
|
110
|
+
alias :to_io :tempfile
|
83
111
|
|
84
|
-
def initialize(
|
85
|
-
@tempfile = tempfile
|
86
|
-
@original_filename = filename
|
87
|
-
@content_type = type
|
112
|
+
def initialize(hash)
|
113
|
+
@tempfile = hash[:tempfile]
|
114
|
+
@original_filename = hash[:filename]
|
115
|
+
@content_type = hash[:type]
|
88
116
|
end
|
89
117
|
|
90
118
|
def path
|
91
119
|
@tempfile.path
|
92
120
|
end
|
93
121
|
|
94
|
-
def to_io
|
95
|
-
@tempfile
|
96
|
-
end
|
97
|
-
|
98
122
|
extend Forwardable
|
99
123
|
delegate Shrine::IO_METHODS.keys => :@tempfile
|
100
124
|
end
|