shrine 2.19.4 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +485 -43
- data/LICENSE.txt +1 -1
- data/README.md +81 -977
- data/doc/advantages.md +231 -204
- data/doc/attacher.md +304 -153
- data/doc/carrierwave.md +297 -226
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +102 -21
- data/doc/changing_storage.md +110 -0
- data/doc/creating_persistence_plugins.md +132 -0
- data/doc/creating_plugins.md +43 -23
- data/doc/creating_storages.md +19 -5
- data/doc/design.md +147 -97
- data/doc/direct_s3.md +38 -28
- data/doc/external/articles.md +63 -0
- data/doc/external/extensions.md +53 -0
- data/doc/external/misc.md +32 -0
- data/doc/getting_started.md +1115 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +62 -34
- data/doc/paperclip.md +384 -262
- data/doc/plugins/activerecord.md +177 -46
- data/doc/plugins/add_metadata.md +139 -38
- data/doc/plugins/atomic_helpers.md +217 -0
- data/doc/plugins/backgrounding.md +156 -98
- data/doc/plugins/cached_attachment_data.md +7 -5
- data/doc/plugins/column.md +121 -0
- data/doc/plugins/data_uri.md +23 -22
- data/doc/plugins/default_storage.md +36 -10
- data/doc/plugins/default_url.md +30 -13
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +162 -101
- data/doc/plugins/derivatives.md +829 -0
- data/doc/plugins/determine_mime_type.md +4 -2
- data/doc/plugins/download_endpoint.md +64 -8
- data/doc/plugins/dynamic_storage.md +5 -3
- data/doc/plugins/entity.md +263 -0
- data/doc/plugins/form_assign.md +55 -0
- data/doc/plugins/included.md +31 -8
- data/doc/plugins/infer_extension.md +21 -10
- data/doc/plugins/instrumentation.md +38 -16
- data/doc/plugins/keep_files.md +14 -17
- data/doc/plugins/metadata_attributes.md +42 -13
- data/doc/plugins/mirroring.md +118 -0
- data/doc/plugins/model.md +210 -0
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +24 -0
- data/doc/plugins/persistence.md +101 -0
- data/doc/plugins/presign_endpoint.md +9 -4
- data/doc/plugins/pretty_location.md +16 -3
- data/doc/plugins/processing.md +4 -2
- data/doc/plugins/rack_file.md +8 -2
- data/doc/plugins/rack_response.md +6 -2
- data/doc/plugins/recache.md +4 -2
- data/doc/plugins/refresh_metadata.md +49 -9
- data/doc/plugins/remote_url.md +84 -47
- data/doc/plugins/remove_attachment.md +27 -6
- data/doc/plugins/remove_invalid.md +21 -6
- data/doc/plugins/restore_cached_data.md +11 -3
- data/doc/plugins/sequel.md +159 -35
- data/doc/plugins/signature.md +16 -5
- data/doc/plugins/store_dimensions.md +14 -2
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +13 -13
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
- data/doc/plugins/validation.md +97 -0
- data/doc/plugins/validation_helpers.md +16 -13
- data/doc/plugins/versions.md +15 -19
- data/doc/processing.md +438 -221
- data/doc/refile.md +185 -167
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +6 -2
- data/doc/release_notes/1.2.0.md +4 -0
- data/doc/release_notes/1.3.0.md +4 -0
- data/doc/release_notes/1.4.0.md +4 -0
- data/doc/release_notes/1.4.1.md +4 -0
- data/doc/release_notes/1.4.2.md +4 -0
- data/doc/release_notes/2.0.0.md +4 -0
- data/doc/release_notes/2.0.1.md +4 -0
- data/doc/release_notes/2.1.0.md +4 -0
- data/doc/release_notes/2.1.1.md +4 -0
- data/doc/release_notes/2.10.0.md +4 -0
- data/doc/release_notes/2.10.1.md +4 -0
- data/doc/release_notes/2.11.0.md +4 -0
- data/doc/release_notes/2.12.0.md +4 -0
- data/doc/release_notes/2.13.0.md +4 -0
- data/doc/release_notes/2.14.0.md +5 -1
- data/doc/release_notes/2.15.0.md +11 -7
- data/doc/release_notes/2.16.0.md +4 -0
- data/doc/release_notes/2.17.0.md +4 -0
- data/doc/release_notes/2.18.0.md +4 -0
- data/doc/release_notes/2.19.0.md +6 -3
- data/doc/release_notes/2.2.0.md +4 -0
- data/doc/release_notes/2.3.0.md +4 -0
- data/doc/release_notes/2.3.1.md +4 -0
- data/doc/release_notes/2.4.0.md +4 -0
- data/doc/release_notes/2.4.1.md +4 -0
- data/doc/release_notes/2.5.0.md +4 -0
- data/doc/release_notes/2.6.0.md +4 -0
- data/doc/release_notes/2.6.1.md +4 -0
- data/doc/release_notes/2.7.0.md +4 -0
- data/doc/release_notes/2.8.0.md +4 -0
- data/doc/release_notes/2.9.0.md +4 -0
- data/doc/release_notes/3.0.0.md +981 -0
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/retrieving_uploads.md +4 -1
- data/doc/securing_uploads.md +60 -37
- data/doc/storage/file_system.md +20 -3
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +117 -83
- data/doc/testing.md +124 -144
- data/doc/upgrading_to_3.md +710 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +287 -171
- data/lib/shrine/attachment.rb +13 -46
- data/lib/shrine/plugins/_persistence.rb +93 -0
- data/lib/shrine/plugins/activerecord.rb +77 -34
- data/lib/shrine/plugins/add_metadata.rb +25 -17
- data/lib/shrine/plugins/atomic_helpers.rb +119 -0
- data/lib/shrine/plugins/backgrounding.rb +77 -113
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
- data/lib/shrine/plugins/column.rb +102 -0
- data/lib/shrine/plugins/data_uri.rb +38 -36
- data/lib/shrine/plugins/default_storage.rb +45 -15
- data/lib/shrine/plugins/default_url.rb +12 -24
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +10 -16
- data/lib/shrine/plugins/derivation_endpoint.rb +89 -134
- data/lib/shrine/plugins/derivatives.rb +637 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +109 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +152 -0
- data/lib/shrine/plugins/form_assign.rb +108 -0
- data/lib/shrine/plugins/included.rb +6 -6
- data/lib/shrine/plugins/infer_extension.rb +13 -20
- data/lib/shrine/plugins/instrumentation.rb +54 -42
- data/lib/shrine/plugins/keep_files.rb +3 -15
- data/lib/shrine/plugins/metadata_attributes.rb +28 -19
- data/lib/shrine/plugins/mirroring.rb +142 -0
- data/lib/shrine/plugins/model.rb +158 -0
- data/lib/shrine/plugins/module_include.rb +3 -3
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/presign_endpoint.rb +18 -22
- data/lib/shrine/plugins/pretty_location.rb +15 -9
- data/lib/shrine/plugins/processing.rb +22 -9
- data/lib/shrine/plugins/rack_file.rb +2 -42
- data/lib/shrine/plugins/rack_response.rb +15 -10
- data/lib/shrine/plugins/recache.rb +6 -5
- data/lib/shrine/plugins/refresh_metadata.rb +13 -11
- data/lib/shrine/plugins/remote_url.rb +49 -49
- data/lib/shrine/plugins/remove_attachment.rb +10 -6
- data/lib/shrine/plugins/remove_invalid.rb +19 -8
- data/lib/shrine/plugins/restore_cached_data.rb +13 -7
- data/lib/shrine/plugins/sequel.rb +86 -36
- data/lib/shrine/plugins/signature.rb +10 -16
- data/lib/shrine/plugins/store_dimensions.rb +35 -40
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +25 -23
- data/lib/shrine/plugins/upload_options.rb +14 -15
- data/lib/shrine/plugins/url_options.rb +31 -0
- data/lib/shrine/plugins/validation.rb +80 -0
- data/lib/shrine/plugins/validation_helpers.rb +34 -57
- data/lib/shrine/plugins/versions.rb +107 -87
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/storage/file_system.rb +46 -64
- data/lib/shrine/storage/linter.rb +42 -7
- data/lib/shrine/storage/memory.rb +49 -0
- data/lib/shrine/storage/s3.rb +154 -158
- data/lib/shrine/uploaded_file.rb +28 -30
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +86 -149
- data/shrine.gemspec +9 -10
- metadata +79 -83
- data/doc/migrating_storage.md +0 -76
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- 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, :
|
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,
|
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
|
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
|
86
|
-
path(id).open(binmode: true, **options
|
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
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
|
107
|
-
|
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!(
|
119
|
-
if
|
104
|
+
def clear!(&condition)
|
105
|
+
if condition
|
120
106
|
list_files(directory) do |path|
|
121
|
-
|
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
|
-
|
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
|
35
|
-
@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"
|
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
|