shrine 2.19.3 → 3.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +523 -41
- data/LICENSE.txt +1 -1
- data/README.md +83 -979
- 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 +103 -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 +1156 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +93 -30
- 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 +186 -101
- data/doc/plugins/derivatives.md +839 -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 +16 -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 +188 -170
- 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 +5 -1
- 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/release_notes/3.5.0.md +63 -0
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/retrieving_uploads.md +5 -2
- 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 +122 -78
- data/doc/testing.md +141 -133
- data/doc/upgrading_to_3.md +708 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +292 -169
- 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 +130 -171
- data/lib/shrine/plugins/derivatives.rb +645 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +118 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +158 -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 +17 -20
- data/lib/shrine/plugins/instrumentation.rb +59 -43
- 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 +160 -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 +27 -28
- 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 +21 -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 +12 -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 +28 -24
- 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 +35 -58
- 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 +173 -160
- data/lib/shrine/uploaded_file.rb +32 -32
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +87 -150
- data/shrine.gemspec +11 -12
- metadata +92 -82
- 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
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
class Shrine
|
|
4
4
|
module Plugins
|
|
5
|
-
# Documentation
|
|
6
|
-
#
|
|
7
|
-
# [doc/plugins/determine_mime_type.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/determine_mime_type.md
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/determine_mime_type
|
|
8
6
|
module DetermineMimeType
|
|
9
7
|
LOG_SUBSCRIBER = -> (event) do
|
|
10
8
|
Shrine.logger.info "MIME Type (#{event.duration}ms) – #{{
|
|
@@ -13,19 +11,12 @@ class Shrine
|
|
|
13
11
|
}.inspect}"
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
def self.configure(uploader,
|
|
17
|
-
|
|
18
|
-
Shrine.deprecation("The :default analyzer of the determine_mime_type plugin has been renamed to :content_type. The :default alias will not be supported in Shrine 3.")
|
|
19
|
-
opts = opts.merge(analyzer: :content_type)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
uploader.opts[:determine_mime_type] ||= { analyzer: :file, analyzer_options: {}, log_subscriber: LOG_SUBSCRIBER }
|
|
14
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
|
15
|
+
uploader.opts[:determine_mime_type] ||= { analyzer: :file, analyzer_options: {} }
|
|
23
16
|
uploader.opts[:determine_mime_type].merge!(opts)
|
|
24
17
|
|
|
25
18
|
# instrumentation plugin integration
|
|
26
|
-
if uploader.respond_to?(:subscribe)
|
|
27
|
-
uploader.subscribe(:mime_type, &uploader.opts[:determine_mime_type][:log_subscriber])
|
|
28
|
-
end
|
|
19
|
+
uploader.subscribe(:mime_type, &log_subscriber) if uploader.respond_to?(:subscribe)
|
|
29
20
|
end
|
|
30
21
|
|
|
31
22
|
module ClassMethods
|
|
@@ -79,11 +70,6 @@ class Shrine
|
|
|
79
70
|
def extract_mime_type(io)
|
|
80
71
|
self.class.determine_mime_type(io)
|
|
81
72
|
end
|
|
82
|
-
|
|
83
|
-
# Returns a hash of built-in MIME type analyzers.
|
|
84
|
-
def mime_type_analyzers
|
|
85
|
-
self.class.mime_type_analyzers
|
|
86
|
-
end
|
|
87
73
|
end
|
|
88
74
|
|
|
89
75
|
class MimeTypeAnalyzer
|
|
@@ -138,6 +124,8 @@ class Shrine
|
|
|
138
124
|
require "fastimage"
|
|
139
125
|
|
|
140
126
|
type = FastImage.type(io)
|
|
127
|
+
return 'image/svg+xml' if type == :svg
|
|
128
|
+
|
|
141
129
|
"image/#{type}" if type
|
|
142
130
|
end
|
|
143
131
|
|
|
@@ -155,7 +143,7 @@ class Shrine
|
|
|
155
143
|
require "mimemagic"
|
|
156
144
|
|
|
157
145
|
mime = MimeMagic.by_magic(io)
|
|
158
|
-
mime
|
|
146
|
+
mime&.type
|
|
159
147
|
end
|
|
160
148
|
|
|
161
149
|
def extract_with_marcel(io, options)
|
|
@@ -172,7 +160,7 @@ class Shrine
|
|
|
172
160
|
|
|
173
161
|
if filename = extract_filename(io)
|
|
174
162
|
mime_type = MIME::Types.of(filename).first
|
|
175
|
-
mime_type
|
|
163
|
+
mime_type&.content_type
|
|
176
164
|
end
|
|
177
165
|
end
|
|
178
166
|
|
|
@@ -181,7 +169,7 @@ class Shrine
|
|
|
181
169
|
|
|
182
170
|
if filename = extract_filename(io)
|
|
183
171
|
info = MiniMime.lookup_by_filename(filename)
|
|
184
|
-
info
|
|
172
|
+
info&.content_type
|
|
185
173
|
end
|
|
186
174
|
end
|
|
187
175
|
|
|
@@ -2,78 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
class Shrine
|
|
4
4
|
module Plugins
|
|
5
|
-
# Documentation
|
|
6
|
-
#
|
|
7
|
-
# [doc/plugins/download_endpoint.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/download_endpoint.md
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/download_endpoint
|
|
8
6
|
module DownloadEndpoint
|
|
9
|
-
def self.load_dependencies(uploader,
|
|
7
|
+
def self.load_dependencies(uploader, **)
|
|
10
8
|
uploader.plugin :rack_response
|
|
11
9
|
uploader.plugin :_urlsafe_serialization
|
|
12
10
|
end
|
|
13
11
|
|
|
14
|
-
def self.configure(uploader, opts
|
|
12
|
+
def self.configure(uploader, **opts)
|
|
15
13
|
uploader.opts[:download_endpoint] ||= { disposition: "inline", download_options: {} }
|
|
16
14
|
uploader.opts[:download_endpoint].merge!(opts)
|
|
17
|
-
|
|
18
|
-
Shrine.deprecation("The :storages download_endpoint option is deprecated, you should use UploadedFile#download_url for generating URLs to the download endpoint.") if uploader.opts[:download_endpoint][:storages]
|
|
19
|
-
|
|
20
|
-
uploader.assign_download_endpoint(App) unless uploader.const_defined?(:DownloadEndpoint)
|
|
21
15
|
end
|
|
22
16
|
|
|
23
17
|
module ClassMethods
|
|
24
|
-
# Assigns the subclass a copy of the download endpoint class.
|
|
25
|
-
def inherited(subclass)
|
|
26
|
-
super
|
|
27
|
-
subclass.assign_download_endpoint(@download_endpoint)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
18
|
# Returns the Rack application that retrieves requested files.
|
|
31
19
|
def download_endpoint(**options)
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Assigns the subclassed endpoint as the `DownloadEndpoint` constant.
|
|
36
|
-
def assign_download_endpoint(app_class)
|
|
37
|
-
@download_endpoint = new_download_endpoint(app_class)
|
|
38
|
-
|
|
39
|
-
const_set(:DownloadEndpoint, @download_endpoint)
|
|
40
|
-
deprecate_constant(:DownloadEndpoint)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def new_download_endpoint(app_class, **options)
|
|
46
|
-
app_class.new(
|
|
20
|
+
Shrine::DownloadEndpoint.new(
|
|
47
21
|
shrine_class: self,
|
|
48
22
|
**opts[:download_endpoint],
|
|
49
23
|
**options,
|
|
50
24
|
)
|
|
51
25
|
end
|
|
52
|
-
end
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
27
|
+
# Calls the download endpoint passing the request information, and
|
|
28
|
+
# returns the Rack response triple.
|
|
29
|
+
#
|
|
30
|
+
# It uses a trick where it removes the download path prefix from the
|
|
31
|
+
# path info before calling the Rack app, which is what web framework
|
|
32
|
+
# routers do before they're calling a mounted Rack app.
|
|
33
|
+
def download_response(env, **options)
|
|
34
|
+
script_name = env["SCRIPT_NAME"]
|
|
35
|
+
path_info = env["PATH_INFO"]
|
|
36
|
+
|
|
37
|
+
prefix = opts[:download_endpoint][:prefix]
|
|
38
|
+
match = path_info.match(/^\/#{prefix}/)
|
|
39
|
+
|
|
40
|
+
fail Error, "request path must start with \"/#{prefix}\", but is \"#{path_info}\"" unless match
|
|
41
|
+
|
|
42
|
+
begin
|
|
43
|
+
env["SCRIPT_NAME"] += match.to_s
|
|
44
|
+
env["PATH_INFO"] = match.post_match
|
|
45
|
+
|
|
46
|
+
download_endpoint(**options).call(env)
|
|
47
|
+
ensure
|
|
48
|
+
env["SCRIPT_NAME"] = script_name
|
|
49
|
+
env["PATH_INFO"] = path_info
|
|
64
50
|
end
|
|
65
51
|
end
|
|
52
|
+
end
|
|
66
53
|
|
|
54
|
+
module FileMethods
|
|
67
55
|
# Returns file URL on the download endpoint.
|
|
68
56
|
def download_url(**options)
|
|
69
57
|
FileUrl.new(self).call(**options)
|
|
70
58
|
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
def download_storages
|
|
75
|
-
shrine_class.opts[:download_endpoint][:storages]
|
|
76
|
-
end
|
|
77
59
|
end
|
|
78
60
|
|
|
79
61
|
class FileUrl
|
|
@@ -105,116 +87,119 @@ class Shrine
|
|
|
105
87
|
file.shrine_class.opts[:download_endpoint]
|
|
106
88
|
end
|
|
107
89
|
end
|
|
90
|
+
end
|
|
108
91
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# streaming.
|
|
112
|
-
class App
|
|
113
|
-
# Writes given options to instance variables.
|
|
114
|
-
def initialize(options)
|
|
115
|
-
options.each do |name, value|
|
|
116
|
-
instance_variable_set("@#{name}", value)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
92
|
+
register_plugin(:download_endpoint, DownloadEndpoint)
|
|
93
|
+
end
|
|
119
94
|
|
|
120
|
-
def call(env)
|
|
121
|
-
request = Rack::Request.new(env)
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
|
|
96
|
+
# Routes incoming requests. It first asserts that the storage is existent
|
|
97
|
+
# and allowed. Afterwards it proceeds with the file download using
|
|
98
|
+
# streaming.
|
|
99
|
+
class DownloadEndpoint
|
|
100
|
+
# Writes given options to instance variables.
|
|
101
|
+
def initialize(options)
|
|
102
|
+
options.each do |name, value|
|
|
103
|
+
instance_variable_set("@#{name}", value)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
125
106
|
|
|
126
|
-
|
|
127
|
-
|
|
107
|
+
def call(env)
|
|
108
|
+
request = Rack::Request.new(env)
|
|
128
109
|
|
|
129
|
-
|
|
110
|
+
status, headers, body = catch(:halt) do
|
|
111
|
+
error!(405, "Method Not Allowed") unless request.get?
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
handle_request(request)
|
|
114
|
+
end
|
|
133
115
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
alias to_s inspect
|
|
116
|
+
headers = Rack::Headers[headers] if Rack.release >= "3"
|
|
117
|
+
headers["Content-Length"] ||= body.respond_to?(:bytesize) ? body.bytesize.to_s :
|
|
118
|
+
body.map(&:bytesize).inject(0, :+).to_s
|
|
138
119
|
|
|
139
|
-
|
|
120
|
+
[status, headers, body]
|
|
121
|
+
end
|
|
140
122
|
|
|
141
|
-
|
|
142
|
-
|
|
123
|
+
def inspect
|
|
124
|
+
"#<#{@shrine_class}::DownloadEndpoint>"
|
|
125
|
+
end
|
|
126
|
+
alias to_s inspect
|
|
143
127
|
|
|
144
|
-
|
|
145
|
-
uploaded_file = get_uploaded_file(components.first)
|
|
146
|
-
elsif components.count == 2
|
|
147
|
-
# handle legacy "/:storage/:id" URLs
|
|
148
|
-
uploaded_file = @shrine_class::UploadedFile.new(
|
|
149
|
-
"storage" => components.first,
|
|
150
|
-
"id" => components.last,
|
|
151
|
-
)
|
|
152
|
-
end
|
|
128
|
+
private
|
|
153
129
|
|
|
154
|
-
|
|
155
|
-
|
|
130
|
+
def handle_request(request)
|
|
131
|
+
_, serialized, * = request.path_info.split("/")
|
|
156
132
|
|
|
157
|
-
|
|
158
|
-
def serve_file(uploaded_file, request)
|
|
159
|
-
if @redirect
|
|
160
|
-
redirect_to_file(uploaded_file, request)
|
|
161
|
-
else
|
|
162
|
-
stream_file(uploaded_file, request)
|
|
163
|
-
end
|
|
164
|
-
end
|
|
133
|
+
uploaded_file = get_uploaded_file(serialized)
|
|
165
134
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
open_file(uploaded_file, request)
|
|
135
|
+
serve_file(uploaded_file, request)
|
|
136
|
+
end
|
|
169
137
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
138
|
+
# Streams or redirects to the uploaded file.
|
|
139
|
+
def serve_file(uploaded_file, request)
|
|
140
|
+
if @redirect
|
|
141
|
+
redirect_to_file(uploaded_file, request)
|
|
142
|
+
else
|
|
143
|
+
stream_file(uploaded_file, request)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
174
146
|
|
|
175
|
-
|
|
147
|
+
# Streams the uploaded file content.
|
|
148
|
+
def stream_file(uploaded_file, request)
|
|
149
|
+
open_file(uploaded_file, request)
|
|
176
150
|
|
|
177
|
-
|
|
178
|
-
|
|
151
|
+
response = uploaded_file.to_rack_response(
|
|
152
|
+
disposition: @disposition,
|
|
153
|
+
range: request.env["HTTP_RANGE"],
|
|
154
|
+
)
|
|
179
155
|
|
|
180
|
-
|
|
181
|
-
def redirect_to_file(uploaded_file, request)
|
|
182
|
-
if @redirect == true
|
|
183
|
-
redirect_url = uploaded_file.url
|
|
184
|
-
else
|
|
185
|
-
redirect_url = @redirect.call(uploaded_file, request)
|
|
186
|
-
end
|
|
156
|
+
response[1]["Cache-Control"] = "max-age=#{365*24*60*60}" # cache for a year
|
|
187
157
|
|
|
188
|
-
|
|
189
|
-
|
|
158
|
+
response
|
|
159
|
+
end
|
|
190
160
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
161
|
+
# Redirects to the uploaded file's direct URL or the specified URL proc.
|
|
162
|
+
def redirect_to_file(uploaded_file, request)
|
|
163
|
+
if @redirect == true
|
|
164
|
+
redirect_url = uploaded_file.url
|
|
165
|
+
else
|
|
166
|
+
redirect_url = @redirect.call(uploaded_file, request)
|
|
167
|
+
end
|
|
194
168
|
|
|
195
|
-
|
|
196
|
-
|
|
169
|
+
[302, { "Location" => redirect_url }, []]
|
|
170
|
+
end
|
|
197
171
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
not_found! unless uploaded_file.exists?
|
|
202
|
-
uploaded_file
|
|
203
|
-
rescue Shrine::Error # storage not found
|
|
204
|
-
not_found!
|
|
205
|
-
end
|
|
172
|
+
def open_file(uploaded_file, request)
|
|
173
|
+
download_options = @download_options
|
|
174
|
+
download_options = download_options.call(uploaded_file, request) if download_options.respond_to?(:call)
|
|
206
175
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
176
|
+
uploaded_file.open(**download_options)
|
|
177
|
+
rescue Shrine::FileNotFound
|
|
178
|
+
not_found!
|
|
179
|
+
end
|
|
210
180
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
181
|
+
# Deserializes a Shrine::UploadedFile from a URL component. Returns 404 if
|
|
182
|
+
# storage is not found.
|
|
183
|
+
def get_uploaded_file(serialized)
|
|
184
|
+
@shrine_class::UploadedFile.urlsafe_load(serialized)
|
|
185
|
+
rescue Shrine::Error # storage not found
|
|
186
|
+
not_found!
|
|
187
|
+
rescue JSON::ParserError, ArgumentError => error # invalid serialized component
|
|
188
|
+
raise if error.is_a?(ArgumentError) && error.message != "invalid base64"
|
|
189
|
+
bad_request!("Invalid serialized file")
|
|
216
190
|
end
|
|
217
191
|
|
|
218
|
-
|
|
192
|
+
def not_found!
|
|
193
|
+
error!(404, "File Not Found")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def bad_request!(message)
|
|
197
|
+
error!(400, message)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Halts the request with the error message.
|
|
201
|
+
def error!(status, message)
|
|
202
|
+
throw :halt, [status, { "Content-Type" => "text/plain" }, [message]]
|
|
203
|
+
end
|
|
219
204
|
end
|
|
220
205
|
end
|
|
@@ -2,21 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
class Shrine
|
|
4
4
|
module Plugins
|
|
5
|
-
# Documentation
|
|
6
|
-
#
|
|
7
|
-
# [doc/plugins/dynamic_storage.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/dynamic_storage.md
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/dynamic_storage
|
|
8
6
|
module DynamicStorage
|
|
9
|
-
def self.configure(uploader
|
|
10
|
-
uploader.opts[:
|
|
7
|
+
def self.configure(uploader)
|
|
8
|
+
uploader.opts[:dynamic_storage] ||= { resolvers: {} }
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
module ClassMethods
|
|
14
|
-
def dynamic_storages
|
|
15
|
-
opts[:dynamic_storages]
|
|
16
|
-
end
|
|
17
|
-
|
|
18
12
|
def storage(regex, &block)
|
|
19
|
-
|
|
13
|
+
opts[:dynamic_storage][:resolvers][regex] = block
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
def find_storage(name)
|
|
@@ -26,7 +20,7 @@ class Shrine
|
|
|
26
20
|
private
|
|
27
21
|
|
|
28
22
|
def resolve_dynamic_storage(name)
|
|
29
|
-
|
|
23
|
+
opts[:dynamic_storage][:resolvers].each do |regex, block|
|
|
30
24
|
if match = name.to_s.match(regex)
|
|
31
25
|
return block.call(match)
|
|
32
26
|
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Shrine
|
|
4
|
+
module Plugins
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/entity
|
|
6
|
+
module Entity
|
|
7
|
+
def self.load_dependencies(uploader, **)
|
|
8
|
+
uploader.plugin :column
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module AttachmentMethods
|
|
12
|
+
# Defines instance methods on initialization.
|
|
13
|
+
def initialize(name, **options)
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
define_entity_methods(name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Defines class methods on inclusion.
|
|
20
|
+
def included(klass)
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
attachment = self
|
|
24
|
+
|
|
25
|
+
klass.send(:define_singleton_method, :"#{@name}_attacher") do |**options|
|
|
26
|
+
attachment.send(:class_attacher, **options)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Defines `#<name>`, `#<name>_url`, and `#<name>_attacher` methods.
|
|
33
|
+
def define_entity_methods(name)
|
|
34
|
+
super if defined?(super)
|
|
35
|
+
|
|
36
|
+
attachment = self
|
|
37
|
+
|
|
38
|
+
# Returns the attached file.
|
|
39
|
+
if shrine_class::Attacher.instance_method(:get).arity == 0
|
|
40
|
+
define_method :"#{name}" do
|
|
41
|
+
send(:"#{name}_attacher").get
|
|
42
|
+
end
|
|
43
|
+
else # derivatives
|
|
44
|
+
define_method :"#{name}" do |*args|
|
|
45
|
+
send(:"#{name}_attacher").get(*args)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns the URL to the attached file.
|
|
50
|
+
define_method :"#{name}_url" do |*args, **options|
|
|
51
|
+
send(:"#{name}_attacher").url(*args, **options)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns an attacher instance.
|
|
55
|
+
define_method :"#{name}_attacher" do |**options|
|
|
56
|
+
attachment.send(:attacher, self, **options)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the class attacher instance with loaded entity. It's not
|
|
61
|
+
# memoized because the entity object could be frozen.
|
|
62
|
+
def attacher(record, **options)
|
|
63
|
+
attacher = class_attacher(**options)
|
|
64
|
+
attacher.load_entity(record, @name)
|
|
65
|
+
attacher
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Creates an instance of the corresponding attacher class with set
|
|
69
|
+
# name.
|
|
70
|
+
def class_attacher(**options)
|
|
71
|
+
attacher = shrine_class::Attacher.new(**@options, **options)
|
|
72
|
+
attacher.instance_variable_set(:@name, @name)
|
|
73
|
+
attacher
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module AttacherClassMethods
|
|
78
|
+
# Initializes itself from an entity instance and attachment name.
|
|
79
|
+
#
|
|
80
|
+
# photo.image_data #=> "{...}" # a file is attached
|
|
81
|
+
#
|
|
82
|
+
# attacher = Attacher.from_entity(photo, :image)
|
|
83
|
+
# attacher.file #=> #<Shrine::UploadedFile>
|
|
84
|
+
def from_entity(record, name, **options)
|
|
85
|
+
attacher = new(**options)
|
|
86
|
+
attacher.load_entity(record, name)
|
|
87
|
+
attacher
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
module AttacherMethods
|
|
92
|
+
attr_reader :record, :name
|
|
93
|
+
|
|
94
|
+
# Saves record and name and initializes attachment from the entity
|
|
95
|
+
# attribute. Called from `Attacher.from_entity`.
|
|
96
|
+
def load_entity(record, name)
|
|
97
|
+
set_entity(record, name)
|
|
98
|
+
read
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Sets record and name without loading the attachment from the entity
|
|
102
|
+
# attribute.
|
|
103
|
+
def set_entity(record, name)
|
|
104
|
+
@record = record
|
|
105
|
+
@name = name.to_sym
|
|
106
|
+
|
|
107
|
+
@context.merge!(record: record, name: name)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Overwrites the current attachment with the one from model attribute.
|
|
111
|
+
#
|
|
112
|
+
# photo.image_data #=> nil
|
|
113
|
+
# attacher = Shrine::Attacher.from_entity(photo, :image)
|
|
114
|
+
# photo.image_data = uploaded_file.to_json
|
|
115
|
+
#
|
|
116
|
+
# attacher.file #=> nil
|
|
117
|
+
# attacher.reload
|
|
118
|
+
# attacher.file #=> #<Shrine::UploadedFile>
|
|
119
|
+
def reload
|
|
120
|
+
read
|
|
121
|
+
@previous = nil
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Loads attachment from the entity attribute.
|
|
126
|
+
def read
|
|
127
|
+
load_column(read_attribute)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns a hash with entity attribute name and column data.
|
|
131
|
+
#
|
|
132
|
+
# attacher.column_values
|
|
133
|
+
# #=> { image_data: '{"id":"...","storage":"...","metadata":{...}}' }
|
|
134
|
+
def column_values
|
|
135
|
+
{ attribute => column_data }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns the entity attribute name used for reading and writing
|
|
139
|
+
# attachment data.
|
|
140
|
+
#
|
|
141
|
+
# attacher = Shrine::Attacher.from_entity(photo, :image)
|
|
142
|
+
# attacher.attribute #=> :image_data
|
|
143
|
+
def attribute
|
|
144
|
+
:"#{name}_data" if name
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# Reads value from the entity attribute.
|
|
150
|
+
def read_attribute
|
|
151
|
+
record.public_send(attribute)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
register_plugin(:entity, Entity)
|
|
157
|
+
end
|
|
158
|
+
end
|