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
@@ -2,18 +2,18 @@
|
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
5
|
-
# Documentation
|
6
|
-
#
|
7
|
-
# [doc/plugins/included.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/included.md
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/included
|
8
6
|
module Included
|
9
7
|
def self.configure(uploader, &block)
|
10
|
-
uploader.opts[:
|
8
|
+
uploader.opts[:included] ||= {}
|
9
|
+
uploader.opts[:included][:block] = block
|
11
10
|
end
|
12
11
|
|
13
12
|
module AttachmentMethods
|
14
|
-
def included(
|
13
|
+
def included(klass)
|
15
14
|
super
|
16
|
-
|
15
|
+
|
16
|
+
klass.instance_exec(@name, &shrine_class.opts[:included][:block])
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "pathname"
|
4
|
+
|
3
5
|
class Shrine
|
4
6
|
module Plugins
|
5
|
-
# Documentation
|
6
|
-
#
|
7
|
-
# [doc/plugins/infer_extension.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/infer_extension.md
|
7
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/infer_extension
|
8
8
|
module InferExtension
|
9
9
|
LOG_SUBSCRIBER = -> (event) do
|
10
10
|
Shrine.logger.info "Extension (#{event.duration}ms) – #{{
|
@@ -13,14 +13,12 @@ class Shrine
|
|
13
13
|
}.inspect}"
|
14
14
|
end
|
15
15
|
|
16
|
-
def self.configure(uploader,
|
17
|
-
uploader.opts[:infer_extension] ||= { inferrer: :
|
16
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
17
|
+
uploader.opts[:infer_extension] ||= { inferrer: :mini_mime }
|
18
18
|
uploader.opts[:infer_extension].merge!(opts)
|
19
19
|
|
20
20
|
# instrumentation plugin integration
|
21
|
-
if uploader.respond_to?(:subscribe)
|
22
|
-
uploader.subscribe(:extension, &uploader.opts[:infer_extension][:log_subscriber])
|
23
|
-
end
|
21
|
+
uploader.subscribe(:extension, &log_subscriber) if uploader.respond_to?(:subscribe)
|
24
22
|
end
|
25
23
|
|
26
24
|
module ClassMethods
|
@@ -53,22 +51,17 @@ class Shrine
|
|
53
51
|
end
|
54
52
|
|
55
53
|
module InstanceMethods
|
54
|
+
private
|
55
|
+
|
56
56
|
def basic_location(io, metadata:)
|
57
|
-
location = super
|
58
|
-
current_extension = File.extname(location)
|
57
|
+
location = Pathname(super)
|
59
58
|
|
60
|
-
if
|
61
|
-
inferred_extension = infer_extension(metadata["mime_type"])
|
62
|
-
location = location.
|
59
|
+
if location.extname.empty? || opts[:infer_extension][:force]
|
60
|
+
inferred_extension = self.class.infer_extension(metadata["mime_type"])
|
61
|
+
location = location.sub_ext(inferred_extension) if inferred_extension
|
63
62
|
end
|
64
63
|
|
65
|
-
location
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def infer_extension(mime_type)
|
71
|
-
self.class.infer_extension(mime_type).to_s
|
64
|
+
location.to_s
|
72
65
|
end
|
73
66
|
end
|
74
67
|
|
@@ -2,25 +2,20 @@
|
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
5
|
-
# Documentation
|
6
|
-
#
|
7
|
-
# [doc/plugins/instrumentation.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/instrumentation.md
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/instrumentation
|
8
6
|
module Instrumentation
|
9
|
-
EVENTS = %i[upload download exists delete metadata].freeze
|
7
|
+
EVENTS = %i[upload download open exists delete metadata].freeze
|
10
8
|
|
11
9
|
# We use a proc in order to be able identify listeners.
|
12
10
|
LOG_SUBSCRIBER = -> (event) { LogSubscriber.call(event) }
|
13
11
|
|
14
|
-
def self.configure(uploader,
|
15
|
-
uploader.opts[:instrumentation] ||= {
|
12
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
13
|
+
uploader.opts[:instrumentation] ||= { log_events: EVENTS, subscribers: {} }
|
16
14
|
uploader.opts[:instrumentation].merge!(opts)
|
17
15
|
uploader.opts[:instrumentation][:notifications] ||= ::ActiveSupport::Notifications
|
18
16
|
|
19
|
-
# we assign it to the top-level so that it's duplicated on subclassing
|
20
|
-
uploader.opts[:instrumentation_subscribers] ||= Hash.new { |h, k| h[k] = [] }
|
21
|
-
|
22
17
|
uploader.opts[:instrumentation][:log_events].each do |event_name|
|
23
|
-
uploader.subscribe(event_name, &
|
18
|
+
uploader.subscribe(event_name, &log_subscriber)
|
24
19
|
end
|
25
20
|
end
|
26
21
|
|
@@ -48,12 +43,13 @@ class Shrine
|
|
48
43
|
# end
|
49
44
|
def subscribe(event_name, &subscriber)
|
50
45
|
return if subscriber.nil?
|
51
|
-
return if subscribers[event_name]
|
46
|
+
return if subscribers[event_name]&.include?(subscriber)
|
52
47
|
|
53
48
|
notifications.subscribe("#{event_name}.shrine") do |event|
|
54
49
|
subscriber.call(event) if event[:uploader] <= self
|
55
50
|
end
|
56
51
|
|
52
|
+
subscribers[event_name] ||= []
|
57
53
|
subscribers[event_name] << subscriber
|
58
54
|
end
|
59
55
|
|
@@ -63,12 +59,8 @@ class Shrine
|
|
63
59
|
Notifications.new(opts[:instrumentation][:notifications])
|
64
60
|
end
|
65
61
|
|
66
|
-
def log_subscriber
|
67
|
-
opts[:instrumentation][:log_subscriber]
|
68
|
-
end
|
69
|
-
|
70
62
|
def subscribers
|
71
|
-
opts[:
|
63
|
+
opts[:instrumentation][:subscribers]
|
72
64
|
end
|
73
65
|
end
|
74
66
|
|
@@ -76,57 +68,68 @@ class Shrine
|
|
76
68
|
private
|
77
69
|
|
78
70
|
# Sends a `upload.shrine` event.
|
79
|
-
def
|
80
|
-
self.class.instrument(
|
81
|
-
:upload,
|
71
|
+
def _upload(io, location:, metadata:, upload_options: {}, **options)
|
72
|
+
self.class.instrument(:upload, {
|
82
73
|
storage: storage_key,
|
83
|
-
location:
|
74
|
+
location: location,
|
84
75
|
io: io,
|
85
|
-
upload_options:
|
86
|
-
|
87
|
-
|
76
|
+
upload_options: upload_options,
|
77
|
+
metadata: metadata,
|
78
|
+
options: options,
|
79
|
+
}) { super }
|
88
80
|
end
|
89
81
|
|
90
82
|
# Sends a `metadata.shrine` event.
|
91
|
-
def get_metadata(io,
|
92
|
-
return super if io.is_a?(UploadedFile) &&
|
83
|
+
def get_metadata(io, metadata: nil, **options)
|
84
|
+
return super if io.is_a?(UploadedFile) && metadata != true || metadata == false
|
93
85
|
|
94
|
-
self.class.instrument(
|
95
|
-
:metadata,
|
86
|
+
self.class.instrument(:metadata, {
|
96
87
|
storage: storage_key,
|
97
88
|
io: io,
|
98
|
-
options:
|
99
|
-
) { super }
|
89
|
+
options: options,
|
90
|
+
}) { super }
|
100
91
|
end
|
101
92
|
end
|
102
93
|
|
103
94
|
module FileMethods
|
104
95
|
# Sends a `download.shrine` event.
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
96
|
+
def stream(destination, **options)
|
97
|
+
return super if opened?
|
98
|
+
|
99
|
+
shrine_class.instrument(:download, {
|
100
|
+
storage: storage_key,
|
109
101
|
location: id,
|
110
102
|
download_options: options,
|
111
|
-
) { super }
|
103
|
+
}) { super(destination, **options, instrument: false) }
|
112
104
|
end
|
113
105
|
|
114
106
|
# Sends a `exists.shrine` event.
|
115
107
|
def exists?
|
116
|
-
shrine_class.instrument(
|
117
|
-
:
|
118
|
-
storage: storage_key.to_sym,
|
108
|
+
shrine_class.instrument(:exists, {
|
109
|
+
storage: storage_key,
|
119
110
|
location: id,
|
120
|
-
) { super }
|
111
|
+
}) { super }
|
121
112
|
end
|
122
113
|
|
123
114
|
# Sends a `delete.shrine` event.
|
124
115
|
def delete
|
125
|
-
shrine_class.instrument(
|
126
|
-
:
|
127
|
-
|
116
|
+
shrine_class.instrument(:delete, {
|
117
|
+
storage: storage_key,
|
118
|
+
location: id,
|
119
|
+
}) { super }
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Sends an `open.shrine` event.
|
125
|
+
def _open(instrument: true, **options)
|
126
|
+
return super(**options) unless instrument
|
127
|
+
|
128
|
+
shrine_class.instrument(:open, {
|
129
|
+
storage: storage_key,
|
128
130
|
location: id,
|
129
|
-
|
131
|
+
download_options: options,
|
132
|
+
}) { super(**options) }
|
130
133
|
end
|
131
134
|
end
|
132
135
|
|
@@ -255,6 +258,15 @@ class Shrine
|
|
255
258
|
)}"
|
256
259
|
end
|
257
260
|
|
261
|
+
def on_open(event)
|
262
|
+
log "Open (#{event.duration}ms) – #{format(
|
263
|
+
storage: event[:storage],
|
264
|
+
location: event[:location],
|
265
|
+
download_options: event[:download_options],
|
266
|
+
uploader: event[:uploader],
|
267
|
+
)}"
|
268
|
+
end
|
269
|
+
|
258
270
|
def on_exists(event)
|
259
271
|
log "Exists (#{event.duration}ms) – #{format(
|
260
272
|
storage: event[:storage],
|
@@ -2,23 +2,11 @@
|
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
5
|
-
# Documentation
|
6
|
-
#
|
7
|
-
# [doc/plugins/keep_files.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/keep_files.md
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/keep_files
|
8
6
|
module KeepFiles
|
9
|
-
def self.configure(uploader, opts = {})
|
10
|
-
keep_files = (uploader.opts[:keep_files] ||= [])
|
11
|
-
opts[:destroyed] ? keep_files << :destroyed : keep_files.delete(:destroyed) if opts.key?(:destroyed)
|
12
|
-
opts[:replaced] ? keep_files << :replaced : keep_files.delete(:replaced) if opts.key?(:replaced)
|
13
|
-
end
|
14
|
-
|
15
7
|
module AttacherMethods
|
16
|
-
def
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def destroy
|
21
|
-
super unless shrine_class.opts[:keep_files].include?(:destroyed)
|
8
|
+
def destroy?
|
9
|
+
false
|
22
10
|
end
|
23
11
|
end
|
24
12
|
end
|
@@ -2,37 +2,46 @@
|
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
5
|
-
# Documentation
|
6
|
-
#
|
7
|
-
# [doc/plugins/metadata_attributes.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/metadata_attributes.md
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/metadata_attributes
|
8
6
|
module MetadataAttributes
|
9
|
-
def self.
|
10
|
-
uploader.
|
11
|
-
|
7
|
+
def self.load_dependencies(uploader, *)
|
8
|
+
uploader.plugin :entity
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.configure(uploader, **opts)
|
12
|
+
uploader.opts[:metadata_attributes] ||= {}
|
13
|
+
uploader.opts[:metadata_attributes].merge!(opts)
|
12
14
|
end
|
13
15
|
|
14
16
|
module AttacherClassMethods
|
15
|
-
def metadata_attributes(mappings)
|
16
|
-
|
17
|
+
def metadata_attributes(mappings = nil)
|
18
|
+
if mappings
|
19
|
+
shrine_class.opts[:metadata_attributes].merge!(mappings)
|
20
|
+
else
|
21
|
+
shrine_class.opts[:metadata_attributes]
|
22
|
+
end
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
26
|
module AttacherMethods
|
21
|
-
def
|
22
|
-
super
|
23
|
-
|
27
|
+
def column_values
|
28
|
+
super.merge(metadata_attributes)
|
29
|
+
end
|
24
30
|
|
25
|
-
|
26
|
-
attribute_name = destination.is_a?(Symbol) ? :"#{name}_#{destination}" : :"#{destination}"
|
31
|
+
private
|
27
32
|
|
28
|
-
|
33
|
+
def metadata_attributes
|
34
|
+
values = {}
|
29
35
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
self.class.metadata_attributes.each do |source, destination|
|
37
|
+
metadata_attribute = destination.is_a?(Symbol) ? :"#{name}_#{destination}" : :"#{destination}"
|
38
|
+
|
39
|
+
next unless record.respond_to?(metadata_attribute)
|
40
|
+
|
41
|
+
values[metadata_attribute] = file && file.metadata[source.to_s]
|
35
42
|
end
|
43
|
+
|
44
|
+
values
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shrine
|
4
|
+
module Plugins
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/mirroring
|
6
|
+
module Mirroring
|
7
|
+
def self.configure(uploader, **opts)
|
8
|
+
uploader.opts[:mirroring] ||= { upload: true, delete: true }
|
9
|
+
uploader.opts[:mirroring].merge!(opts)
|
10
|
+
|
11
|
+
fail Error, ":mirror option is required for mirroring plugin" unless uploader.opts[:mirroring][:mirror]
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def mirrors(storage_key = nil)
|
16
|
+
if storage_key
|
17
|
+
mirrors = opts[:mirroring][:mirror][storage_key]
|
18
|
+
|
19
|
+
fail Error, "no mirrors registered for storage #{storage_key.inspect}" unless mirrors
|
20
|
+
|
21
|
+
Array(mirrors)
|
22
|
+
else
|
23
|
+
opts[:mirroring][:mirror]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def mirror_upload_block(&block)
|
28
|
+
if block
|
29
|
+
opts[:mirroring][:upload_block] = block
|
30
|
+
else
|
31
|
+
opts[:mirroring][:upload_block]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def mirror_delete_block(&block)
|
36
|
+
if block
|
37
|
+
opts[:mirroring][:delete_block] = block
|
38
|
+
else
|
39
|
+
opts[:mirroring][:delete_block]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def mirror_upload?
|
44
|
+
opts[:mirroring][:upload]
|
45
|
+
end
|
46
|
+
|
47
|
+
def mirror_delete?
|
48
|
+
opts[:mirroring][:delete]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module InstanceMethods
|
53
|
+
# Mirrors upload to other mirror storages.
|
54
|
+
def upload(io, mirror: true, **options)
|
55
|
+
file = super(io, **options)
|
56
|
+
file.trigger_mirror_upload(**options) if mirror
|
57
|
+
file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module FileMethods
|
62
|
+
# Mirrors upload if mirrors are defined. Calls mirror block if
|
63
|
+
# registered, otherwise mirrors synchronously.
|
64
|
+
def trigger_mirror_upload(**options)
|
65
|
+
return unless shrine_class.mirrors[storage_key] && shrine_class.mirror_upload?
|
66
|
+
|
67
|
+
if shrine_class.mirror_upload_block
|
68
|
+
mirror_upload_background(**options)
|
69
|
+
else
|
70
|
+
mirror_upload(**options)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Calls mirror upload block.
|
75
|
+
def mirror_upload_background(**options)
|
76
|
+
fail Error, "mirror upload block is not registered" unless shrine_class.mirror_upload_block
|
77
|
+
|
78
|
+
shrine_class.mirror_upload_block.call(self, **options)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Uploads the file to each mirror storage.
|
82
|
+
def mirror_upload(**options)
|
83
|
+
previously_opened = opened?
|
84
|
+
|
85
|
+
each_mirror do |mirror|
|
86
|
+
rewind if opened?
|
87
|
+
|
88
|
+
shrine_class.upload(self, mirror, **options, location: id, close: false, action: :mirror)
|
89
|
+
end
|
90
|
+
ensure
|
91
|
+
if opened? && !previously_opened
|
92
|
+
close
|
93
|
+
@io = nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Mirrors delete to other mirror storages.
|
98
|
+
def delete(mirror: true)
|
99
|
+
result = super()
|
100
|
+
trigger_mirror_delete if mirror
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
# Mirrors delete if mirrors are defined. Calls mirror block if
|
105
|
+
# registered, otherwise mirrors synchronously.
|
106
|
+
def trigger_mirror_delete
|
107
|
+
return unless shrine_class.mirrors[storage_key] && shrine_class.mirror_delete?
|
108
|
+
|
109
|
+
if shrine_class.mirror_delete_block
|
110
|
+
mirror_delete_background
|
111
|
+
else
|
112
|
+
mirror_delete
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Calls mirror delete block.
|
117
|
+
def mirror_delete_background
|
118
|
+
fail Error, "mirror delete block is not registered" unless shrine_class.mirror_delete_block
|
119
|
+
|
120
|
+
shrine_class.mirror_delete_block.call(self)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Deletes the file from each mirror storage.
|
124
|
+
def mirror_delete
|
125
|
+
each_mirror do |mirror|
|
126
|
+
self.class.new(id: id, storage: mirror).delete
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Iterates over mirror storages.
|
133
|
+
def each_mirror(&block)
|
134
|
+
mirrors = shrine_class.mirrors(storage_key)
|
135
|
+
mirrors.map(&block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
register_plugin(:mirroring, Mirroring)
|
141
|
+
end
|
142
|
+
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/model
|
6
|
+
module Model
|
7
|
+
def self.load_dependencies(uploader, **)
|
8
|
+
uploader.plugin :entity
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.configure(uploader, **opts)
|
12
|
+
uploader.opts[:model] ||= { cache: true }
|
13
|
+
uploader.opts[:model].merge!(opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
module AttachmentMethods
|
17
|
+
# Allows disabling model behaviour:
|
18
|
+
#
|
19
|
+
# Shrine::Attachment(:image) # model (default)
|
20
|
+
# Shrine::Attachment(:image, model: false) # entity
|
21
|
+
def initialize(name, model: true, **options)
|
22
|
+
super(name, **options)
|
23
|
+
@model = model
|
24
|
+
end
|
25
|
+
|
26
|
+
# We define model methods only on inclusion. This gives other plugins
|
27
|
+
# the ability to disable model behaviour for entity classes. In this
|
28
|
+
# case we want to skip defining model methods as well.
|
29
|
+
def included(klass)
|
30
|
+
super
|
31
|
+
define_model_methods(@name) if model?
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Defines attachment setter and enhances the copy constructor.
|
37
|
+
def define_model_methods(name)
|
38
|
+
super if defined?(super)
|
39
|
+
|
40
|
+
define_method :"#{name}=" do |value|
|
41
|
+
send(:"#{name}_attacher").model_assign(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
define_method :"#{name}_changed?" do
|
45
|
+
send(:"#{name}_attacher").changed?
|
46
|
+
end
|
47
|
+
|
48
|
+
# The copy constructor that's called on #dup and #clone.
|
49
|
+
define_method :initialize_copy do |other|
|
50
|
+
super(other)
|
51
|
+
instance_variable_set(:"@#{name}_attacher", instance_variable_get(:"@#{name}_attacher")&.dup)
|
52
|
+
self
|
53
|
+
end
|
54
|
+
private :initialize_copy
|
55
|
+
end
|
56
|
+
|
57
|
+
# Memoizes the attacher instance into an instance variable.
|
58
|
+
def attacher(record, **options)
|
59
|
+
return super unless model?
|
60
|
+
|
61
|
+
if !record.instance_variable_get(:"@#{@name}_attacher") || options.any?
|
62
|
+
attacher = class_attacher(**options)
|
63
|
+
attacher.load_model(record, @name)
|
64
|
+
|
65
|
+
record.instance_variable_set(:"@#{@name}_attacher", attacher)
|
66
|
+
else
|
67
|
+
record.instance_variable_get(:"@#{@name}_attacher")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def model?
|
72
|
+
@model
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module AttacherClassMethods
|
77
|
+
# Initializes itself from a model instance and attachment name.
|
78
|
+
#
|
79
|
+
# photo.image_data #=> "{...}" # a file is attached
|
80
|
+
#
|
81
|
+
# attacher = Attacher.from_model(photo, :image)
|
82
|
+
# attacher.file #=> #<Shrine::UploadedFile>
|
83
|
+
def from_model(record, name, **options)
|
84
|
+
attacher = new(**options)
|
85
|
+
attacher.load_model(record, name)
|
86
|
+
attacher
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module AttacherMethods
|
91
|
+
def initialize(model_cache: shrine_class.opts[:model][:cache], **options)
|
92
|
+
super(**options)
|
93
|
+
@model_cache = model_cache
|
94
|
+
@model = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
# Saves record and name and initializes attachment from the model
|
98
|
+
# attribute. Called from `Attacher.from_model`.
|
99
|
+
def load_model(record, name)
|
100
|
+
set_model(record, name)
|
101
|
+
read
|
102
|
+
end
|
103
|
+
|
104
|
+
# Saves record and name without loading attachment from the model
|
105
|
+
# attribute.
|
106
|
+
def set_model(record, name)
|
107
|
+
set_entity(record, name)
|
108
|
+
@model = true
|
109
|
+
end
|
110
|
+
|
111
|
+
# Called by the attachment attribute setter on the model.
|
112
|
+
def model_assign(value, **options)
|
113
|
+
if model_cache?
|
114
|
+
assign(value, **options)
|
115
|
+
else
|
116
|
+
attach(value, **options)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Writes uploaded file data into the model.
|
121
|
+
def set(*)
|
122
|
+
result = super
|
123
|
+
write if model?
|
124
|
+
result
|
125
|
+
end
|
126
|
+
|
127
|
+
# Writes the attachment data into the model attribute.
|
128
|
+
def write
|
129
|
+
column_values.each do |name, value|
|
130
|
+
write_attribute(name, value)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# Writes given value into the model attribute.
|
137
|
+
def write_attribute(name = attribute, value)
|
138
|
+
record.public_send(:"#{name}=", value)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns whether assigned files should be uploaded to/loaded from
|
142
|
+
# temporary storage.
|
143
|
+
def model_cache?
|
144
|
+
@model_cache
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns whether the attacher is being backed by a model instance.
|
148
|
+
# This allows users to still use the attacher with an entity instance
|
149
|
+
# or without any record instance.
|
150
|
+
def model?
|
151
|
+
@model
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
register_plugin(:model, Model)
|
157
|
+
end
|
158
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
Shrine.deprecation("The module_include plugin is deprecated and will be removed in Shrine 4. Override core classes directly instead.")
|
4
|
+
|
3
5
|
class Shrine
|
4
6
|
module Plugins
|
5
|
-
# Documentation
|
6
|
-
#
|
7
|
-
# [doc/plugins/module_include.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/module_include.md
|
7
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/module_include
|
8
8
|
module ModuleInclude
|
9
9
|
module ClassMethods
|
10
10
|
def attachment_module(mod = nil, &block)
|