shrine 2.19.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +485 -43
  3. data/LICENSE.txt +1 -1
  4. data/README.md +81 -977
  5. data/doc/advantages.md +231 -204
  6. data/doc/attacher.md +304 -153
  7. data/doc/carrierwave.md +297 -226
  8. data/doc/changing_derivatives.md +308 -0
  9. data/doc/changing_location.md +102 -21
  10. data/doc/changing_storage.md +110 -0
  11. data/doc/creating_persistence_plugins.md +132 -0
  12. data/doc/creating_plugins.md +43 -23
  13. data/doc/creating_storages.md +19 -5
  14. data/doc/design.md +147 -97
  15. data/doc/direct_s3.md +38 -28
  16. data/doc/external/articles.md +63 -0
  17. data/doc/external/extensions.md +53 -0
  18. data/doc/external/misc.md +32 -0
  19. data/doc/getting_started.md +1115 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +62 -34
  22. data/doc/paperclip.md +384 -262
  23. data/doc/plugins/activerecord.md +177 -46
  24. data/doc/plugins/add_metadata.md +139 -38
  25. data/doc/plugins/atomic_helpers.md +217 -0
  26. data/doc/plugins/backgrounding.md +156 -98
  27. data/doc/plugins/cached_attachment_data.md +7 -5
  28. data/doc/plugins/column.md +121 -0
  29. data/doc/plugins/data_uri.md +23 -22
  30. data/doc/plugins/default_storage.md +36 -10
  31. data/doc/plugins/default_url.md +30 -13
  32. data/doc/plugins/delete_raw.md +4 -2
  33. data/doc/plugins/derivation_endpoint.md +162 -101
  34. data/doc/plugins/derivatives.md +829 -0
  35. data/doc/plugins/determine_mime_type.md +4 -2
  36. data/doc/plugins/download_endpoint.md +64 -8
  37. data/doc/plugins/dynamic_storage.md +5 -3
  38. data/doc/plugins/entity.md +263 -0
  39. data/doc/plugins/form_assign.md +55 -0
  40. data/doc/plugins/included.md +31 -8
  41. data/doc/plugins/infer_extension.md +21 -10
  42. data/doc/plugins/instrumentation.md +38 -16
  43. data/doc/plugins/keep_files.md +14 -17
  44. data/doc/plugins/metadata_attributes.md +42 -13
  45. data/doc/plugins/mirroring.md +118 -0
  46. data/doc/plugins/model.md +210 -0
  47. data/doc/plugins/module_include.md +4 -2
  48. data/doc/plugins/multi_cache.md +24 -0
  49. data/doc/plugins/persistence.md +101 -0
  50. data/doc/plugins/presign_endpoint.md +9 -4
  51. data/doc/plugins/pretty_location.md +16 -3
  52. data/doc/plugins/processing.md +4 -2
  53. data/doc/plugins/rack_file.md +8 -2
  54. data/doc/plugins/rack_response.md +6 -2
  55. data/doc/plugins/recache.md +4 -2
  56. data/doc/plugins/refresh_metadata.md +49 -9
  57. data/doc/plugins/remote_url.md +84 -47
  58. data/doc/plugins/remove_attachment.md +27 -6
  59. data/doc/plugins/remove_invalid.md +21 -6
  60. data/doc/plugins/restore_cached_data.md +11 -3
  61. data/doc/plugins/sequel.md +159 -35
  62. data/doc/plugins/signature.md +16 -5
  63. data/doc/plugins/store_dimensions.md +14 -2
  64. data/doc/plugins/tempfile.md +4 -2
  65. data/doc/plugins/type_predicates.md +96 -0
  66. data/doc/plugins/upload_endpoint.md +13 -13
  67. data/doc/plugins/upload_options.md +6 -4
  68. data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
  69. data/doc/plugins/validation.md +97 -0
  70. data/doc/plugins/validation_helpers.md +16 -13
  71. data/doc/plugins/versions.md +15 -19
  72. data/doc/processing.md +438 -221
  73. data/doc/refile.md +185 -167
  74. data/doc/release_notes/1.0.0.md +4 -0
  75. data/doc/release_notes/1.1.0.md +6 -2
  76. data/doc/release_notes/1.2.0.md +4 -0
  77. data/doc/release_notes/1.3.0.md +4 -0
  78. data/doc/release_notes/1.4.0.md +4 -0
  79. data/doc/release_notes/1.4.1.md +4 -0
  80. data/doc/release_notes/1.4.2.md +4 -0
  81. data/doc/release_notes/2.0.0.md +4 -0
  82. data/doc/release_notes/2.0.1.md +4 -0
  83. data/doc/release_notes/2.1.0.md +4 -0
  84. data/doc/release_notes/2.1.1.md +4 -0
  85. data/doc/release_notes/2.10.0.md +4 -0
  86. data/doc/release_notes/2.10.1.md +4 -0
  87. data/doc/release_notes/2.11.0.md +4 -0
  88. data/doc/release_notes/2.12.0.md +4 -0
  89. data/doc/release_notes/2.13.0.md +4 -0
  90. data/doc/release_notes/2.14.0.md +5 -1
  91. data/doc/release_notes/2.15.0.md +11 -7
  92. data/doc/release_notes/2.16.0.md +4 -0
  93. data/doc/release_notes/2.17.0.md +4 -0
  94. data/doc/release_notes/2.18.0.md +4 -0
  95. data/doc/release_notes/2.19.0.md +6 -3
  96. data/doc/release_notes/2.2.0.md +4 -0
  97. data/doc/release_notes/2.3.0.md +4 -0
  98. data/doc/release_notes/2.3.1.md +4 -0
  99. data/doc/release_notes/2.4.0.md +4 -0
  100. data/doc/release_notes/2.4.1.md +4 -0
  101. data/doc/release_notes/2.5.0.md +4 -0
  102. data/doc/release_notes/2.6.0.md +4 -0
  103. data/doc/release_notes/2.6.1.md +4 -0
  104. data/doc/release_notes/2.7.0.md +4 -0
  105. data/doc/release_notes/2.8.0.md +4 -0
  106. data/doc/release_notes/2.9.0.md +4 -0
  107. data/doc/release_notes/3.0.0.md +981 -0
  108. data/doc/release_notes/3.0.1.md +22 -0
  109. data/doc/release_notes/3.1.0.md +73 -0
  110. data/doc/release_notes/3.2.0.md +96 -0
  111. data/doc/release_notes/3.2.1.md +31 -0
  112. data/doc/release_notes/3.2.2.md +14 -0
  113. data/doc/release_notes/3.3.0.md +105 -0
  114. data/doc/release_notes/3.4.0.md +35 -0
  115. data/doc/retrieving_uploads.md +4 -1
  116. data/doc/securing_uploads.md +60 -37
  117. data/doc/storage/file_system.md +20 -3
  118. data/doc/storage/memory.md +19 -0
  119. data/doc/storage/s3.md +117 -83
  120. data/doc/testing.md +124 -144
  121. data/doc/upgrading_to_3.md +710 -0
  122. data/doc/validation.md +54 -90
  123. data/lib/shrine/attacher.rb +287 -171
  124. data/lib/shrine/attachment.rb +13 -46
  125. data/lib/shrine/plugins/_persistence.rb +93 -0
  126. data/lib/shrine/plugins/activerecord.rb +77 -34
  127. data/lib/shrine/plugins/add_metadata.rb +25 -17
  128. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  129. data/lib/shrine/plugins/backgrounding.rb +77 -113
  130. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  131. data/lib/shrine/plugins/column.rb +102 -0
  132. data/lib/shrine/plugins/data_uri.rb +38 -36
  133. data/lib/shrine/plugins/default_storage.rb +45 -15
  134. data/lib/shrine/plugins/default_url.rb +12 -24
  135. data/lib/shrine/plugins/default_url_options.rb +3 -30
  136. data/lib/shrine/plugins/delete_raw.rb +10 -16
  137. data/lib/shrine/plugins/derivation_endpoint.rb +89 -134
  138. data/lib/shrine/plugins/derivatives.rb +637 -0
  139. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  140. data/lib/shrine/plugins/download_endpoint.rb +109 -133
  141. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  142. data/lib/shrine/plugins/entity.rb +152 -0
  143. data/lib/shrine/plugins/form_assign.rb +108 -0
  144. data/lib/shrine/plugins/included.rb +6 -6
  145. data/lib/shrine/plugins/infer_extension.rb +13 -20
  146. data/lib/shrine/plugins/instrumentation.rb +54 -42
  147. data/lib/shrine/plugins/keep_files.rb +3 -15
  148. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  149. data/lib/shrine/plugins/mirroring.rb +142 -0
  150. data/lib/shrine/plugins/model.rb +158 -0
  151. data/lib/shrine/plugins/module_include.rb +3 -3
  152. data/lib/shrine/plugins/multi_cache.rb +27 -0
  153. data/lib/shrine/plugins/presign_endpoint.rb +18 -22
  154. data/lib/shrine/plugins/pretty_location.rb +15 -9
  155. data/lib/shrine/plugins/processing.rb +22 -9
  156. data/lib/shrine/plugins/rack_file.rb +2 -42
  157. data/lib/shrine/plugins/rack_response.rb +15 -10
  158. data/lib/shrine/plugins/recache.rb +6 -5
  159. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  160. data/lib/shrine/plugins/remote_url.rb +49 -49
  161. data/lib/shrine/plugins/remove_attachment.rb +10 -6
  162. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  163. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  164. data/lib/shrine/plugins/sequel.rb +86 -36
  165. data/lib/shrine/plugins/signature.rb +10 -16
  166. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  167. data/lib/shrine/plugins/tempfile.rb +1 -3
  168. data/lib/shrine/plugins/type_predicates.rb +113 -0
  169. data/lib/shrine/plugins/upload_endpoint.rb +25 -23
  170. data/lib/shrine/plugins/upload_options.rb +14 -15
  171. data/lib/shrine/plugins/url_options.rb +31 -0
  172. data/lib/shrine/plugins/validation.rb +80 -0
  173. data/lib/shrine/plugins/validation_helpers.rb +34 -57
  174. data/lib/shrine/plugins/versions.rb +107 -87
  175. data/lib/shrine/plugins.rb +22 -0
  176. data/lib/shrine/storage/file_system.rb +46 -64
  177. data/lib/shrine/storage/linter.rb +42 -7
  178. data/lib/shrine/storage/memory.rb +49 -0
  179. data/lib/shrine/storage/s3.rb +154 -158
  180. data/lib/shrine/uploaded_file.rb +28 -30
  181. data/lib/shrine/version.rb +3 -3
  182. data/lib/shrine.rb +86 -149
  183. data/shrine.gemspec +9 -10
  184. metadata +79 -83
  185. data/doc/migrating_storage.md +0 -76
  186. data/doc/plugins/backup.md +0 -31
  187. data/doc/plugins/copy.md +0 -24
  188. data/doc/plugins/delete_promoted.md +0 -12
  189. data/doc/plugins/direct_upload.md +0 -172
  190. data/doc/plugins/hooks.md +0 -58
  191. data/doc/plugins/logging.md +0 -42
  192. data/doc/plugins/migration_helpers.md +0 -60
  193. data/doc/plugins/moving.md +0 -19
  194. data/doc/plugins/multi_delete.md +0 -20
  195. data/doc/plugins/parallelize.md +0 -16
  196. data/doc/plugins/parsed_json.md +0 -23
  197. data/doc/regenerating_versions.md +0 -143
  198. data/lib/shrine/plugins/background_helpers.rb +0 -5
  199. data/lib/shrine/plugins/backup.rb +0 -90
  200. data/lib/shrine/plugins/copy.rb +0 -50
  201. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  202. data/lib/shrine/plugins/direct_upload.rb +0 -217
  203. data/lib/shrine/plugins/hooks.rb +0 -90
  204. data/lib/shrine/plugins/logging.rb +0 -142
  205. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  206. data/lib/shrine/plugins/moving.rb +0 -57
  207. data/lib/shrine/plugins/multi_delete.rb +0 -32
  208. data/lib/shrine/plugins/parallelize.rb +0 -78
  209. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -6,7 +6,7 @@ require "pathname"
6
6
  class Shrine
7
7
  module Storage
8
8
  class FileSystem
9
- attr_reader :directory, :prefix, :host, :permissions, :directory_permissions
9
+ attr_reader :directory, :prefix, :permissions, :directory_permissions
10
10
 
11
11
  # Initializes a storage for uploading to the filesystem.
12
12
  #
@@ -28,9 +28,7 @@ class Shrine
28
28
  # : By default empty folders inside the directory are automatically
29
29
  # deleted, but if it happens that it causes too much load on the
30
30
  # filesystem, you can set this option to `false`.
31
- def initialize(directory, prefix: nil, host: nil, clean: true, permissions: 0644, directory_permissions: 0755)
32
- Shrine.deprecation("The :host option to Shrine::Storage::FileSystem#initialize is deprecated and will be removed in Shrine 3. Pass :host to FileSystem#url instead, you can also use default_url_options plugin.") if host
33
-
31
+ def initialize(directory, prefix: nil, clean: true, permissions: 0644, directory_permissions: 0755)
34
32
  if prefix
35
33
  @prefix = Pathname(relative(prefix))
36
34
  @directory = Pathname(directory).join(@prefix).expand_path
@@ -38,7 +36,6 @@ class Shrine
38
36
  @directory = Pathname(directory).expand_path
39
37
  end
40
38
 
41
- @host = host
42
39
  @permissions = permissions
43
40
  @directory_permissions = directory_permissions
44
41
  @clean = clean
@@ -51,39 +48,21 @@ class Shrine
51
48
 
52
49
  # Copies the file into the given location.
53
50
  def upload(io, id, move: false, **)
54
- if move && movable?(io, id)
55
- move(io, id)
51
+ if move && movable?(io)
52
+ move(io, path!(id))
56
53
  else
57
54
  IO.copy_stream(io, path!(id))
58
-
59
- path(id).chmod(permissions) if permissions
60
- end
61
- end
62
-
63
- # Moves the file to the given location. This gets called by the `moving`
64
- # plugin.
65
- def move(io, id, **)
66
- if io.respond_to?(:path)
67
- FileUtils.mv io.path, path!(id)
68
- else
69
- FileUtils.mv io.storage.path(io.id), path!(id)
70
- io.storage.clean(io.storage.path(io.id)) if io.storage.clean?
71
55
  end
72
56
 
73
57
  path(id).chmod(permissions) if permissions
74
58
  end
75
59
 
76
- # Returns true if the file is a `File` or a UploadedFile uploaded by the
77
- # FileSystem storage.
78
- def movable?(io, id)
79
- io.respond_to?(:path) ||
80
- (io.is_a?(UploadedFile) && io.storage.is_a?(Storage::FileSystem))
81
- end
82
-
83
60
  # Opens the file on the given location in read mode. Accepts additional
84
61
  # `File.open` arguments.
85
- def open(id, **options, &block)
86
- path(id).open(binmode: true, **options, &block)
62
+ def open(id, **options)
63
+ path(id).open(binmode: true, **options)
64
+ rescue Errno::ENOENT
65
+ raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
87
66
  end
88
67
 
89
68
  # Returns true if the file exists on the filesystem.
@@ -91,6 +70,16 @@ class Shrine
91
70
  path(id).exist?
92
71
  end
93
72
 
73
+ # If #prefix is not present, returns a path composed of #directory and
74
+ # the given `id`. If #prefix is present, it excludes the #directory part
75
+ # from the returned path (e.g. #directory can be set to "public" folder).
76
+ # Both cases accept a `:host` value which will be prefixed to the
77
+ # generated path.
78
+ def url(id, host: nil, **options)
79
+ path = (prefix ? relative_path(id) : path(id)).to_s
80
+ host ? host + path : path
81
+ end
82
+
94
83
  # Delets the file, and by default deletes the containing directory if
95
84
  # it's empty.
96
85
  def delete(id)
@@ -100,14 +89,11 @@ class Shrine
100
89
  rescue Errno::ENOENT
101
90
  end
102
91
 
103
- # If #prefix is not present, returns a path composed of #directory and
104
- # the given `id`. If #prefix is present, it excludes the #directory part
105
- # from the returned path (e.g. #directory can be set to "public" folder).
106
- # Both cases accept a `:host` value which will be prefixed to the
107
- # generated path.
108
- def url(id, host: self.host, **options)
109
- path = (prefix ? relative_path(id) : path(id)).to_s
110
- host ? host + path : path
92
+ # Deletes the specified directory on the filesystem.
93
+ #
94
+ # file_system.delete_prefixed("somekey/derivatives/")
95
+ def delete_prefixed(delete_prefix)
96
+ FileUtils.rm_rf directory.join(delete_prefix)
111
97
  end
112
98
 
113
99
  # Deletes all files from the #directory. If a block is passed in, deletes
@@ -115,15 +101,10 @@ class Shrine
115
101
  #
116
102
  # file_system.clear! # deletes all files and subdirectories in the storage directory
117
103
  # file_system.clear! { |path| path.mtime < Time.now - 7*24*60*60 } # deletes only files older than 1 week
118
- def clear!(older_than: nil, &condition)
119
- if older_than || condition
104
+ def clear!(&condition)
105
+ if condition
120
106
  list_files(directory) do |path|
121
- if older_than
122
- Shrine.deprecation("The :older_than option to FileSystem#clear! is deprecated and will be removed in Shrine 3. You should use a block instead, e.g. `storage.clear! { |path| path.mtime < Time.now - 7*24*60*60 }`.")
123
- next unless path.mtime < older_than
124
- else
125
- next unless condition.call(path)
126
- end
107
+ next unless condition.call(path)
127
108
  path.delete
128
109
  clean(path) if clean?
129
110
  end
@@ -137,15 +118,6 @@ class Shrine
137
118
  directory.join(id.gsub("/", File::SEPARATOR))
138
119
  end
139
120
 
140
- # Catches the deprecated `#download` method.
141
- def method_missing(name, *args, &block)
142
- case name
143
- when :download then deprecated_download(*args, &block)
144
- else
145
- super
146
- end
147
- end
148
-
149
121
  protected
150
122
 
151
123
  # Cleans all empty subdirectories up the hierarchy.
@@ -165,6 +137,24 @@ class Shrine
165
137
 
166
138
  private
167
139
 
140
+ # Moves the file to the given location. This gets called by the `moving`
141
+ # plugin.
142
+ def move(io, path)
143
+ if io.respond_to?(:path)
144
+ FileUtils.mv io.path, path
145
+ else
146
+ FileUtils.mv io.storage.path(io.id), path
147
+ io.storage.clean(io.storage.path(io.id)) if io.storage.clean?
148
+ end
149
+ end
150
+
151
+ # Returns true if the file is a `File` or a UploadedFile uploaded by the
152
+ # FileSystem storage.
153
+ def movable?(io)
154
+ io.respond_to?(:path) ||
155
+ (io.is_a?(UploadedFile) && io.storage.is_a?(Storage::FileSystem))
156
+ end
157
+
168
158
  # Creates all intermediate directories for that location.
169
159
  def path!(id)
170
160
  path = path(id)
@@ -191,20 +181,12 @@ class Shrine
191
181
  Dir.empty?(path)
192
182
  end
193
183
  else
184
+ # :nocov:
194
185
  def dir_empty?(path)
195
186
  Dir.foreach(path) { |x| return false unless [".", ".."].include?(x) }
196
187
  true
197
188
  end
198
- end
199
-
200
- def deprecated_download(id, **options)
201
- Shrine.deprecation("Shrine::Storage::FileSystem#download is deprecated and will be removed in Shrine 3.")
202
- tempfile = Tempfile.new(["shrine-filesystem", File.extname(id)], binmode: true)
203
- open(id, **options) { |file| IO.copy_stream(file, tempfile) }
204
- tempfile.tap(&:open)
205
- rescue
206
- tempfile.close! if tempfile
207
- raise
189
+ # :nocov:
208
190
  end
209
191
  end
210
192
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require "shrine"
4
2
 
5
3
  require "forwardable"
@@ -30,19 +28,32 @@ class Shrine
30
28
  new(*args).call
31
29
  end
32
30
 
33
- def initialize(storage, action: :error)
34
- @storage = storage
35
- @action = action
31
+ def initialize(storage, action: :error, nonexisting: "nonexisting")
32
+ @storage = storage
33
+ @action = action
34
+ @nonexisting = nonexisting
36
35
  end
37
36
 
38
37
  def call(io_factory = default_io_factory)
39
- storage.upload(io_factory.call, id = "foo".dup, {})
38
+ storage.upload(io_factory.call, id = "foo", shrine_metadata: { "foo" => "bar" })
40
39
 
41
40
  lint_open(id)
42
41
  lint_exists(id)
43
42
  lint_url(id)
44
43
  lint_delete(id)
45
44
 
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")
49
+
50
+ lint_delete_prefixed(prefix: "a/a/",
51
+ expect_deleted: [id1, id2],
52
+ expect_remaining: [id3])
53
+
54
+ storage.delete(id3)
55
+ end
56
+
46
57
  if storage.respond_to?(:clear!)
47
58
  storage.upload(io_factory.call, id = "quux".dup)
48
59
  lint_clear(id)
@@ -51,6 +62,8 @@ class Shrine
51
62
  if storage.respond_to?(:presign)
52
63
  lint_presign(id)
53
64
  end
65
+
66
+ true
54
67
  end
55
68
 
56
69
  def lint_open(id)
@@ -58,6 +71,14 @@ class Shrine
58
71
  error :open, "doesn't return a valid IO object" if !io?(opened)
59
72
  error :open, "returns an empty IO object" if opened.read.empty?
60
73
  opened.close
74
+
75
+ begin
76
+ storage.open(@nonexisting)
77
+ error :open, "should raise an exception on nonexisting file"
78
+ rescue Shrine::FileNotFound
79
+ rescue => exception
80
+ error :open, "should raise Shrine::FileNotFound on nonexisting file"
81
+ end
61
82
  end
62
83
 
63
84
  def lint_exists(id)
@@ -86,12 +107,26 @@ class Shrine
86
107
  end
87
108
 
88
109
  def lint_presign(id)
89
- data = storage.presign(id, {})
110
+ data = storage.presign(id)
90
111
  error :presign, "result should be a Hash" unless data.respond_to?(:to_h)
91
112
  error :presign, "result should include :method key" unless data.to_h.key?(:method)
92
113
  error :presign, "result should include :url key" unless data.to_h.key?(:url)
93
114
  end
94
115
 
116
+ def lint_delete_prefixed(prefix:, expect_deleted:, expect_remaining:)
117
+ storage.delete_prefixed(prefix)
118
+
119
+ expect_deleted.each do |key|
120
+ next unless storage.exists?(key)
121
+ error :delete_prefixed, "#{key} still #exists? after #clear_prefix('a/a/')"
122
+ end
123
+
124
+ expect_remaining.each do |key|
125
+ next if storage.exists?(key)
126
+ error :delete_prefixed, "#{key} doesn't #exists? but should after #clear_prefix('a/a/')"
127
+ end
128
+ end
129
+
95
130
  private
96
131
 
97
132
  attr_reader :storage
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shrine"
4
+ require "stringio"
5
+
6
+ class Shrine
7
+ module Storage
8
+ class Memory
9
+ attr_reader :store
10
+
11
+ def initialize(store = {})
12
+ @store = store
13
+ end
14
+
15
+ def upload(io, id, **)
16
+ store[id] = io.read
17
+ end
18
+
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
23
+ rescue KeyError
24
+ raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
25
+ end
26
+
27
+ def exists?(id)
28
+ store.key?(id)
29
+ end
30
+
31
+ def url(id, *)
32
+ "memory://#{id}"
33
+ end
34
+
35
+ def delete(id)
36
+ store.delete(id)
37
+ end
38
+
39
+ def delete_prefixed(delete_prefix)
40
+ delete_prefix = delete_prefix.chomp("/") + "/"
41
+ store.delete_if { |key, _value| key.start_with?(delete_prefix) }
42
+ end
43
+
44
+ def clear!
45
+ store.clear
46
+ end
47
+ end
48
+ end
49
+ end