shrine 3.4.0 → 3.10.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 +96 -0
- data/README.md +11 -11
- data/doc/advantages.md +1 -1
- data/doc/attacher.md +1 -1
- data/doc/carrierwave.md +2 -2
- data/doc/changing_derivatives.md +2 -1
- data/doc/changing_location.md +19 -6
- data/doc/design.md +4 -4
- data/doc/external/articles.md +16 -16
- data/doc/external/extensions.md +1 -1
- data/doc/getting_started.md +73 -30
- data/doc/multiple_files.md +57 -22
- data/doc/plugins/activerecord.md +91 -0
- data/doc/plugins/backgrounding.md +30 -4
- data/doc/plugins/data_uri.md +15 -0
- data/doc/plugins/derivation_endpoint.md +39 -1
- data/doc/plugins/derivatives.md +13 -2
- data/doc/plugins/download_endpoint.md +16 -4
- data/doc/plugins/dynamic_storage.md +15 -0
- data/doc/plugins/fallback_storage.md +90 -0
- data/doc/plugins/keep_files.md +6 -4
- data/doc/plugins/rack_response.md +9 -0
- data/doc/plugins/refresh_metadata.md +20 -0
- data/doc/plugins/sequel.md +57 -0
- data/doc/plugins/signature.md +8 -6
- data/doc/plugins/tempfile.md +14 -6
- data/doc/plugins/upload_options.md +12 -0
- data/doc/plugins/url_options.md +12 -0
- data/doc/plugins/validation_helpers.md +1 -1
- data/doc/processing.md +5 -3
- data/doc/refile.md +3 -3
- data/doc/release_notes/2.1.0.md +1 -1
- data/doc/release_notes/3.10.0.md +30 -0
- data/doc/release_notes/3.5.0.md +63 -0
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/release_notes/3.7.0.md +75 -0
- data/doc/release_notes/3.7.1.md +31 -0
- data/doc/release_notes/3.8.0.md +37 -0
- data/doc/release_notes/3.9.0.md +42 -0
- data/doc/retrieving_uploads.md +1 -1
- data/doc/storage/s3.md +10 -0
- data/doc/testing.md +45 -17
- data/doc/upgrading_to_3.md +3 -5
- data/lib/shrine/attacher.rb +41 -29
- data/lib/shrine/attachment.rb +2 -2
- data/lib/shrine/plugins/_persistence.rb +1 -1
- data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
- data/lib/shrine/plugins/activerecord.rb +25 -4
- data/lib/shrine/plugins/add_metadata.rb +2 -4
- data/lib/shrine/plugins/atomic_helpers.rb +7 -7
- data/lib/shrine/plugins/backgrounding.rb +10 -10
- data/lib/shrine/plugins/column.rb +7 -5
- data/lib/shrine/plugins/data_uri.rb +28 -3
- data/lib/shrine/plugins/default_url.rb +4 -4
- data/lib/shrine/plugins/delete_raw.rb +2 -2
- data/lib/shrine/plugins/derivation_endpoint.rb +65 -59
- data/lib/shrine/plugins/derivatives.rb +35 -21
- data/lib/shrine/plugins/download_endpoint.rb +72 -11
- data/lib/shrine/plugins/entity.rb +15 -9
- data/lib/shrine/plugins/fallback_storage.rb +51 -0
- data/lib/shrine/plugins/infer_extension.rb +5 -1
- data/lib/shrine/plugins/instrumentation.rb +13 -9
- data/lib/shrine/plugins/metadata_attributes.rb +1 -1
- data/lib/shrine/plugins/mirroring.rb +10 -10
- data/lib/shrine/plugins/model.rb +12 -10
- data/lib/shrine/plugins/presign_endpoint.rb +13 -10
- data/lib/shrine/plugins/pretty_location.rb +2 -2
- data/lib/shrine/plugins/processing.rb +3 -3
- data/lib/shrine/plugins/rack_file.rb +2 -2
- data/lib/shrine/plugins/rack_response.rb +12 -6
- data/lib/shrine/plugins/refresh_metadata.rb +6 -6
- data/lib/shrine/plugins/remote_url.rb +4 -4
- data/lib/shrine/plugins/remove_attachment.rb +2 -0
- data/lib/shrine/plugins/remove_invalid.rb +4 -0
- data/lib/shrine/plugins/restore_cached_data.rb +3 -3
- data/lib/shrine/plugins/sequel.rb +15 -1
- data/lib/shrine/plugins/signature.rb +2 -2
- data/lib/shrine/plugins/store_dimensions.rb +2 -2
- data/lib/shrine/plugins/tempfile.rb +0 -2
- data/lib/shrine/plugins/upload_endpoint.rb +7 -5
- data/lib/shrine/plugins/upload_options.rb +18 -4
- data/lib/shrine/plugins/url_options.rb +17 -3
- data/lib/shrine/plugins/validation.rb +8 -8
- data/lib/shrine/plugins/validation_helpers.rb +2 -2
- data/lib/shrine/plugins/versions.rb +10 -10
- data/lib/shrine/plugins.rb +6 -14
- data/lib/shrine/storage/file_system.rb +15 -19
- data/lib/shrine/storage/linter.rb +8 -8
- data/lib/shrine/storage/memory.rb +1 -3
- data/lib/shrine/storage/s3.rb +60 -40
- data/lib/shrine/uploaded_file.rb +23 -19
- data/lib/shrine/version.rb +1 -1
- data/lib/shrine.rb +24 -20
- data/shrine.gemspec +10 -8
- metadata +52 -26
|
@@ -10,7 +10,7 @@ class Shrine
|
|
|
10
10
|
|
|
11
11
|
module AttachmentMethods
|
|
12
12
|
# Defines instance methods on initialization.
|
|
13
|
-
def initialize(name, **
|
|
13
|
+
def initialize(name, **)
|
|
14
14
|
super
|
|
15
15
|
|
|
16
16
|
define_entity_methods(name)
|
|
@@ -36,8 +36,14 @@ class Shrine
|
|
|
36
36
|
attachment = self
|
|
37
37
|
|
|
38
38
|
# Returns the attached file.
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
# Returns the URL to the attached file.
|
|
@@ -53,16 +59,16 @@ class Shrine
|
|
|
53
59
|
|
|
54
60
|
# Returns the class attacher instance with loaded entity. It's not
|
|
55
61
|
# memoized because the entity object could be frozen.
|
|
56
|
-
def attacher(record, **
|
|
57
|
-
attacher = class_attacher(**
|
|
62
|
+
def attacher(record, **)
|
|
63
|
+
attacher = class_attacher(**)
|
|
58
64
|
attacher.load_entity(record, @name)
|
|
59
65
|
attacher
|
|
60
66
|
end
|
|
61
67
|
|
|
62
68
|
# Creates an instance of the corresponding attacher class with set
|
|
63
69
|
# name.
|
|
64
|
-
def class_attacher(**
|
|
65
|
-
attacher = shrine_class::Attacher.new(**@options, **
|
|
70
|
+
def class_attacher(**)
|
|
71
|
+
attacher = shrine_class::Attacher.new(**@options, **)
|
|
66
72
|
attacher.instance_variable_set(:@name, @name)
|
|
67
73
|
attacher
|
|
68
74
|
end
|
|
@@ -75,8 +81,8 @@ class Shrine
|
|
|
75
81
|
#
|
|
76
82
|
# attacher = Attacher.from_entity(photo, :image)
|
|
77
83
|
# attacher.file #=> #<Shrine::UploadedFile>
|
|
78
|
-
def from_entity(record, name, **
|
|
79
|
-
attacher = new(**
|
|
84
|
+
def from_entity(record, name, **)
|
|
85
|
+
attacher = new(**)
|
|
80
86
|
attacher.load_entity(record, name)
|
|
81
87
|
attacher
|
|
82
88
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Shrine
|
|
4
|
+
module Plugins
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/fallback_storage
|
|
6
|
+
module FallbackStorage
|
|
7
|
+
def self.configure(uploader, **opts)
|
|
8
|
+
uploader.opts[:fallback_storage] ||= { store: :fallback }
|
|
9
|
+
uploader.opts[:fallback_storage].merge!(opts)
|
|
10
|
+
|
|
11
|
+
uploader.find_storage(uploader.opts.dig(:fallback_storage, :store))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module FileMethods
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.class_eval do
|
|
17
|
+
alias_method :storage_exists?, :exists?
|
|
18
|
+
|
|
19
|
+
def exists?
|
|
20
|
+
super || fallback_storage.exists?(id)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def url(**)
|
|
26
|
+
if storage_exists?
|
|
27
|
+
super
|
|
28
|
+
else
|
|
29
|
+
fallback_storage.url(id, **)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def _open(**)
|
|
36
|
+
if storage_exists?
|
|
37
|
+
super
|
|
38
|
+
else
|
|
39
|
+
fallback_storage.open(id, **)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fallback_storage
|
|
44
|
+
shrine_class.find_storage(shrine_class.opts.dig(:fallback_storage, :store))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
register_plugin(:fallback_storage, FallbackStorage)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -46,11 +46,15 @@ class Shrine
|
|
|
46
46
|
def instrument_extension(mime_type, &block)
|
|
47
47
|
return yield unless respond_to?(:instrument)
|
|
48
48
|
|
|
49
|
-
instrument(:extension, mime_type
|
|
49
|
+
instrument(:extension, mime_type:, &block)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
module InstanceMethods
|
|
54
|
+
def infer_extension(mime_type)
|
|
55
|
+
self.class.infer_extension(mime_type)
|
|
56
|
+
end
|
|
57
|
+
|
|
54
58
|
private
|
|
55
59
|
|
|
56
60
|
def basic_location(io, metadata:)
|
|
@@ -12,7 +12,11 @@ class Shrine
|
|
|
12
12
|
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
|
13
13
|
uploader.opts[:instrumentation] ||= { log_events: EVENTS, subscribers: {} }
|
|
14
14
|
uploader.opts[:instrumentation].merge!(opts)
|
|
15
|
-
|
|
15
|
+
begin
|
|
16
|
+
uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
|
|
17
|
+
rescue NameError
|
|
18
|
+
fail Error, "default notifications library is ActiveSupport::Notifications, but activesupport gem is not installed"
|
|
19
|
+
end
|
|
16
20
|
|
|
17
21
|
uploader.opts[:instrumentation][:log_events].each do |event_name|
|
|
18
22
|
uploader.subscribe(event_name, &log_subscriber)
|
|
@@ -165,14 +169,14 @@ class Shrine
|
|
|
165
169
|
end
|
|
166
170
|
end
|
|
167
171
|
|
|
168
|
-
def library_send(method_name,
|
|
172
|
+
def library_send(method_name, ...)
|
|
169
173
|
case notifications.to_s
|
|
170
174
|
when /Dry::Monitor::Notifications/
|
|
171
|
-
send(:"dry_monitor_#{method_name}",
|
|
175
|
+
send(:"dry_monitor_#{method_name}", ...)
|
|
172
176
|
when /ActiveSupport::Notifications/
|
|
173
|
-
send(:"active_support_#{method_name}",
|
|
177
|
+
send(:"active_support_#{method_name}", ...)
|
|
174
178
|
else
|
|
175
|
-
notifications.send(method_name,
|
|
179
|
+
notifications.send(method_name, ...)
|
|
176
180
|
end
|
|
177
181
|
end
|
|
178
182
|
end
|
|
@@ -220,14 +224,14 @@ class Shrine
|
|
|
220
224
|
event.duration.to_i
|
|
221
225
|
end
|
|
222
226
|
|
|
223
|
-
def library_send(method_name,
|
|
227
|
+
def library_send(method_name, ...)
|
|
224
228
|
case event.class.name
|
|
225
229
|
when "ActiveSupport::Notifications::Event"
|
|
226
|
-
send(:"active_support_#{method_name}",
|
|
230
|
+
send(:"active_support_#{method_name}", ...)
|
|
227
231
|
when "Dry::Events::Event"
|
|
228
|
-
send(:"dry_events_#{method_name}",
|
|
232
|
+
send(:"dry_events_#{method_name}", ...)
|
|
229
233
|
else
|
|
230
|
-
event.send(method_name,
|
|
234
|
+
event.send(method_name, ...)
|
|
231
235
|
end
|
|
232
236
|
end
|
|
233
237
|
end
|
|
@@ -51,9 +51,9 @@ class Shrine
|
|
|
51
51
|
|
|
52
52
|
module InstanceMethods
|
|
53
53
|
# Mirrors upload to other mirror storages.
|
|
54
|
-
def upload(io, mirror: true, **
|
|
55
|
-
file = super(io, **
|
|
56
|
-
file.trigger_mirror_upload(**
|
|
54
|
+
def upload(io, mirror: true, **)
|
|
55
|
+
file = super(io, **)
|
|
56
|
+
file.trigger_mirror_upload(**) if mirror
|
|
57
57
|
file
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -61,31 +61,31 @@ class Shrine
|
|
|
61
61
|
module FileMethods
|
|
62
62
|
# Mirrors upload if mirrors are defined. Calls mirror block if
|
|
63
63
|
# registered, otherwise mirrors synchronously.
|
|
64
|
-
def trigger_mirror_upload(**
|
|
64
|
+
def trigger_mirror_upload(**)
|
|
65
65
|
return unless shrine_class.mirrors[storage_key] && shrine_class.mirror_upload?
|
|
66
66
|
|
|
67
67
|
if shrine_class.mirror_upload_block
|
|
68
|
-
mirror_upload_background(**
|
|
68
|
+
mirror_upload_background(**)
|
|
69
69
|
else
|
|
70
|
-
mirror_upload(**
|
|
70
|
+
mirror_upload(**)
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Calls mirror upload block.
|
|
75
|
-
def mirror_upload_background(**
|
|
75
|
+
def mirror_upload_background(**)
|
|
76
76
|
fail Error, "mirror upload block is not registered" unless shrine_class.mirror_upload_block
|
|
77
77
|
|
|
78
|
-
shrine_class.mirror_upload_block.call(self, **
|
|
78
|
+
shrine_class.mirror_upload_block.call(self, **)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Uploads the file to each mirror storage.
|
|
82
|
-
def mirror_upload(**
|
|
82
|
+
def mirror_upload(**)
|
|
83
83
|
previously_opened = opened?
|
|
84
84
|
|
|
85
85
|
each_mirror do |mirror|
|
|
86
86
|
rewind if opened?
|
|
87
87
|
|
|
88
|
-
shrine_class.upload(self, mirror,
|
|
88
|
+
shrine_class.upload(self, mirror, **, location: id, close: false, action: :mirror)
|
|
89
89
|
end
|
|
90
90
|
ensure
|
|
91
91
|
if opened? && !previously_opened
|
data/lib/shrine/plugins/model.rb
CHANGED
|
@@ -18,8 +18,8 @@ class Shrine
|
|
|
18
18
|
#
|
|
19
19
|
# Shrine::Attachment(:image) # model (default)
|
|
20
20
|
# Shrine::Attachment(:image, model: false) # entity
|
|
21
|
-
def initialize(name, model: true, **
|
|
22
|
-
super(name, **
|
|
21
|
+
def initialize(name, model: true, **)
|
|
22
|
+
super(name, **)
|
|
23
23
|
@model = model
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -48,7 +48,9 @@ class Shrine
|
|
|
48
48
|
# The copy constructor that's called on #dup and #clone.
|
|
49
49
|
define_method :initialize_copy do |other|
|
|
50
50
|
super(other)
|
|
51
|
-
|
|
51
|
+
attacher_copy = instance_variable_get(:"@#{name}_attacher")&.dup
|
|
52
|
+
attacher_copy.set_entity(self, name) if attacher_copy
|
|
53
|
+
instance_variable_set(:"@#{name}_attacher", attacher_copy)
|
|
52
54
|
self
|
|
53
55
|
end
|
|
54
56
|
private :initialize_copy
|
|
@@ -80,16 +82,16 @@ class Shrine
|
|
|
80
82
|
#
|
|
81
83
|
# attacher = Attacher.from_model(photo, :image)
|
|
82
84
|
# attacher.file #=> #<Shrine::UploadedFile>
|
|
83
|
-
def from_model(record, name, **
|
|
84
|
-
attacher = new(**
|
|
85
|
+
def from_model(record, name, **)
|
|
86
|
+
attacher = new(**)
|
|
85
87
|
attacher.load_model(record, name)
|
|
86
88
|
attacher
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
module AttacherMethods
|
|
91
|
-
def initialize(model_cache: shrine_class.opts[:model][:cache], **
|
|
92
|
-
super(**
|
|
93
|
+
def initialize(model_cache: shrine_class.opts[:model][:cache], **)
|
|
94
|
+
super(**)
|
|
93
95
|
@model_cache = model_cache
|
|
94
96
|
@model = nil
|
|
95
97
|
end
|
|
@@ -109,11 +111,11 @@ class Shrine
|
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
# Called by the attachment attribute setter on the model.
|
|
112
|
-
def model_assign(value, **
|
|
114
|
+
def model_assign(value, **)
|
|
113
115
|
if model_cache?
|
|
114
|
-
assign(value, **
|
|
116
|
+
assign(value, **)
|
|
115
117
|
else
|
|
116
|
-
attach(value, **
|
|
118
|
+
attach(value, **)
|
|
117
119
|
end
|
|
118
120
|
end
|
|
119
121
|
|
|
@@ -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, **
|
|
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
|
-
|
|
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, **
|
|
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, **
|
|
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[
|
|
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
|
-
|
|
163
|
+
status, headers, body = if @rack_response
|
|
164
|
+
@rack_response.call(object, request)
|
|
163
165
|
else
|
|
164
|
-
|
|
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
|
-
|
|
171
|
+
headers["Cache-Control"] = "no-store" unless headers.key?("Cache-Control")
|
|
169
172
|
|
|
170
|
-
|
|
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, **
|
|
14
|
-
pretty_location(io, **
|
|
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, **
|
|
21
|
+
def upload(io, process: true, **)
|
|
22
22
|
if process
|
|
23
|
-
input = process(io, **
|
|
23
|
+
input = process(io, **)
|
|
24
24
|
else
|
|
25
25
|
input = io
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
super(input, **
|
|
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, **
|
|
27
|
+
def assign(value, **)
|
|
28
28
|
if rack_file?(value)
|
|
29
|
-
assign shrine_class.rack_file(value), **
|
|
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(**
|
|
14
|
-
FileResponse.new(self).call(**
|
|
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
|
|
|
@@ -47,14 +49,14 @@ class Shrine
|
|
|
47
49
|
# "Content-Disposition" headers, whose values are extracted from
|
|
48
50
|
# metadata. Also returns the correct "Content-Range" header on ranged
|
|
49
51
|
# requests.
|
|
50
|
-
def rack_headers(filename: nil, type: nil, disposition: "inline", range: false)
|
|
52
|
+
def rack_headers(filename: nil, type: nil, disposition: "inline", range: false, etag: nil)
|
|
51
53
|
{
|
|
52
54
|
"Content-Length" => content_length(range),
|
|
53
55
|
"Content-Type" => content_type(type),
|
|
54
56
|
"Content-Disposition" => content_disposition(disposition, filename),
|
|
55
57
|
"Content-Range" => content_range(range),
|
|
56
58
|
"Accept-Ranges" => accept_ranges(range),
|
|
57
|
-
"ETag" => etag,
|
|
59
|
+
"ETag" => etag || self.etag,
|
|
58
60
|
}.compact
|
|
59
61
|
end
|
|
60
62
|
|
|
@@ -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
|
|
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:
|
|
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!(**
|
|
9
|
-
file!.refresh_metadata!(**context, **
|
|
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!(**
|
|
16
|
-
return open { refresh_metadata!(**
|
|
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, **
|
|
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
|
|
@@ -18,7 +18,7 @@ class Shrine
|
|
|
18
18
|
|
|
19
19
|
DOWNLOADER = -> (url, **options) { Down.download(url, **options) }
|
|
20
20
|
|
|
21
|
-
def self.load_dependencies(uploader,
|
|
21
|
+
def self.load_dependencies(uploader, **)
|
|
22
22
|
uploader.plugin :validation
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -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: {}, **
|
|
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, **
|
|
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
|
|
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, **
|
|
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, **
|
|
19
|
+
uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **) }
|
|
20
20
|
else
|
|
21
|
-
cached_file.refresh_metadata!(**context, **
|
|
21
|
+
cached_file.refresh_metadata!(**context, **)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
cached_file
|
|
@@ -87,8 +87,11 @@ class Shrine
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# Calls Attacher#save. Called before model save.
|
|
90
|
+
# Calls Attacher#save. Called before model save. Remembers whether
|
|
91
|
+
# the record is new, since by the time `#sequel_after_save` runs,
|
|
92
|
+
# Sequel has already flipped `#new?` to false.
|
|
91
93
|
def sequel_before_save
|
|
94
|
+
@previously_new_record = record.new?
|
|
92
95
|
save
|
|
93
96
|
end
|
|
94
97
|
|
|
@@ -132,6 +135,17 @@ class Shrine
|
|
|
132
135
|
def sequel?
|
|
133
136
|
record.is_a?(::Sequel::Model)
|
|
134
137
|
end
|
|
138
|
+
|
|
139
|
+
# Prevents the previous file from being destroyed when this save
|
|
140
|
+
# created the record (as opposed to updating an existing one), since
|
|
141
|
+
# a record being created can't yet have its own confirmed attachment
|
|
142
|
+
# to safely replace (e.g. when it was duplicated from another,
|
|
143
|
+
# still-persisted record).
|
|
144
|
+
def destroy_previous?
|
|
145
|
+
return super unless sequel?
|
|
146
|
+
|
|
147
|
+
super && !@previously_new_record
|
|
148
|
+
end
|
|
135
149
|
end
|
|
136
150
|
end
|
|
137
151
|
|
|
@@ -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
|
|
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:
|
|
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:
|
|
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
|
|
69
|
+
instrument(:image_dimensions, io:, &block)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|