shrine 3.6.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -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.7.0.md +75 -0
  13. data/lib/shrine/attacher.rb +21 -21
  14. data/lib/shrine/attachment.rb +2 -2
  15. data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
  16. data/lib/shrine/plugins/add_metadata.rb +2 -4
  17. data/lib/shrine/plugins/atomic_helpers.rb +7 -7
  18. data/lib/shrine/plugins/backgrounding.rb +9 -9
  19. data/lib/shrine/plugins/column.rb +6 -4
  20. data/lib/shrine/plugins/default_url.rb +4 -4
  21. data/lib/shrine/plugins/delete_raw.rb +2 -2
  22. data/lib/shrine/plugins/derivation_endpoint.rb +25 -25
  23. data/lib/shrine/plugins/derivatives.rb +5 -1
  24. data/lib/shrine/plugins/download_endpoint.rb +62 -10
  25. data/lib/shrine/plugins/entity.rb +7 -7
  26. data/lib/shrine/plugins/infer_extension.rb +1 -1
  27. data/lib/shrine/plugins/instrumentation.rb +8 -8
  28. data/lib/shrine/plugins/mirroring.rb +10 -10
  29. data/lib/shrine/plugins/model.rb +9 -9
  30. data/lib/shrine/plugins/presign_endpoint.rb +4 -4
  31. data/lib/shrine/plugins/pretty_location.rb +2 -2
  32. data/lib/shrine/plugins/processing.rb +3 -3
  33. data/lib/shrine/plugins/rack_file.rb +2 -2
  34. data/lib/shrine/plugins/rack_response.rb +4 -4
  35. data/lib/shrine/plugins/refresh_metadata.rb +6 -6
  36. data/lib/shrine/plugins/remote_url.rb +3 -3
  37. data/lib/shrine/plugins/restore_cached_data.rb +3 -3
  38. data/lib/shrine/plugins/signature.rb +2 -2
  39. data/lib/shrine/plugins/store_dimensions.rb +2 -2
  40. data/lib/shrine/plugins/upload_endpoint.rb +4 -4
  41. data/lib/shrine/plugins/upload_options.rb +1 -1
  42. data/lib/shrine/plugins/validation.rb +8 -8
  43. data/lib/shrine/plugins/versions.rb +10 -10
  44. data/lib/shrine/plugins.rb +6 -14
  45. data/lib/shrine/storage/file_system.rb +4 -17
  46. data/lib/shrine/storage/linter.rb +8 -8
  47. data/lib/shrine/storage/memory.rb +1 -3
  48. data/lib/shrine/storage/s3.rb +45 -37
  49. data/lib/shrine/uploaded_file.rb +20 -18
  50. data/lib/shrine/version.rb +1 -1
  51. data/lib/shrine.rb +18 -18
  52. data/shrine.gemspec +6 -6
  53. metadata +15 -17
@@ -11,14 +11,14 @@ class Shrine
11
11
  module AttacherMethods
12
12
  private
13
13
 
14
- def cached(value, **options)
14
+ def cached(value, **)
15
15
  cached_file = super
16
16
 
17
17
  # TODO: Remove this conditional when we remove the versions plugin
18
18
  if cached_file.is_a?(Hash) || cached_file.is_a?(Array)
19
- uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **options) }
19
+ uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **) }
20
20
  else
21
- cached_file.refresh_metadata!(**context, **options)
21
+ cached_file.refresh_metadata!(**context, **)
22
22
  end
23
23
 
24
24
  cached_file
@@ -37,7 +37,7 @@ class Shrine
37
37
  def instrument_signature(io, algorithm, format, &block)
38
38
  return yield unless respond_to?(:instrument)
39
39
 
40
- instrument(:signature, io: io, algorithm: algorithm, format: format, &block)
40
+ instrument(:signature, io:, algorithm:, format:, &block)
41
41
  end
42
42
  end
43
43
 
@@ -45,7 +45,7 @@ class Shrine
45
45
  # Calculates `algorithm` hash of the contents of the IO object, and
46
46
  # encodes it into `format`.
47
47
  def calculate_signature(io, algorithm, format: :hex)
48
- self.class.calculate_signature(io, algorithm, format: format)
48
+ self.class.calculate_signature(io, algorithm, format:)
49
49
  end
50
50
  end
51
51
 
@@ -57,7 +57,7 @@ class Shrine
57
57
  def dimensions_analyzer(name)
58
58
  on_error = opts[:store_dimensions][:on_error]
59
59
 
60
- DimensionsAnalyzer.new(name, on_error: on_error).method(:call)
60
+ DimensionsAnalyzer.new(name, on_error:).method(:call)
61
61
  end
62
62
 
63
63
  private
@@ -66,7 +66,7 @@ class Shrine
66
66
  def instrument_dimensions(io, &block)
67
67
  return yield unless respond_to?(:instrument)
68
68
 
69
- instrument(:image_dimensions, io: io, &block)
69
+ instrument(:image_dimensions, io:, &block)
70
70
  end
71
71
  end
72
72
 
@@ -26,12 +26,12 @@ class Shrine
26
26
  # The `storage_key` needs to be one of the registered Shrine storages.
27
27
  # Additional options can be given to override the options given on
28
28
  # plugin initialization.
29
- def upload_endpoint(storage_key, **options)
29
+ def upload_endpoint(storage_key, **)
30
30
  Shrine::UploadEndpoint.new(
31
31
  shrine_class: self,
32
32
  storage_key: storage_key,
33
33
  **opts[:upload_endpoint],
34
- **options,
34
+ **,
35
35
  )
36
36
  end
37
37
 
@@ -41,7 +41,7 @@ class Shrine
41
41
  # It performs the same mounting logic that Rack and other web
42
42
  # frameworks use, and is meant for cases where statically mounting the
43
43
  # endpoint in the router isn't enough.
44
- def upload_response(storage_key, env, **options)
44
+ def upload_response(storage_key, env, **)
45
45
  script_name = env["SCRIPT_NAME"]
46
46
  path_info = env["PATH_INFO"]
47
47
 
@@ -49,7 +49,7 @@ class Shrine
49
49
  env["SCRIPT_NAME"] += path_info
50
50
  env["PATH_INFO"] = ""
51
51
 
52
- upload_endpoint(storage_key, **options).call(env)
52
+ upload_endpoint(storage_key, **).call(env)
53
53
  ensure
54
54
  env["SCRIPT_NAME"] = script_name
55
55
  env["PATH_INFO"] = path_info
@@ -15,7 +15,7 @@ class Shrine
15
15
  def _upload(io, **options)
16
16
  upload_options = get_upload_options(io, options)
17
17
 
18
- super(io, **options, upload_options: upload_options)
18
+ super(io, **options, upload_options:)
19
19
  end
20
20
 
21
21
  def get_upload_options(io, options)
@@ -24,21 +24,21 @@ class Shrine
24
24
  attr_reader :errors
25
25
 
26
26
  # Initializes validation errors to an empty array.
27
- def initialize(**options)
27
+ def initialize(**)
28
28
  super
29
29
  @errors = []
30
30
  end
31
31
 
32
32
  # Performs validations after attaching cached file.
33
- def attach_cached(value, validate: nil, **options)
34
- result = super(value, validate: false, **options)
33
+ def attach_cached(value, validate: nil, **)
34
+ result = super(value, validate: false, **)
35
35
  validation(validate)
36
36
  result
37
37
  end
38
38
 
39
39
  # Performs validations after attaching file.
40
- def attach(io, validate: nil, **options)
41
- result = super(io, **options)
40
+ def attach(io, validate: nil, **)
41
+ result = super(io, **)
42
42
  validation(validate)
43
43
  result
44
44
  end
@@ -61,16 +61,16 @@ class Shrine
61
61
  end
62
62
 
63
63
  # Calls #validate_block, passing it accepted parameters.
64
- def _validate(**options)
64
+ def _validate(**)
65
65
  if method(:validate_block).arity.zero?
66
66
  validate_block
67
67
  else
68
- validate_block(**options)
68
+ validate_block(**)
69
69
  end
70
70
  end
71
71
 
72
72
  # Overridden by the `Attacher.validate` block.
73
- def validate_block(**options)
73
+ def validate_block(**)
74
74
  end
75
75
  end
76
76
  end
@@ -59,16 +59,16 @@ class Shrine
59
59
 
60
60
  # Smart versioned URLs, which include the version name in the default
61
61
  # URL, and properly forwards any options to the underlying storage.
62
- def url(version = nil, **options)
62
+ def url(version = nil, **)
63
63
  if file.is_a?(Hash)
64
64
  if version
65
65
  version = version.to_sym
66
66
  if file.key?(version)
67
- file[version].url(**options)
67
+ file[version].url(**)
68
68
  elsif fallback = shrine_class.version_fallbacks[version]
69
- url(fallback, **options)
69
+ url(fallback, **)
70
70
  else
71
- default_url(**options, version: version)
71
+ default_url(**, version:)
72
72
  end
73
73
  else
74
74
  raise Error, "must call Shrine::Attacher#url with the name of the version"
@@ -76,12 +76,12 @@ class Shrine
76
76
  else
77
77
  if version
78
78
  if file && shrine_class.opts[:versions][:fallback_to_original]
79
- file.url(**options)
79
+ file.url(**)
80
80
  else
81
- default_url(**options, version: version)
81
+ default_url(**, version:)
82
82
  end
83
83
  else
84
- super(**options)
84
+ super(**)
85
85
  end
86
86
  end
87
87
  end
@@ -130,7 +130,7 @@ class Shrine
130
130
 
131
131
  def map_file(object, transform_keys: :to_sym)
132
132
  if object.is_a?(Hash) || object.is_a?(Array)
133
- deep_map(object, transform_keys: transform_keys) do |path, value|
133
+ deep_map(object, transform_keys:) do |path, value|
134
134
  yield path, value unless value.is_a?(Hash) || value.is_a?(Array)
135
135
  end
136
136
  elsif object
@@ -150,7 +150,7 @@ class Shrine
150
150
  key = key.send(transform_keys)
151
151
  result = yield [*path, key], value
152
152
 
153
- hash.merge! key => (result || deep_map(value, [*path, key], transform_keys: transform_keys, &block))
153
+ hash.merge! key => (result || deep_map(value, [*path, key], transform_keys:, &block))
154
154
  end
155
155
  elsif object.is_a?(Array)
156
156
  result = yield path, object
@@ -160,7 +160,7 @@ class Shrine
160
160
  object.map.with_index do |value, idx|
161
161
  result = yield [*path, idx], value
162
162
 
163
- result || deep_map(value, [*path, idx], transform_keys: transform_keys, &block)
163
+ result || deep_map(value, [*path, idx], transform_keys:, &block)
164
164
  end
165
165
  else
166
166
  result = yield path, object
@@ -18,26 +18,18 @@ class Shrine
18
18
  plugin
19
19
  end
20
20
 
21
- # Delegate call to the plugin in a way that works across Ruby versions.
22
- def self.load_dependencies(plugin, uploader, *args, **kwargs, &block)
21
+ # Delegate to the plugin's `load_dependencies` method.
22
+ def self.load_dependencies(plugin, uploader, ...)
23
23
  return unless plugin.respond_to?(:load_dependencies)
24
24
 
25
- if kwargs.any?
26
- plugin.load_dependencies(uploader, *args, **kwargs, &block)
27
- else
28
- plugin.load_dependencies(uploader, *args, &block)
29
- end
25
+ plugin.load_dependencies(uploader, ...)
30
26
  end
31
27
 
32
- # Delegate call to the plugin in a way that works across Ruby versions.
33
- def self.configure(plugin, uploader, *args, **kwargs, &block)
28
+ # Delegate to the plugin's `load_dependencies` method.
29
+ def self.configure(plugin, uploader, ...)
34
30
  return unless plugin.respond_to?(:configure)
35
31
 
36
- if kwargs.any?
37
- plugin.configure(uploader, *args, **kwargs, &block)
38
- else
39
- plugin.configure(uploader, *args, &block)
40
- end
32
+ plugin.configure(uploader, ...)
41
33
  end
42
34
 
43
35
  # Register the given plugin with Shrine, so that it can be loaded using
@@ -59,8 +59,8 @@ class Shrine
59
59
 
60
60
  # Opens the file on the given location in read mode. Accepts additional
61
61
  # `File.open` arguments.
62
- def open(id, **options)
63
- path(id).open(binmode: true, **options)
62
+ def open(id, **)
63
+ path(id).open(binmode: true, **)
64
64
  rescue Errno::ENOENT
65
65
  raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
66
66
  end
@@ -75,7 +75,7 @@ class Shrine
75
75
  # from the returned path (e.g. #directory can be set to "public" folder).
76
76
  # Both cases accept a `:host` value which will be prefixed to the
77
77
  # generated path.
78
- def url(id, host: nil, **options)
78
+ def url(id, host: nil, **)
79
79
  path = (prefix ? relative_path(id) : path(id)).to_s
80
80
  host ? host + path : path
81
81
  end
@@ -123,7 +123,7 @@ class Shrine
123
123
  # Cleans all empty subdirectories up the hierarchy.
124
124
  def clean(path)
125
125
  path.dirname.ascend do |pathname|
126
- if dir_empty?(pathname) && pathname != directory
126
+ if Dir.empty?(pathname) && pathname != directory
127
127
  pathname.rmdir
128
128
  else
129
129
  break
@@ -175,19 +175,6 @@ class Shrine
175
175
  .find
176
176
  .each { |path| yield path if path.file? }
177
177
  end
178
-
179
- if RUBY_VERSION >= "2.4"
180
- def dir_empty?(path)
181
- Dir.empty?(path)
182
- end
183
- else
184
- # :nocov:
185
- def dir_empty?(path)
186
- Dir.foreach(path) { |x| return false unless [".", ".."].include?(x) }
187
- true
188
- end
189
- # :nocov:
190
- end
191
178
  end
192
179
  end
193
180
  end
@@ -24,18 +24,18 @@ class Shrine
24
24
  #
25
25
  # Shrine::Storage::Linter.new(storage).call(->{File.open("test/fixtures/image.jpg")})
26
26
  class Linter
27
- def self.call(*args)
28
- new(*args).call
27
+ def self.call(*)
28
+ new(*).call
29
29
  end
30
30
 
31
- def initialize(storage, action: :error, nonexisting: "nonexisting")
31
+ def initialize(storage, action: :error, nonexisting: String.new("nonexisting"))
32
32
  @storage = storage
33
33
  @action = action
34
34
  @nonexisting = nonexisting
35
35
  end
36
36
 
37
37
  def call(io_factory = default_io_factory)
38
- storage.upload(io_factory.call, id = "foo", shrine_metadata: { "foo" => "bar" })
38
+ storage.upload(io_factory.call, id = String.new("foo"), shrine_metadata: { "foo" => "bar" })
39
39
 
40
40
  lint_open(id)
41
41
  lint_exists(id)
@@ -43,9 +43,9 @@ class Shrine
43
43
  lint_delete(id)
44
44
 
45
45
  if storage.respond_to?(:delete_prefixed)
46
- storage.upload(io_factory.call, id1 = "a/a/a")
47
- storage.upload(io_factory.call, id2 = "a/a/b")
48
- storage.upload(io_factory.call, id3 = "a/aaa/a")
46
+ storage.upload(io_factory.call, id1 = String.new("a/a/a"))
47
+ storage.upload(io_factory.call, id2 = String.new("a/a/b"))
48
+ storage.upload(io_factory.call, id3 = String.new("a/aaa/a"))
49
49
 
50
50
  lint_delete_prefixed(prefix: "a/a/",
51
51
  expect_deleted: [id1, id2],
@@ -55,7 +55,7 @@ class Shrine
55
55
  end
56
56
 
57
57
  if storage.respond_to?(:clear!)
58
- storage.upload(io_factory.call, id = "quux".dup)
58
+ storage.upload(io_factory.call, id = String.new("quux"))
59
59
  lint_clear(id)
60
60
  end
61
61
 
@@ -17,9 +17,7 @@ class Shrine
17
17
  end
18
18
 
19
19
  def open(id, **)
20
- io = StringIO.new(store.fetch(id))
21
- io.set_encoding(io.string.encoding) # Ruby 2.7.0 – https://bugs.ruby-lang.org/issues/16497
22
- io
20
+ StringIO.new(store.fetch(id))
23
21
  rescue KeyError
24
22
  raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
25
23
  end
@@ -68,10 +68,11 @@ class Shrine
68
68
  # [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
69
69
  # [`Aws::S3::Client#initialize`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#initialize-instance_method
70
70
  # [configuring AWS SDK]: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html
71
- def initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS, **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, **)
72
72
  raise ArgumentError, "the :bucket option is nil" unless bucket
73
73
 
74
- @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)
75
76
  @bucket = Aws::S3::Bucket.new(name: bucket, client: @client)
76
77
  @prefix = prefix
77
78
  @upload_options = upload_options
@@ -97,8 +98,7 @@ class Shrine
97
98
  options[:content_disposition] = ContentDisposition.inline(filename) if filename
98
99
  options[:acl] = "public-read" if public
99
100
 
100
- options.merge!(@upload_options)
101
- options.merge!(upload_options)
101
+ options.merge!(@upload_options, upload_options)
102
102
 
103
103
  if copyable?(io)
104
104
  copy(io, id, **options)
@@ -116,8 +116,8 @@ class Shrine
116
116
  # Any additional options are forwarded to [`Aws::S3::Object#get`].
117
117
  #
118
118
  # [`Aws::S3::Object#get`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
119
- def open(id, rewindable: true, encoding: nil, **options)
120
- chunks, length = get(id, **options)
119
+ def open(id, rewindable: true, encoding: nil, **)
120
+ chunks, length = get(id, **)
121
121
 
122
122
  Down::ChunkedIO.new(chunks: chunks, rewindable: rewindable, size: length, encoding: encoding)
123
123
  rescue Aws::S3::Errors::NoSuchKey
@@ -145,11 +145,11 @@ class Shrine
145
145
  #
146
146
  # [`Aws::S3::Object#presigned_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
147
147
  # [`Aws::S3::Object#public_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#public_url-instance_method
148
- def url(id, public: self.public, host: nil, **options)
148
+ def url(id, public: self.public, host: nil, **)
149
149
  if public || signer
150
- url = object(id).public_url(**options)
150
+ url = object(id).public_url(**)
151
151
  else
152
- url = object(id).presigned_url(:get, **options)
152
+ url = object(id).presigned_url(:get, **)
153
153
  end
154
154
 
155
155
  if host
@@ -159,7 +159,7 @@ class Shrine
159
159
  end
160
160
 
161
161
  if signer
162
- url = signer.call(url, **options)
162
+ url = signer.call(url, **)
163
163
  end
164
164
 
165
165
  url
@@ -190,8 +190,7 @@ class Shrine
190
190
  options = {}
191
191
  options[:acl] = "public-read" if public
192
192
 
193
- options.merge!(@upload_options)
194
- options.merge!(presign_options)
193
+ options.merge!(@upload_options, presign_options)
195
194
 
196
195
  send(:"presign_#{method}", id, options)
197
196
  end
@@ -231,12 +230,21 @@ class Shrine
231
230
 
232
231
  private
233
232
 
234
- # Uploads the file to S3. Uses multipart upload for large files.
235
- def put(io, id, **options)
236
- if io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
237
- object(id).put(body: io, **options)
238
- else # multipart upload
239
- 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|
240
248
  IO.copy_stream(io, write_stream)
241
249
  end
242
250
  end
@@ -255,8 +263,7 @@ class Shrine
255
263
  options.merge!(multipart_copy: true, content_length: io.size)
256
264
  end
257
265
 
258
- options.merge!(@copy_options)
259
- options.merge!(copy_options)
266
+ options.merge!(@copy_options, copy_options)
260
267
 
261
268
  object(id).copy_from(io.storage.object(io.id), **options)
262
269
  end
@@ -274,13 +281,14 @@ class Shrine
274
281
 
275
282
  # When any of these options are specified, the corresponding request
276
283
  # headers must be included in the upload request.
277
- headers = {}
278
- headers["Content-Length"] = options[:content_length] if options[:content_length]
279
- headers["Content-Type"] = options[:content_type] if options[:content_type]
280
- headers["Content-Disposition"] = options[:content_disposition] if options[:content_disposition]
281
- headers["Content-Encoding"] = options[:content_encoding] if options[:content_encoding]
282
- headers["Content-Language"] = options[:content_language] if options[:content_language]
283
- 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
284
292
 
285
293
  { method: :put, url: url, headers: headers }
286
294
  end
@@ -301,8 +309,8 @@ class Shrine
301
309
  # object before all content is downloaded, so we hack our way around it.
302
310
  # This way get the content length without an additional HEAD request.
303
311
  if Gem::Version.new(Aws::CORE_GEM_VERSION) >= Gem::Version.new("3.104.0")
304
- def get(id, **params)
305
- enum = object(id).enum_for(:get, **params)
312
+ def get(id, **)
313
+ enum = object(id).enum_for(:get, **)
306
314
 
307
315
  begin
308
316
  content_length = Integer(enum.peek.last["content-length"])
@@ -315,8 +323,8 @@ class Shrine
315
323
  [chunks, content_length]
316
324
  end
317
325
  else
318
- def get(id, **params)
319
- 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), **)
320
328
 
321
329
  body = req.enum_for(:send_request)
322
330
  begin
@@ -359,10 +367,10 @@ class Shrine
359
367
 
360
368
  # Save the encryption client and continue initialization with normal
361
369
  # client.
362
- def initialize(client: nil, **options)
370
+ def initialize(client: nil, **)
363
371
  return super unless client.class.name.start_with?("Aws::S3::Encryption")
364
372
 
365
- super(client: client.client, **options)
373
+ super(client: client.client, **)
366
374
  @encryption_client = client
367
375
  end
368
376
 
@@ -370,19 +378,19 @@ class Shrine
370
378
 
371
379
  # Encryption client doesn't support multipart uploads, so we always use
372
380
  # #put_object.
373
- def put(io, id, **options)
381
+ def put(io, id, **)
374
382
  return super unless encryption_client
375
383
 
376
- 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), **)
377
385
  end
378
386
 
379
- def get(id, **options)
387
+ def get(id, **)
380
388
  return super unless encryption_client
381
389
 
382
390
  # Encryption client v2 warns against streaming download, so we first
383
391
  # download all content into a file.
384
392
  tempfile = Tempfile.new("shrine-s3", binmode: true)
385
- 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), **)
386
394
  tempfile.rewind
387
395
 
388
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
@@ -179,8 +181,8 @@ class Shrine
179
181
  end
180
182
 
181
183
  # Calls `#url` on the storage, forwarding any given URL options.
182
- def url(**options)
183
- storage.url(id, **options)
184
+ def url(**)
185
+ storage.url(id, **)
184
186
  end
185
187
 
186
188
  # Calls `#exists?` on the storage, which checks whether the file exists
@@ -190,8 +192,8 @@ class Shrine
190
192
  end
191
193
 
192
194
  # Uploads a new file to this file's location and returns it.
193
- def replace(io, **options)
194
- uploader.upload(io, **options, location: id)
195
+ def replace(io, **)
196
+ uploader.upload(io, **, location: id)
195
197
  end
196
198
 
197
199
  # Calls `#delete` on the storage, which deletes the file from the
@@ -207,12 +209,12 @@ class Shrine
207
209
 
208
210
  # Returns the data hash in the JSON format. Suitable for storing in a
209
211
  # database column or passing to a background job.
210
- def to_json(*args)
211
- data.to_json(*args)
212
+ def to_json(*)
213
+ data.to_json(*)
212
214
  end
213
215
 
214
216
  # Conform to ActiveSupport's JSON interface.
215
- def as_json(*args)
217
+ def as_json(*)
216
218
  data
217
219
  end
218
220
 
@@ -263,8 +265,8 @@ class Shrine
263
265
  @io ||= _open
264
266
  end
265
267
 
266
- def _open(**options)
267
- storage.open(id, **options)
268
+ def _open(**)
269
+ storage.open(id, **)
268
270
  end
269
271
  end
270
272
 
@@ -7,7 +7,7 @@ class Shrine
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 3
10
- MINOR = 6
10
+ MINOR = 7
11
11
  TINY = 0
12
12
  PRE = nil
13
13