shrine 3.5.0 → 3.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/doc/changing_derivatives.md +2 -1
- data/doc/changing_location.md +17 -5
- data/doc/getting_started.md +4 -2
- data/doc/plugins/derivation_endpoint.md +2 -1
- data/doc/plugins/derivatives.md +2 -1
- data/doc/plugins/download_endpoint.md +16 -4
- data/doc/plugins/refresh_metadata.md +20 -0
- data/doc/plugins/signature.md +8 -6
- data/doc/processing.md +5 -3
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/release_notes/3.7.0.md +75 -0
- data/doc/storage/s3.md +10 -0
- data/lib/shrine/attacher.rb +28 -21
- data/lib/shrine/attachment.rb +2 -2
- data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
- data/lib/shrine/plugins/add_metadata.rb +2 -4
- data/lib/shrine/plugins/atomic_helpers.rb +7 -7
- data/lib/shrine/plugins/backgrounding.rb +9 -9
- data/lib/shrine/plugins/column.rb +6 -4
- data/lib/shrine/plugins/default_url.rb +4 -4
- data/lib/shrine/plugins/delete_raw.rb +2 -2
- data/lib/shrine/plugins/derivation_endpoint.rb +37 -34
- data/lib/shrine/plugins/derivatives.rb +5 -1
- data/lib/shrine/plugins/download_endpoint.rb +65 -11
- data/lib/shrine/plugins/entity.rb +7 -7
- data/lib/shrine/plugins/infer_extension.rb +1 -1
- data/lib/shrine/plugins/instrumentation.rb +8 -8
- data/lib/shrine/plugins/mirroring.rb +10 -10
- data/lib/shrine/plugins/model.rb +9 -9
- data/lib/shrine/plugins/presign_endpoint.rb +13 -10
- data/lib/shrine/plugins/pretty_location.rb +2 -2
- data/lib/shrine/plugins/processing.rb +3 -3
- data/lib/shrine/plugins/rack_file.rb +2 -2
- data/lib/shrine/plugins/rack_response.rb +10 -4
- data/lib/shrine/plugins/refresh_metadata.rb +6 -6
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/restore_cached_data.rb +3 -3
- data/lib/shrine/plugins/signature.rb +2 -2
- data/lib/shrine/plugins/store_dimensions.rb +2 -2
- data/lib/shrine/plugins/upload_endpoint.rb +7 -5
- data/lib/shrine/plugins/upload_options.rb +1 -1
- data/lib/shrine/plugins/validation.rb +8 -8
- data/lib/shrine/plugins/versions.rb +10 -10
- data/lib/shrine/plugins.rb +6 -14
- data/lib/shrine/storage/file_system.rb +4 -17
- data/lib/shrine/storage/linter.rb +8 -8
- data/lib/shrine/storage/memory.rb +1 -3
- data/lib/shrine/storage/s3.rb +53 -38
- data/lib/shrine/uploaded_file.rb +21 -18
- data/lib/shrine/version.rb +1 -1
- data/lib/shrine.rb +18 -18
- data/shrine.gemspec +8 -8
- metadata +31 -26
data/lib/shrine/storage/s3.rb
CHANGED
|
@@ -14,13 +14,15 @@ require "tempfile"
|
|
|
14
14
|
class Shrine
|
|
15
15
|
module Storage
|
|
16
16
|
class S3
|
|
17
|
-
attr_reader :client, :bucket, :prefix, :upload_options, :signer, :public
|
|
17
|
+
attr_reader :client, :bucket, :prefix, :upload_options, :copy_options, :signer, :public
|
|
18
18
|
|
|
19
19
|
MAX_MULTIPART_PARTS = 10_000
|
|
20
20
|
MIN_PART_SIZE = 5*1024*1024
|
|
21
21
|
|
|
22
22
|
MULTIPART_THRESHOLD = { upload: 15*1024*1024, copy: 100*1024*1024 }
|
|
23
23
|
|
|
24
|
+
COPY_OPTIONS = { tagging_directive: "REPLACE" }
|
|
25
|
+
|
|
24
26
|
# Initializes a storage for uploading to S3. All options are forwarded to
|
|
25
27
|
# [`Aws::S3::Client#initialize`], except the following:
|
|
26
28
|
#
|
|
@@ -41,6 +43,10 @@ class Shrine
|
|
|
41
43
|
# be passed to [`Aws::S3::Object#put`], [`Aws::S3::Object#copy_from`]
|
|
42
44
|
# and [`Aws::S3::Bucket#presigned_post`].
|
|
43
45
|
#
|
|
46
|
+
# :copy_options
|
|
47
|
+
# : Additional options that will be used for copying files, they will
|
|
48
|
+
# be passed to [`Aws::S3::Object#copy_from`].
|
|
49
|
+
#
|
|
44
50
|
# :multipart_threshold
|
|
45
51
|
# : If the input file is larger than the specified size, a parallelized
|
|
46
52
|
# multipart will be used for the upload/copy. Defaults to
|
|
@@ -62,13 +68,15 @@ class Shrine
|
|
|
62
68
|
# [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
|
|
63
69
|
# [`Aws::S3::Client#initialize`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#initialize-instance_method
|
|
64
70
|
# [configuring AWS SDK]: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html
|
|
65
|
-
def initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, **
|
|
71
|
+
def initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS, **)
|
|
66
72
|
raise ArgumentError, "the :bucket option is nil" unless bucket
|
|
67
73
|
|
|
68
|
-
@client = client || Aws::S3::Client.new(**
|
|
74
|
+
@client = client || Aws::S3::Client.new(**)
|
|
75
|
+
@transfer_manager = Aws::S3::TransferManager.new(client: @client) if defined?(Aws::S3::TransferManager)
|
|
69
76
|
@bucket = Aws::S3::Bucket.new(name: bucket, client: @client)
|
|
70
77
|
@prefix = prefix
|
|
71
78
|
@upload_options = upload_options
|
|
79
|
+
@copy_options = copy_options
|
|
72
80
|
@multipart_threshold = MULTIPART_THRESHOLD.merge(multipart_threshold)
|
|
73
81
|
@max_multipart_parts = max_multipart_parts || MAX_MULTIPART_PARTS
|
|
74
82
|
@signer = signer
|
|
@@ -90,8 +98,7 @@ class Shrine
|
|
|
90
98
|
options[:content_disposition] = ContentDisposition.inline(filename) if filename
|
|
91
99
|
options[:acl] = "public-read" if public
|
|
92
100
|
|
|
93
|
-
options.merge!(@upload_options)
|
|
94
|
-
options.merge!(upload_options)
|
|
101
|
+
options.merge!(@upload_options, upload_options)
|
|
95
102
|
|
|
96
103
|
if copyable?(io)
|
|
97
104
|
copy(io, id, **options)
|
|
@@ -109,8 +116,8 @@ class Shrine
|
|
|
109
116
|
# Any additional options are forwarded to [`Aws::S3::Object#get`].
|
|
110
117
|
#
|
|
111
118
|
# [`Aws::S3::Object#get`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
|
|
112
|
-
def open(id, rewindable: true, encoding: nil, **
|
|
113
|
-
chunks, length = get(id, **
|
|
119
|
+
def open(id, rewindable: true, encoding: nil, **)
|
|
120
|
+
chunks, length = get(id, **)
|
|
114
121
|
|
|
115
122
|
Down::ChunkedIO.new(chunks: chunks, rewindable: rewindable, size: length, encoding: encoding)
|
|
116
123
|
rescue Aws::S3::Errors::NoSuchKey
|
|
@@ -138,11 +145,11 @@ class Shrine
|
|
|
138
145
|
#
|
|
139
146
|
# [`Aws::S3::Object#presigned_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
|
|
140
147
|
# [`Aws::S3::Object#public_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#public_url-instance_method
|
|
141
|
-
def url(id, public: self.public, host: nil, **
|
|
148
|
+
def url(id, public: self.public, host: nil, **)
|
|
142
149
|
if public || signer
|
|
143
|
-
url = object(id).public_url(**
|
|
150
|
+
url = object(id).public_url(**)
|
|
144
151
|
else
|
|
145
|
-
url = object(id).presigned_url(:get, **
|
|
152
|
+
url = object(id).presigned_url(:get, **)
|
|
146
153
|
end
|
|
147
154
|
|
|
148
155
|
if host
|
|
@@ -152,7 +159,7 @@ class Shrine
|
|
|
152
159
|
end
|
|
153
160
|
|
|
154
161
|
if signer
|
|
155
|
-
url = signer.call(url, **
|
|
162
|
+
url = signer.call(url, **)
|
|
156
163
|
end
|
|
157
164
|
|
|
158
165
|
url
|
|
@@ -183,8 +190,7 @@ class Shrine
|
|
|
183
190
|
options = {}
|
|
184
191
|
options[:acl] = "public-read" if public
|
|
185
192
|
|
|
186
|
-
options.merge!(@upload_options)
|
|
187
|
-
options.merge!(presign_options)
|
|
193
|
+
options.merge!(@upload_options, presign_options)
|
|
188
194
|
|
|
189
195
|
send(:"presign_#{method}", id, options)
|
|
190
196
|
end
|
|
@@ -224,12 +230,21 @@ class Shrine
|
|
|
224
230
|
|
|
225
231
|
private
|
|
226
232
|
|
|
227
|
-
#
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
# Upload the file to S3.
|
|
234
|
+
# Uses @transfer_manager, if defined, for any size upload.
|
|
235
|
+
# Falls back to the original code using the older, now depricated
|
|
236
|
+
# AWS APIs for users of older version of the AWS Gem.
|
|
237
|
+
# for multipart uploads of large files.
|
|
238
|
+
def put(io, id, **)
|
|
239
|
+
if @transfer_manager
|
|
240
|
+
@transfer_manager.upload_stream(bucket: bucket.name, key: object_key(id), part_size: part_size(io), **) do |write_stream|
|
|
241
|
+
IO.copy_stream(io, write_stream)
|
|
242
|
+
end
|
|
243
|
+
elsif io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
|
|
244
|
+
object(id).put(body: io, **)
|
|
245
|
+
else
|
|
246
|
+
# multipart upload old API
|
|
247
|
+
object(id).upload_stream(part_size: part_size(io), **) do |write_stream|
|
|
233
248
|
IO.copy_stream(io, write_stream)
|
|
234
249
|
end
|
|
235
250
|
end
|
|
@@ -241,7 +256,6 @@ class Shrine
|
|
|
241
256
|
# don't inherit source object metadata or AWS tags
|
|
242
257
|
options = {
|
|
243
258
|
metadata_directive: "REPLACE",
|
|
244
|
-
tagging_directive: "REPLACE"
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
if io.size && io.size >= @multipart_threshold[:copy]
|
|
@@ -249,7 +263,7 @@ class Shrine
|
|
|
249
263
|
options.merge!(multipart_copy: true, content_length: io.size)
|
|
250
264
|
end
|
|
251
265
|
|
|
252
|
-
options.merge!(copy_options)
|
|
266
|
+
options.merge!(@copy_options, copy_options)
|
|
253
267
|
|
|
254
268
|
object(id).copy_from(io.storage.object(io.id), **options)
|
|
255
269
|
end
|
|
@@ -267,13 +281,14 @@ class Shrine
|
|
|
267
281
|
|
|
268
282
|
# When any of these options are specified, the corresponding request
|
|
269
283
|
# headers must be included in the upload request.
|
|
270
|
-
headers = {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
284
|
+
headers = {
|
|
285
|
+
"Content-Length" => options[:content_length],
|
|
286
|
+
"Content-Type" => options[:content_type],
|
|
287
|
+
"Content-Disposition" => options[:content_disposition],
|
|
288
|
+
"Content-Encoding" => options[:content_encoding],
|
|
289
|
+
"Content-Language" => options[:content_language],
|
|
290
|
+
"Content-MD5" => options[:content_md5],
|
|
291
|
+
}.compact
|
|
277
292
|
|
|
278
293
|
{ method: :put, url: url, headers: headers }
|
|
279
294
|
end
|
|
@@ -294,8 +309,8 @@ class Shrine
|
|
|
294
309
|
# object before all content is downloaded, so we hack our way around it.
|
|
295
310
|
# This way get the content length without an additional HEAD request.
|
|
296
311
|
if Gem::Version.new(Aws::CORE_GEM_VERSION) >= Gem::Version.new("3.104.0")
|
|
297
|
-
def get(id, **
|
|
298
|
-
enum = object(id).enum_for(:get, **
|
|
312
|
+
def get(id, **)
|
|
313
|
+
enum = object(id).enum_for(:get, **)
|
|
299
314
|
|
|
300
315
|
begin
|
|
301
316
|
content_length = Integer(enum.peek.last["content-length"])
|
|
@@ -308,8 +323,8 @@ class Shrine
|
|
|
308
323
|
[chunks, content_length]
|
|
309
324
|
end
|
|
310
325
|
else
|
|
311
|
-
def get(id, **
|
|
312
|
-
req = client.build_request(:get_object, bucket: bucket.name, key: object_key(id), **
|
|
326
|
+
def get(id, **)
|
|
327
|
+
req = client.build_request(:get_object, bucket: bucket.name, key: object_key(id), **)
|
|
313
328
|
|
|
314
329
|
body = req.enum_for(:send_request)
|
|
315
330
|
begin
|
|
@@ -352,10 +367,10 @@ class Shrine
|
|
|
352
367
|
|
|
353
368
|
# Save the encryption client and continue initialization with normal
|
|
354
369
|
# client.
|
|
355
|
-
def initialize(client: nil, **
|
|
370
|
+
def initialize(client: nil, **)
|
|
356
371
|
return super unless client.class.name.start_with?("Aws::S3::Encryption")
|
|
357
372
|
|
|
358
|
-
super(client: client.client, **
|
|
373
|
+
super(client: client.client, **)
|
|
359
374
|
@encryption_client = client
|
|
360
375
|
end
|
|
361
376
|
|
|
@@ -363,19 +378,19 @@ class Shrine
|
|
|
363
378
|
|
|
364
379
|
# Encryption client doesn't support multipart uploads, so we always use
|
|
365
380
|
# #put_object.
|
|
366
|
-
def put(io, id, **
|
|
381
|
+
def put(io, id, **)
|
|
367
382
|
return super unless encryption_client
|
|
368
383
|
|
|
369
|
-
encryption_client.put_object(body: io, bucket: bucket.name, key: object_key(id), **
|
|
384
|
+
encryption_client.put_object(body: io, bucket: bucket.name, key: object_key(id), **)
|
|
370
385
|
end
|
|
371
386
|
|
|
372
|
-
def get(id, **
|
|
387
|
+
def get(id, **)
|
|
373
388
|
return super unless encryption_client
|
|
374
389
|
|
|
375
390
|
# Encryption client v2 warns against streaming download, so we first
|
|
376
391
|
# download all content into a file.
|
|
377
392
|
tempfile = Tempfile.new("shrine-s3", binmode: true)
|
|
378
|
-
response = encryption_client.get_object(response_target: tempfile, bucket: bucket.name, key: object_key(id), **
|
|
393
|
+
response = encryption_client.get_object(response_target: tempfile, bucket: bucket.name, key: object_key(id), **)
|
|
379
394
|
tempfile.rewind
|
|
380
395
|
|
|
381
396
|
chunks = Enumerator.new do |yielder|
|
data/lib/shrine/uploaded_file.rb
CHANGED
|
@@ -22,6 +22,8 @@ class Shrine
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
module InstanceMethods
|
|
25
|
+
RFC2396_PARSER = URI::RFC2396_Parser.new
|
|
26
|
+
|
|
25
27
|
# The location where the file was uploaded to the storage.
|
|
26
28
|
attr_reader :id
|
|
27
29
|
|
|
@@ -50,7 +52,7 @@ class Shrine
|
|
|
50
52
|
# The extension derived from #id if present, otherwise it's derived
|
|
51
53
|
# from #original_filename.
|
|
52
54
|
def extension
|
|
53
|
-
identifier = id =~
|
|
55
|
+
identifier = id =~ RFC2396_PARSER.make_regexp ? id.sub(/\?.+$/, "") : id # strip query params for shrine-url
|
|
54
56
|
result = File.extname(identifier)[1..-1]
|
|
55
57
|
result ||= File.extname(original_filename.to_s)[1..-1]
|
|
56
58
|
result.downcase if result
|
|
@@ -89,9 +91,9 @@ class Shrine
|
|
|
89
91
|
# # or
|
|
90
92
|
#
|
|
91
93
|
# uploaded_file.open { |io| io.read } # the IO is automatically closed
|
|
92
|
-
def open(**
|
|
94
|
+
def open(**)
|
|
93
95
|
@io.close if @io
|
|
94
|
-
@io = _open(**
|
|
96
|
+
@io = _open(**)
|
|
95
97
|
|
|
96
98
|
return @io unless block_given?
|
|
97
99
|
|
|
@@ -118,9 +120,9 @@ class Shrine
|
|
|
118
120
|
# # or
|
|
119
121
|
#
|
|
120
122
|
# uploaded_file.download { |tempfile| tempfile.read } # tempfile is deleted
|
|
121
|
-
def download(**
|
|
123
|
+
def download(**)
|
|
122
124
|
tempfile = Tempfile.new(["shrine", ".#{extension}"], binmode: true)
|
|
123
|
-
stream(tempfile, **
|
|
125
|
+
stream(tempfile, **)
|
|
124
126
|
tempfile.open
|
|
125
127
|
|
|
126
128
|
block_given? ? yield(tempfile) : tempfile
|
|
@@ -139,19 +141,19 @@ class Shrine
|
|
|
139
141
|
# uploaded_file.stream(StringIO.new)
|
|
140
142
|
# # or
|
|
141
143
|
# uploaded_file.stream("/path/to/destination")
|
|
142
|
-
def stream(destination, **
|
|
144
|
+
def stream(destination, **)
|
|
143
145
|
if opened?
|
|
144
146
|
IO.copy_stream(io, destination)
|
|
145
147
|
io.rewind
|
|
146
148
|
else
|
|
147
|
-
open(**
|
|
149
|
+
open(**) { |io| IO.copy_stream(io, destination) }
|
|
148
150
|
end
|
|
149
151
|
end
|
|
150
152
|
|
|
151
153
|
# Part of complying to the IO interface. It delegates to the internally
|
|
152
154
|
# opened IO object.
|
|
153
|
-
def read(*
|
|
154
|
-
io.read(*
|
|
155
|
+
def read(*)
|
|
156
|
+
io.read(*)
|
|
155
157
|
end
|
|
156
158
|
|
|
157
159
|
# Part of complying to the IO interface. It delegates to the internally
|
|
@@ -170,6 +172,7 @@ class Shrine
|
|
|
170
172
|
# opened IO object.
|
|
171
173
|
def close
|
|
172
174
|
io.close if opened?
|
|
175
|
+
@io = nil
|
|
173
176
|
end
|
|
174
177
|
|
|
175
178
|
# Returns whether the file has already been opened.
|
|
@@ -178,8 +181,8 @@ class Shrine
|
|
|
178
181
|
end
|
|
179
182
|
|
|
180
183
|
# Calls `#url` on the storage, forwarding any given URL options.
|
|
181
|
-
def url(**
|
|
182
|
-
storage.url(id, **
|
|
184
|
+
def url(**)
|
|
185
|
+
storage.url(id, **)
|
|
183
186
|
end
|
|
184
187
|
|
|
185
188
|
# Calls `#exists?` on the storage, which checks whether the file exists
|
|
@@ -189,8 +192,8 @@ class Shrine
|
|
|
189
192
|
end
|
|
190
193
|
|
|
191
194
|
# Uploads a new file to this file's location and returns it.
|
|
192
|
-
def replace(io, **
|
|
193
|
-
uploader.upload(io,
|
|
195
|
+
def replace(io, **)
|
|
196
|
+
uploader.upload(io, **, location: id)
|
|
194
197
|
end
|
|
195
198
|
|
|
196
199
|
# Calls `#delete` on the storage, which deletes the file from the
|
|
@@ -206,12 +209,12 @@ class Shrine
|
|
|
206
209
|
|
|
207
210
|
# Returns the data hash in the JSON format. Suitable for storing in a
|
|
208
211
|
# database column or passing to a background job.
|
|
209
|
-
def to_json(*
|
|
210
|
-
data.to_json(*
|
|
212
|
+
def to_json(*)
|
|
213
|
+
data.to_json(*)
|
|
211
214
|
end
|
|
212
215
|
|
|
213
216
|
# Conform to ActiveSupport's JSON interface.
|
|
214
|
-
def as_json(*
|
|
217
|
+
def as_json(*)
|
|
215
218
|
data
|
|
216
219
|
end
|
|
217
220
|
|
|
@@ -262,8 +265,8 @@ class Shrine
|
|
|
262
265
|
@io ||= _open
|
|
263
266
|
end
|
|
264
267
|
|
|
265
|
-
def _open(**
|
|
266
|
-
storage.open(id, **
|
|
268
|
+
def _open(**)
|
|
269
|
+
storage.open(id, **)
|
|
267
270
|
end
|
|
268
271
|
end
|
|
269
272
|
|
data/lib/shrine/version.rb
CHANGED
data/lib/shrine.rb
CHANGED
|
@@ -68,9 +68,9 @@ class Shrine
|
|
|
68
68
|
#
|
|
69
69
|
# Shrine.plugin MyPlugin
|
|
70
70
|
# Shrine.plugin :my_plugin
|
|
71
|
-
def plugin(plugin,
|
|
71
|
+
def plugin(plugin, ...)
|
|
72
72
|
plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
|
73
|
-
Plugins.load_dependencies(plugin, self,
|
|
73
|
+
Plugins.load_dependencies(plugin, self, ...)
|
|
74
74
|
self.include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
|
|
75
75
|
self.extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
|
|
76
76
|
self::UploadedFile.include(plugin::FileMethods) if defined?(plugin::FileMethods)
|
|
@@ -79,7 +79,7 @@ class Shrine
|
|
|
79
79
|
self::Attachment.extend(plugin::AttachmentClassMethods) if defined?(plugin::AttachmentClassMethods)
|
|
80
80
|
self::Attacher.include(plugin::AttacherMethods) if defined?(plugin::AttacherMethods)
|
|
81
81
|
self::Attacher.extend(plugin::AttacherClassMethods) if defined?(plugin::AttacherClassMethods)
|
|
82
|
-
Plugins.configure(plugin, self,
|
|
82
|
+
Plugins.configure(plugin, self, ...)
|
|
83
83
|
plugin
|
|
84
84
|
end
|
|
85
85
|
|
|
@@ -95,8 +95,8 @@ class Shrine
|
|
|
95
95
|
# class Photo
|
|
96
96
|
# include Shrine::Attachment(:image) # creates a Shrine::Attachment object
|
|
97
97
|
# end
|
|
98
|
-
def Attachment(name, **
|
|
99
|
-
self::Attachment.new(name, **
|
|
98
|
+
def Attachment(name, **)
|
|
99
|
+
self::Attachment.new(name, **)
|
|
100
100
|
end
|
|
101
101
|
alias attachment Attachment
|
|
102
102
|
alias [] Attachment
|
|
@@ -104,8 +104,8 @@ class Shrine
|
|
|
104
104
|
# Uploads the file to the specified storage. It delegates to `Shrine#upload`.
|
|
105
105
|
#
|
|
106
106
|
# Shrine.upload(io, :store) #=> #<Shrine::UploadedFile>
|
|
107
|
-
def upload(io, storage, **
|
|
108
|
-
new(storage).upload(io, **
|
|
107
|
+
def upload(io, storage, **)
|
|
108
|
+
new(storage).upload(io, **)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
# Instantiates a Shrine::UploadedFile from a hash, and optionally
|
|
@@ -201,13 +201,13 @@ class Shrine
|
|
|
201
201
|
# uploader.upload(io, metadata: { "foo" => "bar" }) # add metadata
|
|
202
202
|
# uploader.upload(io, location: "path/to/file") # specify location
|
|
203
203
|
# uploader.upload(io, upload_options: { acl: "public-read" }) # add upload options
|
|
204
|
-
def upload(io, **
|
|
204
|
+
def upload(io, **)
|
|
205
205
|
_enforce_io(io)
|
|
206
206
|
|
|
207
|
-
metadata = get_metadata(io, **
|
|
208
|
-
location = get_location(io,
|
|
207
|
+
metadata = get_metadata(io, **)
|
|
208
|
+
location = get_location(io, **, metadata:)
|
|
209
209
|
|
|
210
|
-
_upload(io,
|
|
210
|
+
_upload(io, **, location:, metadata:)
|
|
211
211
|
|
|
212
212
|
self.class::UploadedFile.new(
|
|
213
213
|
id: location,
|
|
@@ -219,13 +219,13 @@ class Shrine
|
|
|
219
219
|
# Generates a unique location for the uploaded file, preserving the
|
|
220
220
|
# file extension. Can be overriden in uploaders for generating custom
|
|
221
221
|
# location.
|
|
222
|
-
def generate_location(io, metadata: {}, **
|
|
223
|
-
basic_location(io, metadata:
|
|
222
|
+
def generate_location(io, metadata: {}, **)
|
|
223
|
+
basic_location(io, metadata:)
|
|
224
224
|
end
|
|
225
225
|
|
|
226
226
|
# Extracts filename, size and MIME type from the file, which is later
|
|
227
227
|
# accessible through UploadedFile#metadata.
|
|
228
|
-
def extract_metadata(io, **
|
|
228
|
+
def extract_metadata(io, **)
|
|
229
229
|
{
|
|
230
230
|
"filename" => extract_filename(io),
|
|
231
231
|
"size" => extract_size(io),
|
|
@@ -281,11 +281,11 @@ class Shrine
|
|
|
281
281
|
|
|
282
282
|
# If the IO object is a Shrine::UploadedFile, it simply copies over its
|
|
283
283
|
# metadata, otherwise it calls #extract_metadata.
|
|
284
|
-
def get_metadata(io, metadata: nil, **
|
|
284
|
+
def get_metadata(io, metadata: nil, **)
|
|
285
285
|
if io.is_a?(UploadedFile) && metadata != true
|
|
286
286
|
result = io.metadata.dup
|
|
287
287
|
elsif metadata != false
|
|
288
|
-
result = extract_metadata(io, **
|
|
288
|
+
result = extract_metadata(io, **)
|
|
289
289
|
else
|
|
290
290
|
result = {}
|
|
291
291
|
end
|
|
@@ -296,8 +296,8 @@ class Shrine
|
|
|
296
296
|
|
|
297
297
|
# Retrieves the location for the given IO and context. First it looks
|
|
298
298
|
# for the `:location` option, otherwise it calls #generate_location.
|
|
299
|
-
def get_location(io, location: nil, **
|
|
300
|
-
location ||= generate_location(io, **
|
|
299
|
+
def get_location(io, location: nil, **)
|
|
300
|
+
location ||= generate_location(io, **)
|
|
301
301
|
location or fail Error, "location generated for #{io.inspect} was nil"
|
|
302
302
|
end
|
|
303
303
|
|
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 = ">= 3.2"
|
|
8
8
|
|
|
9
9
|
gem.summary = "Toolkit for file attachments in Ruby applications"
|
|
10
10
|
gem.description = <<-END
|
|
@@ -38,13 +38,13 @@ direct uploads for fully asynchronous user experience.
|
|
|
38
38
|
|
|
39
39
|
# general testing helpers
|
|
40
40
|
gem.add_development_dependency "rake", ">= 11.1"
|
|
41
|
-
gem.add_development_dependency "minitest", "~>
|
|
41
|
+
gem.add_development_dependency "minitest", "~> 6.0"
|
|
42
42
|
gem.add_development_dependency "mocha", "~> 1.11"
|
|
43
43
|
|
|
44
44
|
# for endpoint plugins
|
|
45
|
-
gem.add_development_dependency "rack", "
|
|
45
|
+
gem.add_development_dependency "rack", ">= 2", "< 4"
|
|
46
46
|
gem.add_development_dependency "http-form_data", "~> 2.2"
|
|
47
|
-
gem.add_development_dependency "rack-
|
|
47
|
+
gem.add_development_dependency "rack-test", "~> 2.1"
|
|
48
48
|
|
|
49
49
|
# for determine_mime_type plugin
|
|
50
50
|
gem.add_development_dependency "mimemagic", ">= 0.3.2"
|
|
@@ -57,7 +57,7 @@ direct uploads for fully asynchronous user experience.
|
|
|
57
57
|
|
|
58
58
|
# for store_dimensions plugin
|
|
59
59
|
gem.add_development_dependency "fastimage"
|
|
60
|
-
gem.add_development_dependency "mini_magick", "~>
|
|
60
|
+
gem.add_development_dependency "mini_magick", "~> 5.0" unless ENV["CI"]
|
|
61
61
|
gem.add_development_dependency "ruby-vips", "~> 2.0" unless ENV["CI"]
|
|
62
62
|
|
|
63
63
|
# for S3 storage
|
|
@@ -67,10 +67,10 @@ direct uploads for fully asynchronous user experience.
|
|
|
67
67
|
|
|
68
68
|
# for instrumentation plugin
|
|
69
69
|
gem.add_development_dependency "dry-monitor"
|
|
70
|
-
gem.add_development_dependency "activesupport",
|
|
70
|
+
gem.add_development_dependency "activesupport", RUBY_ENGINE == "jruby" ? "~> 7.0.0" : "~> 8.1"
|
|
71
71
|
|
|
72
72
|
# for ORM plugins
|
|
73
73
|
gem.add_development_dependency "sequel"
|
|
74
|
-
gem.add_development_dependency "activerecord",
|
|
75
|
-
gem.add_development_dependency "sqlite3", "~> 1
|
|
74
|
+
gem.add_development_dependency "activerecord", RUBY_ENGINE == "jruby" ? "~> 7.0.0" : "~> 8.1"
|
|
75
|
+
gem.add_development_dependency "sqlite3", "~> 2.1" unless RUBY_ENGINE == "jruby"
|
|
76
76
|
end
|