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
@@ -21,12 +21,12 @@ class Shrine
21
21
  # The `storage_key` needs to be one of the registered Shrine storages.
22
22
  # Additional options can be given to override the options given on
23
23
  # plugin initialization.
24
- def presign_endpoint(storage_key, **options)
24
+ def presign_endpoint(storage_key, **)
25
25
  Shrine::PresignEndpoint.new(
26
26
  shrine_class: self,
27
27
  storage_key: storage_key,
28
28
  **opts[:presign_endpoint],
29
- **options,
29
+ **,
30
30
  )
31
31
  end
32
32
 
@@ -36,7 +36,7 @@ class Shrine
36
36
  # It performs the same mounting logic that Rack and other web
37
37
  # frameworks use, and is meant for cases where statically mounting the
38
38
  # endpoint in the router isn't enough.
39
- def presign_response(storage_key, env, **options)
39
+ def presign_response(storage_key, env, **)
40
40
  script_name = env["SCRIPT_NAME"]
41
41
  path_info = env["PATH_INFO"]
42
42
 
@@ -44,7 +44,7 @@ class Shrine
44
44
  env["SCRIPT_NAME"] += path_info
45
45
  env["PATH_INFO"] = ""
46
46
 
47
- presign_endpoint(storage_key, **options).call(env)
47
+ presign_endpoint(storage_key, **).call(env)
48
48
  ensure
49
49
  env["SCRIPT_NAME"] = script_name
50
50
  env["PATH_INFO"] = path_info
@@ -91,7 +91,9 @@ class Shrine
91
91
  end
92
92
  end
93
93
 
94
- headers["Content-Length"] ||= body.map(&:bytesize).inject(0, :+).to_s
94
+ headers = Rack::Headers[headers] if Rack.release >= "3"
95
+ headers["Content-Length"] ||= body.respond_to?(:bytesize) ? body.bytesize.to_s :
96
+ body.map(&:bytesize).inject(0, :+).to_s
95
97
 
96
98
  [status, headers, body]
97
99
  end
@@ -158,16 +160,17 @@ class Shrine
158
160
  # headers, and a body enumerable. If `:rack_response` option is given,
159
161
  # calls that instead.
160
162
  def make_response(object, request)
161
- if @rack_response
162
- response = @rack_response.call(object, request)
163
+ status, headers, body = if @rack_response
164
+ @rack_response.call(object, request)
163
165
  else
164
- response = [200, { "Content-Type" => CONTENT_TYPE_JSON }, [object.to_json]]
166
+ [200, { "Content-Type" => CONTENT_TYPE_JSON }, [object.to_json]]
165
167
  end
166
168
 
169
+ headers = Rack::Headers[headers] if Rack.release >= "3"
167
170
  # prevent browsers from caching the response
168
- response[1]["Cache-Control"] = "no-store" unless response[1].key?("Cache-Control")
171
+ headers["Cache-Control"] = "no-store" unless headers.key?("Cache-Control")
169
172
 
170
- response
173
+ [status, headers, body]
171
174
  end
172
175
 
173
176
  # Used for early returning an error response.
@@ -10,8 +10,8 @@ class Shrine
10
10
  end
11
11
 
12
12
  module InstanceMethods
13
- def generate_location(io, **options)
14
- pretty_location(io, **options)
13
+ def generate_location(io, **)
14
+ pretty_location(io, **)
15
15
  end
16
16
 
17
17
  def pretty_location(io, name: nil, record: nil, version: nil, derivative: nil, identifier: nil, metadata: {}, **)
@@ -18,14 +18,14 @@ class Shrine
18
18
  end
19
19
 
20
20
  module InstanceMethods
21
- def upload(io, process: true, **options)
21
+ def upload(io, process: true, **)
22
22
  if process
23
- input = process(io, **options)
23
+ input = process(io, **)
24
24
  else
25
25
  input = io
26
26
  end
27
27
 
28
- super(input, **options)
28
+ super(input, **)
29
29
  end
30
30
 
31
31
  private
@@ -24,9 +24,9 @@ class Shrine
24
24
  module AttacherMethods
25
25
  # Checks whether a file is a Rack file hash, and in that case wraps the
26
26
  # hash in an IO-like object.
27
- def assign(value, **options)
27
+ def assign(value, **)
28
28
  if rack_file?(value)
29
- assign shrine_class.rack_file(value), **options
29
+ assign shrine_class.rack_file(value), **
30
30
  else
31
31
  super
32
32
  end
@@ -10,8 +10,8 @@ class Shrine
10
10
  module RackResponse
11
11
  module FileMethods
12
12
  # Returns a Rack response triple for the uploaded file.
13
- def to_rack_response(**options)
14
- FileResponse.new(self).call(**options)
13
+ def to_rack_response(**)
14
+ FileResponse.new(self).call(**)
15
15
  end
16
16
  end
17
17
 
@@ -32,6 +32,8 @@ class Shrine
32
32
  headers = rack_headers(**options)
33
33
  body = rack_body(**options)
34
34
 
35
+ headers = Rack::Headers[headers] if Rack.release >= "3"
36
+
35
37
  [status, headers, body]
36
38
  end
37
39
 
@@ -73,7 +75,7 @@ class Shrine
73
75
  def content_disposition(disposition, filename)
74
76
  filename ||= file.original_filename || file.id.split("/").last
75
77
 
76
- ContentDisposition.format(disposition: disposition, filename: filename)
78
+ ContentDisposition.format(disposition:, filename:)
77
79
  end
78
80
 
79
81
  # Value for the "Content-Range" header.
@@ -96,7 +98,7 @@ class Shrine
96
98
  # Returns an object that responds to #each and #close, which yields
97
99
  # contents of the file.
98
100
  def rack_body(range: nil, **)
99
- FileBody.new(file, range: range)
101
+ FileBody.new(file, range:)
100
102
  end
101
103
 
102
104
  # Retrieves a range value parsed from HTTP "Range" header.
@@ -141,6 +143,10 @@ class Shrine
141
143
  file.close
142
144
  end
143
145
 
146
+ def bytesize
147
+ each.inject(0) { |sum, chunk| sum += chunk.length }
148
+ end
149
+
144
150
  # Rack::Sendfile is activated when response body responds to #to_path.
145
151
  def respond_to_missing?(name, include_private = false)
146
152
  name == :to_path && path
@@ -5,19 +5,19 @@ class Shrine
5
5
  # Documentation can be found on https://shrinerb.com/docs/plugins/refresh_metadata
6
6
  module RefreshMetadata
7
7
  module AttacherMethods
8
- def refresh_metadata!(**options)
9
- file!.refresh_metadata!(**context, **options)
8
+ def refresh_metadata!(**)
9
+ file!.refresh_metadata!(**context, **)
10
10
  set(file) # trigger model write
11
11
  end
12
12
  end
13
13
 
14
14
  module FileMethods
15
- def refresh_metadata!(**options)
16
- return open { refresh_metadata!(**options) } unless opened?
15
+ def refresh_metadata!(replace: false, **)
16
+ return open { refresh_metadata!(replace:, **) } unless opened?
17
17
 
18
- refreshed_metadata = uploader.send(:get_metadata, self, metadata: true, **options)
18
+ refreshed_metadata = uploader.send(:get_metadata, self, metadata: true, **)
19
19
 
20
- @metadata = @metadata.merge(refreshed_metadata)
20
+ @metadata = replace ? refreshed_metadata : @metadata.merge(refreshed_metadata)
21
21
  end
22
22
  end
23
23
  end
@@ -84,11 +84,11 @@ class Shrine
84
84
  # Downloads the remote file and assigns it. If download failed, sets
85
85
  # the error message and assigns the url to an instance variable so that
86
86
  # it shows up in the form.
87
- def assign_remote_url(url, downloader: {}, **options)
87
+ def assign_remote_url(url, downloader: {}, **)
88
88
  return if url == "" || url.nil?
89
89
 
90
90
  downloaded_file = shrine_class.remote_url(url, **downloader)
91
- attach_cached(downloaded_file, **options)
91
+ attach_cached(downloaded_file, **)
92
92
  rescue DownloadError => error
93
93
  errors.clear << remote_url_error_message(url, error)
94
94
  false
@@ -110,7 +110,7 @@ class Shrine
110
110
  # Generates an error message for failed remote URL download.
111
111
  def remote_url_error_message(url, error)
112
112
  message = shrine_class.opts[:remote_url][:error_message]
113
- message = message.call *[url, error].take(message.arity.abs) if message.respond_to?(:call)
113
+ message = message.call(*[url, error].take(message.arity.abs)) if message.respond_to?(:call)
114
114
  message || "download failed: #{error.message}"
115
115
  end
116
116
  end
@@ -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
@@ -91,7 +91,9 @@ class Shrine
91
91
  handle_request(request)
92
92
  end
93
93
 
94
- headers["Content-Length"] ||= body.map(&:bytesize).inject(0, :+).to_s
94
+ headers = Rack::Headers[headers] if Rack.release >= "3"
95
+ headers["Content-Length"] ||= body.respond_to?(:bytesize) ? body.bytesize.to_s :
96
+ body.map(&:bytesize).inject(0, :+).to_s
95
97
 
96
98
  [status, headers, body]
97
99
  end
@@ -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