shrine 3.0.0 → 3.2.2
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 +4 -4
- data/CHANGELOG.md +87 -33
- data/LICENSE.txt +1 -1
- data/README.md +94 -4
- data/doc/advantages.md +35 -18
- data/doc/attacher.md +16 -17
- data/doc/carrierwave.md +75 -34
- data/doc/changing_derivatives.md +39 -39
- data/doc/design.md +134 -85
- data/doc/external/articles.md +56 -41
- data/doc/external/extensions.md +38 -34
- data/doc/getting_started.md +182 -112
- data/doc/metadata.md +79 -43
- data/doc/multiple_files.md +5 -3
- data/doc/paperclip.md +110 -42
- data/doc/plugins/activerecord.md +5 -5
- data/doc/plugins/add_metadata.md +92 -35
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/column.md +36 -7
- data/doc/plugins/data_uri.md +2 -2
- data/doc/plugins/default_url.md +6 -3
- data/doc/plugins/derivation_endpoint.md +26 -28
- data/doc/plugins/derivatives.md +205 -169
- data/doc/plugins/determine_mime_type.md +2 -2
- data/doc/plugins/entity.md +3 -3
- data/doc/plugins/form_assign.md +5 -5
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/infer_extension.md +2 -2
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/metadata_attributes.md +21 -10
- data/doc/plugins/model.md +4 -4
- data/doc/plugins/persistence.md +1 -0
- data/doc/plugins/refresh_metadata.md +5 -4
- data/doc/plugins/remote_url.md +8 -3
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/sequel.md +4 -4
- data/doc/plugins/signature.md +11 -2
- data/doc/plugins/store_dimensions.md +2 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +7 -11
- data/doc/plugins/upload_options.md +1 -1
- data/doc/plugins/url_options.md +2 -2
- data/doc/plugins/validation.md +14 -4
- data/doc/plugins/validation_helpers.md +3 -3
- data/doc/plugins/versions.md +11 -11
- data/doc/processing.md +289 -125
- data/doc/refile.md +39 -18
- data/doc/release_notes/2.19.0.md +1 -1
- data/doc/release_notes/3.0.0.md +275 -258
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +32 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/securing_uploads.md +3 -3
- data/doc/storage/file_system.md +1 -1
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +105 -86
- data/doc/testing.md +2 -2
- data/doc/upgrading_to_3.md +115 -33
- data/doc/validation.md +3 -2
- data/lib/shrine.rb +8 -8
- data/lib/shrine/attacher.rb +19 -14
- data/lib/shrine/attachment.rb +5 -5
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/plugins/add_metadata.rb +12 -3
- data/lib/shrine/plugins/default_storage.rb +6 -6
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +10 -6
- data/lib/shrine/plugins/derivatives.rb +19 -17
- data/lib/shrine/plugins/determine_mime_type.rb +3 -3
- data/lib/shrine/plugins/entity.rb +6 -6
- data/lib/shrine/plugins/metadata_attributes.rb +1 -1
- data/lib/shrine/plugins/model.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +2 -2
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/signature.rb +7 -6
- data/lib/shrine/plugins/store_dimensions.rb +18 -9
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +3 -3
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/url_options.rb +2 -2
- data/lib/shrine/plugins/validation.rb +9 -7
- data/lib/shrine/storage/linter.rb +4 -4
- data/lib/shrine/storage/s3.rb +62 -38
- data/lib/shrine/uploaded_file.rb +5 -1
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +6 -7
- metadata +23 -29
@@ -21,10 +21,13 @@ class Shrine
|
|
21
21
|
module ClassMethods
|
22
22
|
# Calculates `algorithm` hash of the contents of the IO object, and
|
23
23
|
# encodes it into `format`.
|
24
|
-
def calculate_signature(io, algorithm, format: :hex)
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
def calculate_signature(io, algorithm, format: :hex, rewind: true)
|
25
|
+
calculator = SignatureCalculator.new(algorithm.downcase, format: format)
|
26
|
+
|
27
|
+
signature = instrument_signature(io, algorithm, format) { calculator.call(io) }
|
28
|
+
io.rewind if rewind
|
29
|
+
|
30
|
+
signature
|
28
31
|
end
|
29
32
|
alias signature calculate_signature
|
30
33
|
|
@@ -62,8 +65,6 @@ class Shrine
|
|
62
65
|
|
63
66
|
def call(io)
|
64
67
|
hash = send(:"calculate_#{algorithm}", io)
|
65
|
-
io.rewind
|
66
|
-
|
67
68
|
send(:"encode_#{format}", hash)
|
68
69
|
end
|
69
70
|
|
@@ -113,23 +113,32 @@ class Shrine
|
|
113
113
|
|
114
114
|
def extract_with_fastimage(io)
|
115
115
|
require "fastimage"
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
|
117
|
+
begin
|
118
|
+
FastImage.size(io, raise_on_failure: true)
|
119
|
+
rescue FastImage::FastImageException => error
|
120
|
+
on_error(error)
|
121
|
+
end
|
119
122
|
end
|
120
123
|
|
121
124
|
def extract_with_mini_magick(io)
|
122
125
|
require "mini_magick"
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
+
|
127
|
+
begin
|
128
|
+
Shrine.with_file(io) { |file| MiniMagick::Image.new(file.path).dimensions }
|
129
|
+
rescue MiniMagick::Error => error
|
130
|
+
on_error(error)
|
131
|
+
end
|
126
132
|
end
|
127
133
|
|
128
134
|
def extract_with_ruby_vips(io)
|
129
135
|
require "vips"
|
130
|
-
|
131
|
-
|
132
|
-
|
136
|
+
|
137
|
+
begin
|
138
|
+
Shrine.with_file(io) { |file| Vips::Image.new_from_file(file.path).size }
|
139
|
+
rescue Vips::Error => error
|
140
|
+
on_error(error)
|
141
|
+
end
|
133
142
|
end
|
134
143
|
|
135
144
|
def on_error(error)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shrine
|
4
|
+
module Plugins
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/type_predicates
|
6
|
+
module TypePredicates
|
7
|
+
def self.configure(uploader, methods: [], **opts)
|
8
|
+
uploader.opts[:type_predicates] ||= { mime: :mini_mime }
|
9
|
+
uploader.opts[:type_predicates].merge!(opts)
|
10
|
+
|
11
|
+
methods.each do |name|
|
12
|
+
uploader::UploadedFile.send(:define_method, "#{name}?") { type?(name) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def type_lookup(extension, database = nil)
|
18
|
+
database ||= opts[:type_predicates][:mime]
|
19
|
+
database = MimeDatabase.new(database) if database.is_a?(Symbol)
|
20
|
+
database.call(extension.to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module FileMethods
|
25
|
+
def image?
|
26
|
+
general_type?("image")
|
27
|
+
end
|
28
|
+
|
29
|
+
def video?
|
30
|
+
general_type?("video")
|
31
|
+
end
|
32
|
+
|
33
|
+
def audio?
|
34
|
+
general_type?("audio")
|
35
|
+
end
|
36
|
+
|
37
|
+
def text?
|
38
|
+
general_type?("text")
|
39
|
+
end
|
40
|
+
|
41
|
+
def type?(type)
|
42
|
+
matching_mime_type = shrine_class.type_lookup(type)
|
43
|
+
|
44
|
+
fail Error, "type #{type.inspect} is not recognized by the MIME library" unless matching_mime_type
|
45
|
+
|
46
|
+
mime_type! == matching_mime_type
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def general_type?(type)
|
52
|
+
mime_type!.start_with?(type)
|
53
|
+
end
|
54
|
+
|
55
|
+
def mime_type!
|
56
|
+
mime_type or fail Error, "mime_type metadata value is missing"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class MimeDatabase
|
61
|
+
SUPPORTED_TOOLS = %i[mini_mime mime_types mimemagic marcel rack_mime]
|
62
|
+
|
63
|
+
def initialize(tool)
|
64
|
+
raise Error, "unknown type database #{tool.inspect}, supported databases are: #{SUPPORTED_TOOLS.join(",")}" unless SUPPORTED_TOOLS.include?(tool)
|
65
|
+
|
66
|
+
@tool = tool
|
67
|
+
end
|
68
|
+
|
69
|
+
def call(extension)
|
70
|
+
send(:"lookup_with_#{@tool}", extension)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def lookup_with_mini_mime(extension)
|
76
|
+
require "mini_mime"
|
77
|
+
|
78
|
+
info = MiniMime.lookup_by_extension(extension)
|
79
|
+
info&.content_type
|
80
|
+
end
|
81
|
+
|
82
|
+
def lookup_with_mime_types(extension)
|
83
|
+
require "mime/types"
|
84
|
+
|
85
|
+
mime_type = MIME::Types.of(".#{extension}").first
|
86
|
+
mime_type&.content_type
|
87
|
+
end
|
88
|
+
|
89
|
+
def lookup_with_mimemagic(extension)
|
90
|
+
require "mimemagic"
|
91
|
+
|
92
|
+
magic = MimeMagic.by_extension(".#{extension}")
|
93
|
+
magic&.type
|
94
|
+
end
|
95
|
+
|
96
|
+
def lookup_with_marcel(extension)
|
97
|
+
require "marcel"
|
98
|
+
|
99
|
+
type = Marcel::MimeType.for(extension: ".#{extension}")
|
100
|
+
type unless type == "application/octet-stream"
|
101
|
+
end
|
102
|
+
|
103
|
+
def lookup_with_rack_mime(extension)
|
104
|
+
require "rack/mime"
|
105
|
+
|
106
|
+
Rack::Mime.mime_type(".#{extension}", nil)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
register_plugin(:type_predicates, TypePredicates)
|
112
|
+
end
|
113
|
+
end
|
@@ -9,11 +9,11 @@ class Shrine
|
|
9
9
|
module Plugins
|
10
10
|
# Documentation can be found on https://shrinerb.com/docs/plugins/upload_endpoint
|
11
11
|
module UploadEndpoint
|
12
|
-
def self.load_dependencies(uploader,
|
12
|
+
def self.load_dependencies(uploader, **)
|
13
13
|
uploader.plugin :rack_file
|
14
14
|
end
|
15
15
|
|
16
|
-
def self.configure(uploader, opts
|
16
|
+
def self.configure(uploader, **opts)
|
17
17
|
uploader.opts[:upload_endpoint] ||= {}
|
18
18
|
uploader.opts[:upload_endpoint].merge!(opts)
|
19
19
|
end
|
@@ -156,7 +156,7 @@ class Shrine
|
|
156
156
|
if @upload
|
157
157
|
@upload.call(io, context, request)
|
158
158
|
else
|
159
|
-
uploader.upload(io, context)
|
159
|
+
uploader.upload(io, **context)
|
160
160
|
end
|
161
161
|
end
|
162
162
|
|
@@ -4,9 +4,9 @@ class Shrine
|
|
4
4
|
module Plugins
|
5
5
|
# Documentation can be found on https://shrinerb.com/docs/plugins/upload_options
|
6
6
|
module UploadOptions
|
7
|
-
def self.configure(uploader,
|
7
|
+
def self.configure(uploader, **opts)
|
8
8
|
uploader.opts[:upload_options] ||= {}
|
9
|
-
uploader.opts[:upload_options].merge!(
|
9
|
+
uploader.opts[:upload_options].merge!(opts)
|
10
10
|
end
|
11
11
|
|
12
12
|
module InstanceMethods
|
@@ -4,9 +4,9 @@ class Shrine
|
|
4
4
|
module Plugins
|
5
5
|
# Documentation can be found on https://shrinerb.com/docs/plugins/url_options
|
6
6
|
module UrlOptions
|
7
|
-
def self.configure(uploader, **
|
7
|
+
def self.configure(uploader, **opts)
|
8
8
|
uploader.opts[:url_options] ||= {}
|
9
|
-
uploader.opts[:url_options].merge!(
|
9
|
+
uploader.opts[:url_options].merge!(opts)
|
10
10
|
end
|
11
11
|
|
12
12
|
module FileMethods
|
@@ -29,14 +29,16 @@ class Shrine
|
|
29
29
|
@errors = []
|
30
30
|
end
|
31
31
|
|
32
|
-
#
|
33
|
-
def
|
34
|
-
super(
|
32
|
+
# Performs validations after attaching cached file.
|
33
|
+
def attach_cached(value, validate: nil, **options)
|
34
|
+
result = super(value, validate: false, **options)
|
35
|
+
validation(validate)
|
36
|
+
result
|
35
37
|
end
|
36
38
|
|
37
|
-
# Performs validations after
|
38
|
-
def
|
39
|
-
result = super
|
39
|
+
# Performs validations after attaching file.
|
40
|
+
def attach(io, validate: nil, **options)
|
41
|
+
result = super(io, **options)
|
40
42
|
validation(validate)
|
41
43
|
result
|
42
44
|
end
|
@@ -52,7 +54,7 @@ class Shrine
|
|
52
54
|
# Calls validation appropriately based on the :validate value.
|
53
55
|
def validation(argument)
|
54
56
|
case argument
|
55
|
-
when Hash then validate(argument)
|
57
|
+
when Hash then validate(**argument)
|
56
58
|
when false then errors.clear # skip validation
|
57
59
|
else validate
|
58
60
|
end
|
@@ -35,7 +35,7 @@ class Shrine
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def call(io_factory = default_io_factory)
|
38
|
-
storage.upload(io_factory.call, id = "foo", {})
|
38
|
+
storage.upload(io_factory.call, id = "foo", shrine_metadata: { "foo" => "bar" })
|
39
39
|
|
40
40
|
lint_open(id)
|
41
41
|
lint_exists(id)
|
@@ -67,13 +67,13 @@ class Shrine
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def lint_open(id)
|
70
|
-
opened = storage.open(id
|
70
|
+
opened = storage.open(id)
|
71
71
|
error :open, "doesn't return a valid IO object" if !io?(opened)
|
72
72
|
error :open, "returns an empty IO object" if opened.read.empty?
|
73
73
|
opened.close
|
74
74
|
|
75
75
|
begin
|
76
|
-
storage.open(@nonexisting
|
76
|
+
storage.open(@nonexisting)
|
77
77
|
error :open, "should raise an exception on nonexisting file"
|
78
78
|
rescue Shrine::FileNotFound
|
79
79
|
rescue => exception
|
@@ -107,7 +107,7 @@ class Shrine
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def lint_presign(id)
|
110
|
-
data = storage.presign(id
|
110
|
+
data = storage.presign(id)
|
111
111
|
error :presign, "result should be a Hash" unless data.respond_to?(:to_h)
|
112
112
|
error :presign, "result should include :method key" unless data.to_h.key?(:method)
|
113
113
|
error :presign, "result should include :url key" unless data.to_h.key?(:url)
|
data/lib/shrine/storage/s3.rb
CHANGED
@@ -103,7 +103,7 @@ class Shrine
|
|
103
103
|
#
|
104
104
|
# [`Aws::S3::Object#get`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
|
105
105
|
def open(id, rewindable: true, **options)
|
106
|
-
chunks, length =
|
106
|
+
chunks, length = get(object(id), options)
|
107
107
|
|
108
108
|
Down::ChunkedIO.new(chunks: chunks, rewindable: rewindable, size: length)
|
109
109
|
rescue Aws::S3::Errors::NoSuchKey
|
@@ -179,25 +179,7 @@ class Shrine
|
|
179
179
|
options.merge!(@upload_options)
|
180
180
|
options.merge!(presign_options)
|
181
181
|
|
182
|
-
|
183
|
-
presigned_post = object(id).presigned_post(options)
|
184
|
-
|
185
|
-
{ method: method, url: presigned_post.url, fields: presigned_post.fields }
|
186
|
-
else
|
187
|
-
url = object(id).presigned_url(method, options)
|
188
|
-
|
189
|
-
# When any of these options are specified, the corresponding request
|
190
|
-
# headers must be included in the upload request.
|
191
|
-
headers = {}
|
192
|
-
headers["Content-Length"] = options[:content_length] if options[:content_length]
|
193
|
-
headers["Content-Type"] = options[:content_type] if options[:content_type]
|
194
|
-
headers["Content-Disposition"] = options[:content_disposition] if options[:content_disposition]
|
195
|
-
headers["Content-Encoding"] = options[:content_encoding] if options[:content_encoding]
|
196
|
-
headers["Content-Language"] = options[:content_language] if options[:content_language]
|
197
|
-
headers["Content-MD5"] = options[:content_md5] if options[:content_md5]
|
198
|
-
|
199
|
-
{ method: method, url: url, headers: headers }
|
200
|
-
end
|
182
|
+
send(:"presign_#{method}", id, options)
|
201
183
|
end
|
202
184
|
|
203
185
|
# Deletes the file from the storage.
|
@@ -235,6 +217,17 @@ class Shrine
|
|
235
217
|
|
236
218
|
private
|
237
219
|
|
220
|
+
# Uploads the file to S3. Uses multipart upload for large files.
|
221
|
+
def put(io, id, **options)
|
222
|
+
if io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
|
223
|
+
object(id).put(body: io, **options)
|
224
|
+
else # multipart upload
|
225
|
+
object(id).upload_stream(part_size: part_size(io), **options) do |write_stream|
|
226
|
+
IO.copy_stream(io, write_stream)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
238
231
|
# Copies an existing S3 object to a new location. Uses multipart copy for
|
239
232
|
# large files.
|
240
233
|
def copy(io, id, **copy_options)
|
@@ -250,15 +243,28 @@ class Shrine
|
|
250
243
|
object(id).copy_from(io.storage.object(io.id), **options)
|
251
244
|
end
|
252
245
|
|
253
|
-
#
|
254
|
-
def
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
246
|
+
# Generates parameters for a POST upload request.
|
247
|
+
def presign_post(id, options)
|
248
|
+
presigned_post = object(id).presigned_post(options)
|
249
|
+
|
250
|
+
{ method: :post, url: presigned_post.url, fields: presigned_post.fields }
|
251
|
+
end
|
252
|
+
|
253
|
+
# Generates parameters for a PUT upload request.
|
254
|
+
def presign_put(id, options)
|
255
|
+
url = object(id).presigned_url(:put, options)
|
256
|
+
|
257
|
+
# When any of these options are specified, the corresponding request
|
258
|
+
# headers must be included in the upload request.
|
259
|
+
headers = {}
|
260
|
+
headers["Content-Length"] = options[:content_length] if options[:content_length]
|
261
|
+
headers["Content-Type"] = options[:content_type] if options[:content_type]
|
262
|
+
headers["Content-Disposition"] = options[:content_disposition] if options[:content_disposition]
|
263
|
+
headers["Content-Encoding"] = options[:content_encoding] if options[:content_encoding]
|
264
|
+
headers["Content-Language"] = options[:content_language] if options[:content_language]
|
265
|
+
headers["Content-MD5"] = options[:content_md5] if options[:content_md5]
|
266
|
+
|
267
|
+
{ method: :put, url: url, headers: headers }
|
262
268
|
end
|
263
269
|
|
264
270
|
# Determins the part size that should be used when uploading the given IO
|
@@ -273,18 +279,36 @@ class Shrine
|
|
273
279
|
end
|
274
280
|
end
|
275
281
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
282
|
+
if Gem::Version.new(Aws::CORE_GEM_VERSION) >= Gem::Version.new("3.104.0")
|
283
|
+
def get(object, params)
|
284
|
+
enum = object.enum_for(:get, **params)
|
285
|
+
|
286
|
+
begin
|
287
|
+
content_length = Integer(enum.peek.last["content-length"])
|
288
|
+
rescue StopIteration
|
289
|
+
content_length = 0
|
290
|
+
end
|
291
|
+
|
292
|
+
chunks = Enumerator.new { |y| loop { y << enum.next.first } }
|
280
293
|
|
281
|
-
|
282
|
-
|
294
|
+
[chunks, content_length]
|
295
|
+
end
|
296
|
+
else
|
297
|
+
def get(object, params)
|
298
|
+
req = client.build_request(:get_object, bucket: bucket.name, key: object.key, **params)
|
299
|
+
|
300
|
+
body = req.enum_for(:send_request)
|
301
|
+
begin
|
302
|
+
body.peek # start the request
|
303
|
+
rescue StopIteration
|
304
|
+
# the S3 object is empty
|
305
|
+
end
|
283
306
|
|
284
|
-
|
285
|
-
|
307
|
+
content_length = Integer(req.context.http_response.headers["Content-Length"])
|
308
|
+
chunks = Enumerator.new { |y| loop { y << body.next } }
|
286
309
|
|
287
|
-
|
310
|
+
[chunks, content_length]
|
311
|
+
end
|
288
312
|
end
|
289
313
|
|
290
314
|
# The file is copyable if it's on S3 and on the same Amazon account.
|
data/lib/shrine/uploaded_file.rb
CHANGED
@@ -6,7 +6,6 @@ require "uri"
|
|
6
6
|
|
7
7
|
class Shrine
|
8
8
|
# Core class that represents a file uploaded to a storage.
|
9
|
-
# Base implementation is defined in InstanceMethods and ClassMethods.
|
10
9
|
class UploadedFile
|
11
10
|
@shrine_class = ::Shrine
|
12
11
|
|
@@ -249,6 +248,11 @@ class Shrine
|
|
249
248
|
self.class.shrine_class
|
250
249
|
end
|
251
250
|
|
251
|
+
# Returns simplified inspect output.
|
252
|
+
def inspect
|
253
|
+
"#<#{self.class.inspect} storage=#{storage_key.inspect} id=#{id.inspect} metadata=#{metadata.inspect}>"
|
254
|
+
end
|
255
|
+
|
252
256
|
private
|
253
257
|
|
254
258
|
# Returns an opened IO object for the uploaded file by calling `#open`
|