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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/doc/changing_derivatives.md +2 -1
  4. data/doc/changing_location.md +17 -5
  5. data/doc/getting_started.md +4 -2
  6. data/doc/plugins/derivation_endpoint.md +2 -1
  7. data/doc/plugins/derivatives.md +2 -1
  8. data/doc/plugins/download_endpoint.md +16 -4
  9. data/doc/plugins/refresh_metadata.md +20 -0
  10. data/doc/plugins/signature.md +8 -6
  11. data/doc/processing.md +5 -3
  12. data/doc/release_notes/3.6.0.md +23 -0
  13. data/doc/release_notes/3.7.0.md +75 -0
  14. data/doc/storage/s3.md +10 -0
  15. data/lib/shrine/attacher.rb +28 -21
  16. data/lib/shrine/attachment.rb +2 -2
  17. data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
  18. data/lib/shrine/plugins/add_metadata.rb +2 -4
  19. data/lib/shrine/plugins/atomic_helpers.rb +7 -7
  20. data/lib/shrine/plugins/backgrounding.rb +9 -9
  21. data/lib/shrine/plugins/column.rb +6 -4
  22. data/lib/shrine/plugins/default_url.rb +4 -4
  23. data/lib/shrine/plugins/delete_raw.rb +2 -2
  24. data/lib/shrine/plugins/derivation_endpoint.rb +37 -34
  25. data/lib/shrine/plugins/derivatives.rb +5 -1
  26. data/lib/shrine/plugins/download_endpoint.rb +65 -11
  27. data/lib/shrine/plugins/entity.rb +7 -7
  28. data/lib/shrine/plugins/infer_extension.rb +1 -1
  29. data/lib/shrine/plugins/instrumentation.rb +8 -8
  30. data/lib/shrine/plugins/mirroring.rb +10 -10
  31. data/lib/shrine/plugins/model.rb +9 -9
  32. data/lib/shrine/plugins/presign_endpoint.rb +13 -10
  33. data/lib/shrine/plugins/pretty_location.rb +2 -2
  34. data/lib/shrine/plugins/processing.rb +3 -3
  35. data/lib/shrine/plugins/rack_file.rb +2 -2
  36. data/lib/shrine/plugins/rack_response.rb +10 -4
  37. data/lib/shrine/plugins/refresh_metadata.rb +6 -6
  38. data/lib/shrine/plugins/remote_url.rb +3 -3
  39. data/lib/shrine/plugins/restore_cached_data.rb +3 -3
  40. data/lib/shrine/plugins/signature.rb +2 -2
  41. data/lib/shrine/plugins/store_dimensions.rb +2 -2
  42. data/lib/shrine/plugins/upload_endpoint.rb +7 -5
  43. data/lib/shrine/plugins/upload_options.rb +1 -1
  44. data/lib/shrine/plugins/validation.rb +8 -8
  45. data/lib/shrine/plugins/versions.rb +10 -10
  46. data/lib/shrine/plugins.rb +6 -14
  47. data/lib/shrine/storage/file_system.rb +4 -17
  48. data/lib/shrine/storage/linter.rb +8 -8
  49. data/lib/shrine/storage/memory.rb +1 -3
  50. data/lib/shrine/storage/s3.rb +53 -38
  51. data/lib/shrine/uploaded_file.rb +21 -18
  52. data/lib/shrine/version.rb +1 -1
  53. data/lib/shrine.rb +18 -18
  54. data/shrine.gemspec +8 -8
  55. metadata +31 -26
@@ -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, **s3_options)
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(**s3_options)
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, **options)
113
- chunks, length = get(id, **options)
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, **options)
148
+ def url(id, public: self.public, host: nil, **)
142
149
  if public || signer
143
- url = object(id).public_url(**options)
150
+ url = object(id).public_url(**)
144
151
  else
145
- url = object(id).presigned_url(:get, **options)
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, **options)
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
- # Uploads the file to S3. Uses multipart upload for large files.
228
- def put(io, id, **options)
229
- if io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
230
- object(id).put(body: io, **options)
231
- else # multipart upload
232
- object(id).upload_stream(part_size: part_size(io), **options) do |write_stream|
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
- headers["Content-Length"] = options[:content_length] if options[:content_length]
272
- headers["Content-Type"] = options[:content_type] if options[:content_type]
273
- headers["Content-Disposition"] = options[:content_disposition] if options[:content_disposition]
274
- headers["Content-Encoding"] = options[:content_encoding] if options[:content_encoding]
275
- headers["Content-Language"] = options[:content_language] if options[:content_language]
276
- headers["Content-MD5"] = options[:content_md5] if options[:content_md5]
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, **params)
298
- enum = object(id).enum_for(:get, **params)
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, **params)
312
- req = client.build_request(:get_object, bucket: bucket.name, key: object_key(id), **params)
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, **options)
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, **options)
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, **options)
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), **options)
384
+ encryption_client.put_object(body: io, bucket: bucket.name, key: object_key(id), **)
370
385
  end
371
386
 
372
- def get(id, **options)
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), **options)
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|
@@ -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 =~ URI::DEFAULT_PARSER.make_regexp ? id.sub(/\?.+$/, "") : id # strip query params for shrine-url
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(**options)
94
+ def open(**)
93
95
  @io.close if @io
94
- @io = _open(**options)
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(**options)
123
+ def download(**)
122
124
  tempfile = Tempfile.new(["shrine", ".#{extension}"], binmode: true)
123
- stream(tempfile, **options)
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, **options)
144
+ def stream(destination, **)
143
145
  if opened?
144
146
  IO.copy_stream(io, destination)
145
147
  io.rewind
146
148
  else
147
- open(**options) { |io| IO.copy_stream(io, destination) }
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(*args)
154
- io.read(*args)
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(**options)
182
- storage.url(id, **options)
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, **options)
193
- uploader.upload(io, **options, location: id)
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(*args)
210
- data.to_json(*args)
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(*args)
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(**options)
266
- storage.open(id, **options)
268
+ def _open(**)
269
+ storage.open(id, **)
267
270
  end
268
271
  end
269
272
 
@@ -7,7 +7,7 @@ class Shrine
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 3
10
- MINOR = 5
10
+ MINOR = 7
11
11
  TINY = 0
12
12
  PRE = nil
13
13
 
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, *args, **kwargs, &block)
71
+ def plugin(plugin, ...)
72
72
  plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
73
- Plugins.load_dependencies(plugin, self, *args, **kwargs, &block)
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, *args, **kwargs, &block)
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, **args)
99
- self::Attachment.new(name, **args)
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, **options)
108
- new(storage).upload(io, **options)
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, **options)
204
+ def upload(io, **)
205
205
  _enforce_io(io)
206
206
 
207
- metadata = get_metadata(io, **options)
208
- location = get_location(io, **options, metadata: metadata)
207
+ metadata = get_metadata(io, **)
208
+ location = get_location(io, **, metadata:)
209
209
 
210
- _upload(io, **options, location: location, metadata: metadata)
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: {}, **options)
223
- basic_location(io, metadata: 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, **options)
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, **options)
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, **options)
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, **options)
300
- location ||= generate_location(io, **options)
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.3"
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", "~> 5.8"
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", "~> 2.0"
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-test_app"
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", "~> 4.0" unless ENV["CI"]
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", RUBY_VERSION >= "2.7" ? "~> 7.0" : RUBY_VERSION >= "2.5" ? "~> 6.0" : "~> 5.2"
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", RUBY_VERSION >= "2.7" ? "~> 7.0" : RUBY_VERSION >= "2.5" ? "~> 6.0" : "~> 5.2"
75
- gem.add_development_dependency "sqlite3", "~> 1.4" unless RUBY_ENGINE == "jruby"
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