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
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Shrine
|
|
4
|
+
module Plugins
|
|
5
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/derivatives
|
|
6
|
+
module Derivatives
|
|
7
|
+
LOG_SUBSCRIBER = -> (event) do
|
|
8
|
+
Shrine.logger.info "Derivatives (#{event.duration}ms) – #{{
|
|
9
|
+
processor: event[:processor],
|
|
10
|
+
processor_options: event[:processor_options],
|
|
11
|
+
uploader: event[:uploader],
|
|
12
|
+
}.inspect}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.load_dependencies(uploader, **)
|
|
16
|
+
uploader.plugin :default_url
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, versions_compatibility: false, **opts)
|
|
20
|
+
uploader.opts[:derivatives] ||= { processors: {}, processor_settings: {}, storage: proc { store_key }, mutex: true }
|
|
21
|
+
uploader.opts[:derivatives].merge!(opts)
|
|
22
|
+
|
|
23
|
+
# instrumentation plugin integration
|
|
24
|
+
uploader.subscribe(:derivatives, &log_subscriber) if uploader.respond_to?(:subscribe)
|
|
25
|
+
|
|
26
|
+
uploader::Attacher.include(VersionsCompatibility) if versions_compatibility
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module AttachmentMethods
|
|
30
|
+
def define_entity_methods(name)
|
|
31
|
+
super if defined?(super)
|
|
32
|
+
|
|
33
|
+
define_method(:"#{name}_derivatives") do |*args|
|
|
34
|
+
send(:"#{name}_attacher").get_derivatives(*args)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def define_model_methods(name)
|
|
39
|
+
super if defined?(super)
|
|
40
|
+
|
|
41
|
+
define_method(:"#{name}_derivatives!") do |*args, **options|
|
|
42
|
+
send(:"#{name}_attacher").create_derivatives(*args, **options)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module AttacherClassMethods
|
|
48
|
+
# Registers a derivatives processor on the attacher class.
|
|
49
|
+
#
|
|
50
|
+
# Attacher.derivatives_processor :thumbnails do |original|
|
|
51
|
+
# # ...
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# By default, Shrine will convert the source IO object into a file
|
|
55
|
+
# before it's passed to the processor block. You can set `download:
|
|
56
|
+
# false` to pass the source IO object to the processor block as is.
|
|
57
|
+
#
|
|
58
|
+
# Attacher.derivatives_processor :thumbnails, download: false do |original|
|
|
59
|
+
# # ...
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# This can be useful if you'd like to defer or avoid a possibly
|
|
63
|
+
# expensive download operation for processor logic that does not
|
|
64
|
+
# require it.
|
|
65
|
+
def derivatives_processor(name = :default, download: true, &block)
|
|
66
|
+
if block
|
|
67
|
+
shrine_class.derivatives_options[:processors][name.to_sym] = block
|
|
68
|
+
shrine_class.derivatives_options[:processor_settings][name.to_sym] = { download: download }
|
|
69
|
+
else
|
|
70
|
+
shrine_class.derivatives_options[:processors].fetch(name.to_sym) do
|
|
71
|
+
fail Error, "derivatives processor #{name.inspect} not registered" unless name == :default
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
alias derivatives derivatives_processor
|
|
76
|
+
|
|
77
|
+
# Returns settings for the given derivatives processor.
|
|
78
|
+
#
|
|
79
|
+
# Attacher.derivatives_processor_settings(:thumbnails) #=> { download: true }
|
|
80
|
+
def derivatives_processor_settings(name)
|
|
81
|
+
shrine_class.derivatives_options[:processor_settings][name.to_sym] || {}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Specifies default storage to which derivatives will be uploaded.
|
|
85
|
+
#
|
|
86
|
+
# Attacher.derivatives_storage :other_store
|
|
87
|
+
# # or
|
|
88
|
+
# Attacher.derivatives_storage do |name|
|
|
89
|
+
# if name == :thumbnail
|
|
90
|
+
# :thumbnail_store
|
|
91
|
+
# else
|
|
92
|
+
# :store
|
|
93
|
+
# end
|
|
94
|
+
# end
|
|
95
|
+
def derivatives_storage(storage_key = nil, &block)
|
|
96
|
+
if storage_key || block
|
|
97
|
+
shrine_class.derivatives_options[:storage] = storage_key || block
|
|
98
|
+
else
|
|
99
|
+
shrine_class.derivatives_options[:storage]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module AttacherMethods
|
|
105
|
+
attr_reader :derivatives
|
|
106
|
+
|
|
107
|
+
# Adds the ability to accept derivatives.
|
|
108
|
+
def initialize(derivatives: {}, **options)
|
|
109
|
+
super(**options)
|
|
110
|
+
|
|
111
|
+
@derivatives = derivatives
|
|
112
|
+
@derivatives_mutex = Mutex.new if shrine_class.derivatives_options[:mutex]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Convenience method for accessing derivatives.
|
|
116
|
+
#
|
|
117
|
+
# photo.image_derivatives[:thumb] #=> #<Shrine::UploadedFile>
|
|
118
|
+
# # can be shortened to
|
|
119
|
+
# photo.image(:thumb) #=> #<Shrine::UploadedFile>
|
|
120
|
+
def get(*path)
|
|
121
|
+
return super if path.empty?
|
|
122
|
+
|
|
123
|
+
get_derivatives(*path)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Convenience method for accessing derivatives.
|
|
127
|
+
#
|
|
128
|
+
# photo.image_derivatives.dig(:thumbnails, :large)
|
|
129
|
+
# # can be shortened to
|
|
130
|
+
# photo.image_derivatives(:thumbnails, :large)
|
|
131
|
+
def get_derivatives(*path)
|
|
132
|
+
return derivatives if path.empty?
|
|
133
|
+
|
|
134
|
+
path = derivative_path(path)
|
|
135
|
+
|
|
136
|
+
derivatives.dig(*path)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Allows generating a URL to the derivative by passing the derivative
|
|
140
|
+
# name.
|
|
141
|
+
#
|
|
142
|
+
# attacher.add_derivatives({ thumb: thumb })
|
|
143
|
+
# attacher.url(:thumb) #=> "https://example.org/thumb.jpg"
|
|
144
|
+
def url(*path, **options)
|
|
145
|
+
return super if path.empty?
|
|
146
|
+
|
|
147
|
+
path = derivative_path(path)
|
|
148
|
+
|
|
149
|
+
url = derivatives.dig(*path)&.url(**options)
|
|
150
|
+
url ||= default_url(**options, derivative: path)
|
|
151
|
+
url
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# In addition to promoting the main file, also promotes any cached
|
|
155
|
+
# derivatives. This is useful when these derivatives are being created
|
|
156
|
+
# as part of a direct upload.
|
|
157
|
+
#
|
|
158
|
+
# attacher.assign(io)
|
|
159
|
+
# attacher.add_derivative(:thumb, file, storage: :cache)
|
|
160
|
+
# attacher.promote
|
|
161
|
+
# attacher.stored?(attacher.derivatives[:thumb]) #=> true
|
|
162
|
+
def promote(**options)
|
|
163
|
+
super
|
|
164
|
+
promote_derivatives
|
|
165
|
+
create_derivatives if create_derivatives_on_promote?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Uploads any cached derivatives to permanent storage.
|
|
169
|
+
def promote_derivatives(**options)
|
|
170
|
+
stored_derivatives = map_derivative(derivatives) do |path, derivative|
|
|
171
|
+
if cached?(derivative)
|
|
172
|
+
upload_derivative(path, derivative, **options)
|
|
173
|
+
else
|
|
174
|
+
derivative
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
set_derivatives(stored_derivatives) unless derivatives == stored_derivatives
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# In addition to deleting the main file it also deletes any derivatives.
|
|
182
|
+
#
|
|
183
|
+
# attacher.add_derivatives({ thumb: thumb })
|
|
184
|
+
# attacher.derivatives[:thumb].exists? #=> true
|
|
185
|
+
# attacher.destroy
|
|
186
|
+
# attacher.derivatives[:thumb].exists? #=> false
|
|
187
|
+
def destroy
|
|
188
|
+
super
|
|
189
|
+
delete_derivatives
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Calls processor and adds returned derivatives.
|
|
193
|
+
#
|
|
194
|
+
# Attacher.derivatives_processor :my_processor do |original|
|
|
195
|
+
# # ...
|
|
196
|
+
# end
|
|
197
|
+
#
|
|
198
|
+
# attacher.create_derivatives(:my_processor)
|
|
199
|
+
def create_derivatives(*args, storage: nil, **options)
|
|
200
|
+
files = process_derivatives(*args, **options)
|
|
201
|
+
add_derivatives(files, storage: storage)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Uploads given hash of files and adds uploaded files to the
|
|
205
|
+
# derivatives hash.
|
|
206
|
+
#
|
|
207
|
+
# attacher.derivatives #=>
|
|
208
|
+
# # {
|
|
209
|
+
# # thumb: #<Shrine::UploadedFile>,
|
|
210
|
+
# # }
|
|
211
|
+
# attacher.add_derivatives({ cropped: cropped })
|
|
212
|
+
# attacher.derivatives #=>
|
|
213
|
+
# # {
|
|
214
|
+
# # thumb: #<Shrine::UploadedFile>,
|
|
215
|
+
# # cropped: #<Shrine::UploadedFile>,
|
|
216
|
+
# # }
|
|
217
|
+
def add_derivatives(files, **options)
|
|
218
|
+
new_derivatives = upload_derivatives(files, **options)
|
|
219
|
+
merge_derivatives(new_derivatives)
|
|
220
|
+
new_derivatives
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Uploads a given file and adds it to the derivatives hash.
|
|
224
|
+
#
|
|
225
|
+
# attacher.derivatives #=>
|
|
226
|
+
# # {
|
|
227
|
+
# # thumb: #<Shrine::UploadedFile>,
|
|
228
|
+
# # }
|
|
229
|
+
# attacher.add_derivative(:cropped, cropped)
|
|
230
|
+
# attacher.derivatives #=>
|
|
231
|
+
# # {
|
|
232
|
+
# # thumb: #<Shrine::UploadedFile>,
|
|
233
|
+
# # cropped: #<Shrine::UploadedFile>,
|
|
234
|
+
# # }
|
|
235
|
+
def add_derivative(name, file, **options)
|
|
236
|
+
add_derivatives({ name => file }, **options)
|
|
237
|
+
derivatives[name]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Uploads given hash of files.
|
|
241
|
+
#
|
|
242
|
+
# hash = attacher.upload_derivatives({ thumb: thumb })
|
|
243
|
+
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
|
244
|
+
def upload_derivatives(files, **options)
|
|
245
|
+
map_derivative(files) do |path, file|
|
|
246
|
+
upload_derivative(path, file, **options)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Uploads the given file and deletes it afterwards.
|
|
251
|
+
#
|
|
252
|
+
# hash = attacher.upload_derivative(:thumb, thumb)
|
|
253
|
+
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
|
254
|
+
def upload_derivative(path, file, storage: nil, **options)
|
|
255
|
+
path = derivative_path(path)
|
|
256
|
+
storage ||= derivative_storage(path)
|
|
257
|
+
|
|
258
|
+
file.open if file.is_a?(Tempfile) # refresh file descriptor
|
|
259
|
+
file.binmode if file.respond_to?(:binmode) # ensure binary mode
|
|
260
|
+
|
|
261
|
+
upload(file, storage, derivative: path, delete: true, action: :derivatives, **options)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Downloads the attached file and calls the specified processor.
|
|
265
|
+
#
|
|
266
|
+
# Attacher.derivatives_processor :thumbnails do |original|
|
|
267
|
+
# processor = ImageProcessing::MiniMagick.source(original)
|
|
268
|
+
#
|
|
269
|
+
# {
|
|
270
|
+
# small: processor.resize_to_limit!(300, 300),
|
|
271
|
+
# medium: processor.resize_to_limit!(500, 500),
|
|
272
|
+
# large: processor.resize_to_limit!(800, 800),
|
|
273
|
+
# }
|
|
274
|
+
# end
|
|
275
|
+
#
|
|
276
|
+
# attacher.process_derivatives(:thumbnails)
|
|
277
|
+
# #=> { small: #<File:...>, medium: #<File:...>, large: #<File:...> }
|
|
278
|
+
def process_derivatives(processor_name = :default, source = nil, **options)
|
|
279
|
+
# handle receiving only source file without a processor
|
|
280
|
+
unless processor_name.respond_to?(:to_sym)
|
|
281
|
+
source = processor_name
|
|
282
|
+
processor_name = :default
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
source ||= file!
|
|
286
|
+
|
|
287
|
+
processor_settings = self.class.derivatives_processor_settings(processor_name) || {}
|
|
288
|
+
|
|
289
|
+
if processor_settings[:download]
|
|
290
|
+
shrine_class.with_file(source) do |file|
|
|
291
|
+
_process_derivatives(processor_name, file, **options)
|
|
292
|
+
end
|
|
293
|
+
else
|
|
294
|
+
_process_derivatives(processor_name, source, **options)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Deep merges given uploaded derivatives with current derivatives.
|
|
299
|
+
#
|
|
300
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
|
|
301
|
+
# attacher.merge_derivatives({ two: uploaded_file })
|
|
302
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
|
303
|
+
def merge_derivatives(new_derivatives)
|
|
304
|
+
derivatives_synchronize do
|
|
305
|
+
merged_derivatives = deep_merge_derivatives(derivatives, new_derivatives)
|
|
306
|
+
set_derivatives(merged_derivatives)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Removes derivatives with specified name from the derivatives hash.
|
|
311
|
+
#
|
|
312
|
+
# attacher.derivatives
|
|
313
|
+
# #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }
|
|
314
|
+
#
|
|
315
|
+
# attacher.remove_derivatives(:two, :three)
|
|
316
|
+
# #=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)
|
|
317
|
+
#
|
|
318
|
+
# attacher.derivatives
|
|
319
|
+
# #=> { one: #<Shrine::UploadedFile> }
|
|
320
|
+
#
|
|
321
|
+
# Nested derivatives are also supported:
|
|
322
|
+
#
|
|
323
|
+
# attacher.derivatives
|
|
324
|
+
# #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> } }
|
|
325
|
+
#
|
|
326
|
+
# attacher.remove_derivatives([:nested, :two], [:nested, :three])
|
|
327
|
+
# #=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)
|
|
328
|
+
#
|
|
329
|
+
# attacher.derivatives
|
|
330
|
+
# #=> { nested: { one: #<Shrine::UploadedFile> } }
|
|
331
|
+
#
|
|
332
|
+
# The :delete option can be passed for deleting removed derivatives:
|
|
333
|
+
#
|
|
334
|
+
# attacher.derivatives
|
|
335
|
+
# #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }
|
|
336
|
+
#
|
|
337
|
+
# two, three = attacher.remove_derivatives(:two, :three, delete: true)
|
|
338
|
+
#
|
|
339
|
+
# two.exists? #=> false
|
|
340
|
+
# three.exists? #=> false
|
|
341
|
+
def remove_derivatives(*paths, delete: false)
|
|
342
|
+
removed_derivatives = paths.map do |path|
|
|
343
|
+
path = Array(path)
|
|
344
|
+
|
|
345
|
+
if path.one?
|
|
346
|
+
derivatives.delete(path.first)
|
|
347
|
+
else
|
|
348
|
+
derivatives.dig(*path[0..-2]).delete(path[-1])
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
set_derivatives derivatives
|
|
353
|
+
|
|
354
|
+
delete_derivatives(removed_derivatives) if delete
|
|
355
|
+
|
|
356
|
+
removed_derivatives
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Removes derivative with specified name from the derivatives hash.
|
|
360
|
+
#
|
|
361
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
|
362
|
+
# attacher.remove_derivative(:one) #=> #<Shrine::UploadedFile> (removed derivative)
|
|
363
|
+
# attacher.derivatives #=> { two: #<Shrine::UploadedFile> }
|
|
364
|
+
#
|
|
365
|
+
# Nested derivatives are also supported:
|
|
366
|
+
#
|
|
367
|
+
# attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
|
|
368
|
+
# attacher.remove_derivative([:nested, :one]) #=> #<Shrine::UploadedFile> (removed derivative)
|
|
369
|
+
# attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
|
|
370
|
+
#
|
|
371
|
+
# The :delete option can be passed for deleting removed derivative:
|
|
372
|
+
#
|
|
373
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
|
374
|
+
# derivative = attacher.remove_derivatives(:two, delete: true)
|
|
375
|
+
# derivative.exists? #=> false
|
|
376
|
+
def remove_derivative(path, **options)
|
|
377
|
+
remove_derivatives(path, **options).first
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Deletes given hash of uploaded files.
|
|
381
|
+
#
|
|
382
|
+
# attacher.delete_derivatives({ thumb: uploaded_file })
|
|
383
|
+
# uploaded_file.exists? #=> false
|
|
384
|
+
def delete_derivatives(derivatives = self.derivatives)
|
|
385
|
+
map_derivative(derivatives) { |_, derivative| derivative.delete }
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Sets the given hash of uploaded files as derivatives.
|
|
389
|
+
#
|
|
390
|
+
# attacher.set_derivatives({ thumb: uploaded_file })
|
|
391
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
|
392
|
+
def set_derivatives(derivatives)
|
|
393
|
+
self.derivatives = derivatives
|
|
394
|
+
set file # trigger model write
|
|
395
|
+
derivatives
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Adds derivative data into the hash.
|
|
399
|
+
#
|
|
400
|
+
# attacher.attach(io)
|
|
401
|
+
# attacher.add_derivatives({ thumb: thumb })
|
|
402
|
+
# attacher.data
|
|
403
|
+
# #=>
|
|
404
|
+
# # {
|
|
405
|
+
# # "id" => "...",
|
|
406
|
+
# # "storage" => "store",
|
|
407
|
+
# # "metadata" => { ... },
|
|
408
|
+
# # "derivatives" => {
|
|
409
|
+
# # "thumb" => {
|
|
410
|
+
# # "id" => "...",
|
|
411
|
+
# # "storage" => "store",
|
|
412
|
+
# # "metadata" => { ... },
|
|
413
|
+
# # }
|
|
414
|
+
# # }
|
|
415
|
+
# # }
|
|
416
|
+
def data
|
|
417
|
+
result = super
|
|
418
|
+
|
|
419
|
+
if derivatives.any?
|
|
420
|
+
result ||= {}
|
|
421
|
+
result["derivatives"] = map_derivative(derivatives, transform_keys: :to_s) do |_, derivative|
|
|
422
|
+
derivative.data
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
result
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Loads derivatives from data generated by `Attacher#data`.
|
|
430
|
+
#
|
|
431
|
+
# attacher.load_data({
|
|
432
|
+
# "id" => "...",
|
|
433
|
+
# "storage" => "store",
|
|
434
|
+
# "metadata" => { ... },
|
|
435
|
+
# "derivatives" => {
|
|
436
|
+
# "thumb" => {
|
|
437
|
+
# "id" => "...",
|
|
438
|
+
# "storage" => "store",
|
|
439
|
+
# "metadata" => { ... },
|
|
440
|
+
# }
|
|
441
|
+
# }
|
|
442
|
+
# })
|
|
443
|
+
# attacher.file #=> #<Shrine::UploadedFile>
|
|
444
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
|
445
|
+
def load_data(data)
|
|
446
|
+
data ||= {}
|
|
447
|
+
data = data.dup
|
|
448
|
+
|
|
449
|
+
derivatives_data = data.delete("derivatives") || data.delete(:derivatives) || {}
|
|
450
|
+
@derivatives = shrine_class.derivatives(derivatives_data)
|
|
451
|
+
|
|
452
|
+
data = nil if data.empty?
|
|
453
|
+
|
|
454
|
+
super(data)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Clears derivatives when attachment changes.
|
|
458
|
+
#
|
|
459
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
|
460
|
+
# attacher.change(file)
|
|
461
|
+
# attacher.derivatives #=> {}
|
|
462
|
+
def change(*)
|
|
463
|
+
result = super
|
|
464
|
+
set_derivatives({})
|
|
465
|
+
result
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Sets a hash of derivatives.
|
|
469
|
+
#
|
|
470
|
+
# attacher.derivatives = { thumb: Shrine.uploaded_file(...) }
|
|
471
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile ...> }
|
|
472
|
+
def derivatives=(derivatives)
|
|
473
|
+
unless derivatives.is_a?(Hash)
|
|
474
|
+
fail ArgumentError, "expected derivatives to be a Hash, got #{derivatives.inspect}"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
@derivatives = derivatives
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Iterates through nested derivatives and maps results.
|
|
481
|
+
#
|
|
482
|
+
# attacher.map_derivative(derivatives) { |path, file| ... }
|
|
483
|
+
def map_derivative(derivatives, **options, &block)
|
|
484
|
+
shrine_class.map_derivative(derivatives, **options, &block)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
private
|
|
488
|
+
|
|
489
|
+
# Calls the derivatives processor with the source file and options.
|
|
490
|
+
def _process_derivatives(processor_name, source, **options)
|
|
491
|
+
processor = self.class.derivatives_processor(processor_name)
|
|
492
|
+
|
|
493
|
+
return {} unless processor
|
|
494
|
+
|
|
495
|
+
result = instrument_derivatives(processor_name, source, options) do
|
|
496
|
+
instance_exec(source, **options, &processor)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
unless result.is_a?(Hash)
|
|
500
|
+
fail Error, "expected derivatives processor #{processor_name.inspect} to return a Hash, got #{result.inspect}"
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
result
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Sends a `derivatives.shrine` event for instrumentation plugin.
|
|
507
|
+
def instrument_derivatives(processor_name, source, processor_options, &block)
|
|
508
|
+
return yield unless shrine_class.respond_to?(:instrument)
|
|
509
|
+
|
|
510
|
+
shrine_class.instrument(:derivatives, {
|
|
511
|
+
processor: processor_name,
|
|
512
|
+
processor_options: processor_options,
|
|
513
|
+
io: source,
|
|
514
|
+
attacher: self,
|
|
515
|
+
}, &block)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Returns symbolized array or single key.
|
|
519
|
+
def derivative_path(path)
|
|
520
|
+
path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
|
|
521
|
+
path = path.first if path.one?
|
|
522
|
+
path
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Storage to which derivatives will be uploaded to by default.
|
|
526
|
+
def derivative_storage(path)
|
|
527
|
+
storage = self.class.derivatives_storage
|
|
528
|
+
storage = instance_exec(path, &storage) if storage.respond_to?(:call)
|
|
529
|
+
storage
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Deep merge nested hashes/arrays.
|
|
533
|
+
def deep_merge_derivatives(o1, o2)
|
|
534
|
+
if o1.is_a?(Hash) && o2.is_a?(Hash)
|
|
535
|
+
o1.merge(o2) { |_, v1, v2| deep_merge_derivatives(v1, v2) }
|
|
536
|
+
elsif o1.is_a?(Array) && o2.is_a?(Array)
|
|
537
|
+
o1 + o2
|
|
538
|
+
else
|
|
539
|
+
o2
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Whether to automatically create derivatives on promotion
|
|
544
|
+
def create_derivatives_on_promote?
|
|
545
|
+
shrine_class.derivatives_options[:create_on_promote]
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def derivatives_synchronize
|
|
549
|
+
if @derivatives_mutex
|
|
550
|
+
@derivatives_mutex.synchronize { yield }
|
|
551
|
+
else
|
|
552
|
+
yield
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
module ClassMethods
|
|
558
|
+
# Converts data into a Hash of derivatives.
|
|
559
|
+
#
|
|
560
|
+
# Shrine.derivatives('{"thumb":{"id":"foo","storage":"store","metadata":{}}}')
|
|
561
|
+
# #=> { thumb: #<Shrine::UploadedFile id="foo" storage=:store metadata={}> }
|
|
562
|
+
#
|
|
563
|
+
# Shrine.derivatives({ "thumb" => { "id" => "foo", "storage" => "store", "metadata" => {} } })
|
|
564
|
+
# #=> { thumb: #<Shrine::UploadedFile id="foo" storage=:store metadata={}> }
|
|
565
|
+
#
|
|
566
|
+
# Shrine.derivatives({ thumb: { id: "foo", storage: "store", metadata: {} } })
|
|
567
|
+
# #=> { thumb: #<Shrine::UploadedFile id="foo" storage=:store metadata={}> }
|
|
568
|
+
def derivatives(object)
|
|
569
|
+
if object.is_a?(String)
|
|
570
|
+
derivatives JSON.parse(object)
|
|
571
|
+
elsif object.is_a?(Hash) || object.is_a?(Array)
|
|
572
|
+
map_derivative(
|
|
573
|
+
object,
|
|
574
|
+
transform_keys: :to_sym,
|
|
575
|
+
leaf: -> (value) { value.is_a?(Hash) && (value["id"] || value[:id]).is_a?(String) },
|
|
576
|
+
) { |_, value| uploaded_file(value) }
|
|
577
|
+
else
|
|
578
|
+
fail ArgumentError, "cannot convert #{object.inspect} to derivatives"
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# Iterates over a nested collection, yielding on each part of the path.
|
|
583
|
+
# If the block returns a truthy value, that branch is terminated
|
|
584
|
+
def map_derivative(object, path = [], transform_keys: :to_sym, leaf: nil, &block)
|
|
585
|
+
return enum_for(__method__, object) unless block_given?
|
|
586
|
+
|
|
587
|
+
if leaf && leaf.call(object)
|
|
588
|
+
yield path, object
|
|
589
|
+
elsif object.is_a?(Hash)
|
|
590
|
+
object.inject({}) do |hash, (key, value)|
|
|
591
|
+
key = key.send(transform_keys)
|
|
592
|
+
|
|
593
|
+
hash.merge! key => map_derivative(
|
|
594
|
+
value, [*path, key],
|
|
595
|
+
transform_keys: transform_keys, leaf: leaf,
|
|
596
|
+
&block
|
|
597
|
+
)
|
|
598
|
+
end
|
|
599
|
+
elsif object.is_a?(Array)
|
|
600
|
+
object.map.with_index do |value, idx|
|
|
601
|
+
map_derivative(
|
|
602
|
+
value, [*path, idx],
|
|
603
|
+
transform_keys: transform_keys, leaf: leaf,
|
|
604
|
+
&block
|
|
605
|
+
)
|
|
606
|
+
end
|
|
607
|
+
else
|
|
608
|
+
yield path, object
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Returns derivatives plugin options.
|
|
613
|
+
def derivatives_options
|
|
614
|
+
opts[:derivatives]
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
module FileMethods
|
|
619
|
+
def [](*keys)
|
|
620
|
+
if keys.any? { |key| key.is_a?(Symbol) }
|
|
621
|
+
fail Error, "Shrine::UploadedFile#[] doesn't accept symbol metadata names. Did you happen to call `record.attachment[:derivative_name]` when you meant to call `record.attachment(:derivative_name)`?"
|
|
622
|
+
else
|
|
623
|
+
super
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# Adds compatibility with how the versions plugin stores processed files.
|
|
629
|
+
module VersionsCompatibility
|
|
630
|
+
def load_data(data)
|
|
631
|
+
return super if data.nil?
|
|
632
|
+
return super if data["derivatives"] || data[:derivatives]
|
|
633
|
+
return super if (data["id"] || data[:id]).is_a?(String)
|
|
634
|
+
|
|
635
|
+
data = data.dup
|
|
636
|
+
original = data.delete("original") || data.delete(:original) || {}
|
|
637
|
+
|
|
638
|
+
super original.merge("derivatives" => data)
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
register_plugin(:derivatives, Derivatives)
|
|
644
|
+
end
|
|
645
|
+
end
|