shrine 2.19.4 → 3.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +299 -11
- data/README.md +9 -3
- data/doc/advantages.md +1 -1
- data/doc/carrierwave.md +4 -4
- data/doc/creating_persistence_plugins.md +172 -0
- data/doc/creating_plugins.md +1 -1
- data/doc/creating_storages.md +3 -1
- data/doc/design.md +2 -2
- data/doc/direct_s3.md +0 -22
- data/doc/paperclip.md +3 -3
- data/doc/plugins/activerecord.md +211 -42
- data/doc/plugins/atomic_helpers.md +153 -0
- data/doc/plugins/column.md +90 -0
- data/doc/plugins/derivation_endpoint.md +54 -62
- data/doc/plugins/derivatives.md +752 -0
- data/doc/plugins/entity.md +204 -0
- data/doc/plugins/infer_extension.md +8 -8
- data/doc/plugins/instrumentation.md +33 -13
- data/doc/plugins/keep_files.md +5 -15
- data/doc/plugins/model.md +157 -0
- data/doc/plugins/presign_endpoint.md +2 -1
- data/doc/plugins/refresh_metadata.md +44 -7
- data/doc/plugins/sequel.md +190 -33
- data/doc/plugins/{default_url_options.md → url_options.md} +5 -5
- data/doc/processing.md +1 -1
- data/doc/release_notes/1.1.0.md +2 -2
- data/doc/release_notes/2.15.0.md +1 -1
- data/doc/storage/s3.md +2 -2
- data/doc/testing.md +1 -1
- data/lib/shrine.rb +72 -138
- data/lib/shrine/attacher.rb +272 -176
- data/lib/shrine/attachment.rb +2 -42
- data/lib/shrine/plugins/activerecord.rb +103 -26
- data/lib/shrine/plugins/add_metadata.rb +9 -10
- data/lib/shrine/plugins/atomic_helpers.rb +111 -0
- data/lib/shrine/plugins/attacher_options.rb +55 -0
- data/lib/shrine/plugins/backgrounding.rb +147 -115
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -9
- data/lib/shrine/plugins/column.rb +104 -0
- data/lib/shrine/plugins/data_uri.rb +35 -38
- data/lib/shrine/plugins/default_storage.rb +18 -12
- data/lib/shrine/plugins/default_url.rb +11 -21
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +9 -13
- data/lib/shrine/plugins/derivation_endpoint.rb +75 -114
- data/lib/shrine/plugins/derivatives.rb +576 -0
- data/lib/shrine/plugins/determine_mime_type.rb +3 -15
- data/lib/shrine/plugins/download_endpoint.rb +83 -131
- data/lib/shrine/plugins/dynamic_storage.rb +4 -8
- data/lib/shrine/plugins/entity.rb +128 -0
- data/lib/shrine/plugins/form_assign.rb +107 -0
- data/lib/shrine/plugins/included.rb +4 -3
- data/lib/shrine/plugins/infer_extension.rb +10 -17
- data/lib/shrine/plugins/instrumentation.rb +45 -25
- data/lib/shrine/plugins/keep_files.rb +2 -12
- data/lib/shrine/plugins/metadata_attributes.rb +15 -14
- data/lib/shrine/plugins/model.rb +137 -0
- data/lib/shrine/plugins/module_include.rb +2 -0
- data/lib/shrine/plugins/presign_endpoint.rb +1 -15
- data/lib/shrine/plugins/pretty_location.rb +5 -5
- data/lib/shrine/plugins/processing.rb +21 -6
- data/lib/shrine/plugins/rack_file.rb +1 -39
- data/lib/shrine/plugins/rack_response.rb +14 -7
- data/lib/shrine/plugins/recache.rb +5 -2
- data/lib/shrine/plugins/refresh_metadata.rb +12 -8
- data/lib/shrine/plugins/remote_url.rb +44 -53
- data/lib/shrine/plugins/remove_attachment.rb +7 -2
- data/lib/shrine/plugins/remove_invalid.rb +8 -4
- data/lib/shrine/plugins/restore_cached_data.rb +12 -4
- data/lib/shrine/plugins/sequel.rb +115 -27
- data/lib/shrine/plugins/signature.rb +2 -7
- data/lib/shrine/plugins/store_dimensions.rb +13 -27
- data/lib/shrine/plugins/upload_endpoint.rb +14 -15
- data/lib/shrine/plugins/upload_options.rb +9 -8
- data/lib/shrine/plugins/url_options.rb +33 -0
- data/lib/shrine/plugins/validation.rb +87 -0
- data/lib/shrine/plugins/validation_helpers.rb +33 -54
- data/lib/shrine/plugins/versions.rb +106 -84
- data/lib/shrine/storage/file_system.rb +32 -57
- data/lib/shrine/storage/linter.rb +9 -1
- data/lib/shrine/storage/memory.rb +42 -0
- data/lib/shrine/storage/s3.rb +38 -146
- data/lib/shrine/uploaded_file.rb +22 -29
- data/lib/shrine/version.rb +4 -4
- data/shrine.gemspec +2 -3
- metadata +27 -54
- 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/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,576 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shrine
|
4
|
+
module Plugins
|
5
|
+
# Documentation lives in [doc/plugins/derivatives.md] on GitHub.
|
6
|
+
#
|
7
|
+
# [doc/plugins/derivatives.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivatives.md
|
8
|
+
module Derivatives
|
9
|
+
LOG_SUBSCRIBER = -> (event) do
|
10
|
+
Shrine.logger.info "Derivatives (#{event.duration}ms) – #{{
|
11
|
+
processor: event[:processor],
|
12
|
+
processor_options: event[:processor_options],
|
13
|
+
uploader: event[:uploader],
|
14
|
+
}.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.load_dependencies(uploader, versions_compatibility: false, **)
|
18
|
+
uploader.plugin :default_url
|
19
|
+
|
20
|
+
AttacherMethods.prepend(VersionsCompatibility) if versions_compatibility
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
24
|
+
uploader.opts[:derivatives] ||= { processors: {}, storage: proc { store_key } }
|
25
|
+
uploader.opts[:derivatives].merge!(opts)
|
26
|
+
|
27
|
+
# instrumentation plugin integration
|
28
|
+
uploader.subscribe(:derivatives, &log_subscriber) if uploader.respond_to?(:subscribe)
|
29
|
+
end
|
30
|
+
|
31
|
+
module AttachmentMethods
|
32
|
+
def initialize(name, **options)
|
33
|
+
super
|
34
|
+
|
35
|
+
define_method(:"#{name}_derivatives") do |*args|
|
36
|
+
send(:"#{name}_attacher").get_derivatives(*args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module AttacherClassMethods
|
42
|
+
# Registers a derivatives processor on the attacher class.
|
43
|
+
#
|
44
|
+
# Attacher.derivatives_processor :thumbnails do |original|
|
45
|
+
# # ...
|
46
|
+
# end
|
47
|
+
def derivatives_processor(name, &block)
|
48
|
+
shrine_class.opts[:derivatives][:processors][name.to_sym] = block
|
49
|
+
end
|
50
|
+
|
51
|
+
# Specifies default storage to which derivatives will be uploaded.
|
52
|
+
#
|
53
|
+
# Attacher.derivatives_storage :other_store
|
54
|
+
# # or
|
55
|
+
# Attacher.derivatives_storage do |name|
|
56
|
+
# if name == :thumbnail
|
57
|
+
# :thumbnail_store
|
58
|
+
# else
|
59
|
+
# :store
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
def derivatives_storage(storage_key = nil, &block)
|
63
|
+
fail ArgumentError, "storage key or block needs to be provided" unless storage_key || block
|
64
|
+
|
65
|
+
shrine_class.opts[:derivatives][:storage] = storage_key || block
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module AttacherMethods
|
70
|
+
attr_reader :derivatives
|
71
|
+
|
72
|
+
# Adds the ability to accept derivatives.
|
73
|
+
def initialize(derivatives: {}, **options)
|
74
|
+
super(**options)
|
75
|
+
|
76
|
+
@derivatives = derivatives
|
77
|
+
@derivatives_mutex = Mutex.new
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convenience method for accessing derivatives.
|
81
|
+
#
|
82
|
+
# photo.image_derivatives[:thumb] #=> #<Shrine::UploadedFile>
|
83
|
+
# # can be shortened to
|
84
|
+
# photo.image(:thumb) #=> #<Shrine::UploadedFile>
|
85
|
+
def get(*path)
|
86
|
+
return super if path.empty?
|
87
|
+
|
88
|
+
get_derivatives(*path)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Convenience method for accessing derivatives.
|
92
|
+
#
|
93
|
+
# photo.image_derivatives.dig(:thumbnails, :large)
|
94
|
+
# # can be shortened to
|
95
|
+
# photo.image_derivatives(:thumbnails, :large)
|
96
|
+
def get_derivatives(*path)
|
97
|
+
return derivatives if path.empty?
|
98
|
+
|
99
|
+
path = derivative_path(path)
|
100
|
+
|
101
|
+
derivatives.dig(*path)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Allows generating a URL to the derivative by passing the derivative
|
105
|
+
# name.
|
106
|
+
#
|
107
|
+
# attacher.add_derivatives(thumb: thumb)
|
108
|
+
# attacher.url(:thumb) #=> "https://example.org/thumb.jpg"
|
109
|
+
def url(*path, **options)
|
110
|
+
return super if path.empty?
|
111
|
+
|
112
|
+
path = derivative_path(path)
|
113
|
+
|
114
|
+
url = derivatives.dig(*path)&.url(**options)
|
115
|
+
url ||= default_url(**options, derivative: path)
|
116
|
+
url
|
117
|
+
end
|
118
|
+
|
119
|
+
# In addition to promoting the main file, also promotes any cached
|
120
|
+
# derivatives. This is useful when these derivatives are being created
|
121
|
+
# as part of a direct upload.
|
122
|
+
#
|
123
|
+
# attacher.assign(io)
|
124
|
+
# attacher.add_derivative(:thumb, file, storage: :cache)
|
125
|
+
# attacher.promote
|
126
|
+
# attacher.stored?(attacher.derivatives[:thumb]) #=> true
|
127
|
+
def promote(background: false, **options)
|
128
|
+
super
|
129
|
+
promote_derivatives unless background
|
130
|
+
end
|
131
|
+
|
132
|
+
# Uploads any cached derivatives to permanent storage.
|
133
|
+
def promote_derivatives(**options)
|
134
|
+
stored_derivatives = map_derivative(derivatives) do |path, derivative|
|
135
|
+
if cached?(derivative)
|
136
|
+
upload_derivative(path, derivative, **options)
|
137
|
+
else
|
138
|
+
derivative
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
set_derivatives(stored_derivatives) unless derivatives == stored_derivatives
|
143
|
+
end
|
144
|
+
|
145
|
+
# In addition to deleting the main file it also deletes any derivatives.
|
146
|
+
#
|
147
|
+
# attacher.add_derivatives(thumb: thumb)
|
148
|
+
# attacher.derivatives[:thumb].exists? #=> true
|
149
|
+
# attacher.destroy
|
150
|
+
# attacher.derivatives[:thumb].exists? #=> false
|
151
|
+
def destroy(background: false, **options)
|
152
|
+
super
|
153
|
+
delete_derivatives unless background
|
154
|
+
end
|
155
|
+
|
156
|
+
# Calls processor and adds returned derivatives.
|
157
|
+
#
|
158
|
+
# Attacher.derivatives_processor :my_processor do |original|
|
159
|
+
# # ...
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# attacher.create_derivatives(:my_processor)
|
163
|
+
def create_derivatives(processor_name, **options)
|
164
|
+
files = process_derivatives(processor_name)
|
165
|
+
add_derivatives(files, **options)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Uploads given hash of files and adds uploaded files to the
|
169
|
+
# derivatives hash.
|
170
|
+
#
|
171
|
+
# attacher.derivatives #=>
|
172
|
+
# # {
|
173
|
+
# # thumb: #<Shrine::UploadedFile>,
|
174
|
+
# # }
|
175
|
+
# attacher.add_derivatives(cropped: cropped)
|
176
|
+
# attacher.derivatives #=>
|
177
|
+
# # {
|
178
|
+
# # thumb: #<Shrine::UploadedFile>,
|
179
|
+
# # cropped: #<Shrine::UploadedFile>,
|
180
|
+
# # }
|
181
|
+
def add_derivatives(files, **options)
|
182
|
+
new_derivatives = upload_derivatives(files, **options)
|
183
|
+
merge_derivatives(new_derivatives)
|
184
|
+
new_derivatives
|
185
|
+
end
|
186
|
+
|
187
|
+
# Uploads a given file and adds it to the derivatives hash.
|
188
|
+
#
|
189
|
+
# attacher.derivatives #=>
|
190
|
+
# # {
|
191
|
+
# # thumb: #<Shrine::UploadedFile>,
|
192
|
+
# # }
|
193
|
+
# attacher.add_derivative(:cropped, cropped)
|
194
|
+
# attacher.derivatives #=>
|
195
|
+
# # {
|
196
|
+
# # thumb: #<Shrine::UploadedFile>,
|
197
|
+
# # cropped: #<Shrine::UploadedFile>,
|
198
|
+
# # }
|
199
|
+
def add_derivative(name, file, **options)
|
200
|
+
add_derivatives({ name => file }, **options)
|
201
|
+
derivatives[name]
|
202
|
+
end
|
203
|
+
|
204
|
+
# Uploads given hash of files.
|
205
|
+
#
|
206
|
+
# hash = attacher.upload_derivatives(thumb: thumb)
|
207
|
+
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
208
|
+
def upload_derivatives(files, **options)
|
209
|
+
map_derivative(files) do |path, file|
|
210
|
+
path = derivative_path(path)
|
211
|
+
|
212
|
+
upload_derivative(path, file, **options)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Uploads the given file and deletes it afterwards.
|
217
|
+
#
|
218
|
+
# hash = attacher.upload_derivative(:thumb, thumb)
|
219
|
+
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
220
|
+
def upload_derivative(path, file, storage: nil, **options)
|
221
|
+
storage ||= derivative_storage(path)
|
222
|
+
|
223
|
+
upload(file, storage, derivative: path, delete: true, **options)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Downloads the attached file and calls the specified processor.
|
227
|
+
#
|
228
|
+
# Attacher.derivatives_processor :thumbnails do |original|
|
229
|
+
# processor = ImageProcessing::MiniMagick.source(original)
|
230
|
+
#
|
231
|
+
# {
|
232
|
+
# small: processor.resize_to_limit!(300, 300),
|
233
|
+
# medium: processor.resize_to_limit!(500, 500),
|
234
|
+
# large: processor.resize_to_limit!(800, 800),
|
235
|
+
# }
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# attacher.process_derivatives(:thumbnails)
|
239
|
+
# #=> { small: #<File:...>, medium: #<File:...>, large: #<File:...> }
|
240
|
+
def process_derivatives(processor_name, source = nil, **options)
|
241
|
+
processor = derivatives_processor(processor_name)
|
242
|
+
fetch_source = source ? source.method(:tap) : file!.method(:download)
|
243
|
+
result = nil
|
244
|
+
|
245
|
+
fetch_source.call do |source_file|
|
246
|
+
instrument_derivatives(processor_name, options) do
|
247
|
+
result = instance_exec(source_file, **options, &processor)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
unless result.is_a?(Hash)
|
252
|
+
fail Error, "expected derivatives processor #{processor_name.inspect} to return a Hash, got #{result.inspect}"
|
253
|
+
end
|
254
|
+
|
255
|
+
result
|
256
|
+
end
|
257
|
+
|
258
|
+
# Deep merges given uploaded derivatives with current derivatives.
|
259
|
+
#
|
260
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
|
261
|
+
# attacher.merge_derivatives(two: uploaded_file)
|
262
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
263
|
+
def merge_derivatives(new_derivatives)
|
264
|
+
@derivatives_mutex.synchronize do
|
265
|
+
merged_derivatives = deep_merge_derivatives(derivatives, new_derivatives)
|
266
|
+
set_derivatives(merged_derivatives)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Removes derivatives with specified name from the derivatives hash.
|
271
|
+
#
|
272
|
+
# attacher.derivatives
|
273
|
+
# #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }
|
274
|
+
#
|
275
|
+
# attacher.remove_derivatives(:two, :three)
|
276
|
+
# #=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)
|
277
|
+
#
|
278
|
+
# attacher.derivatives
|
279
|
+
# #=> { one: #<Shrine::UploadedFile> }
|
280
|
+
#
|
281
|
+
# Nested derivatives are also supported:
|
282
|
+
#
|
283
|
+
# attacher.derivatives
|
284
|
+
# #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> } }
|
285
|
+
#
|
286
|
+
# attacher.remove_derivatives([:nested, :two], [:nested, :three])
|
287
|
+
# #=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)
|
288
|
+
#
|
289
|
+
# attacher.derivatives
|
290
|
+
# #=> { nested: { one: #<Shrine::UploadedFile> } }
|
291
|
+
#
|
292
|
+
# The :delete option can be passed for deleting removed derivatives:
|
293
|
+
#
|
294
|
+
# attacher.derivatives
|
295
|
+
# #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }
|
296
|
+
#
|
297
|
+
# two, three = attacher.remove_derivatives(:two, :three, delete: true)
|
298
|
+
#
|
299
|
+
# two.exists? #=> false
|
300
|
+
# three.exists? #=> false
|
301
|
+
def remove_derivatives(*paths, delete: false)
|
302
|
+
removed_derivatives = paths.map do |path|
|
303
|
+
path = Array(path)
|
304
|
+
|
305
|
+
if path.one?
|
306
|
+
derivatives.delete(path.first)
|
307
|
+
else
|
308
|
+
derivatives.dig(*path[0..-2]).delete(path[-1])
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
set_derivatives derivatives
|
313
|
+
|
314
|
+
delete_derivatives(removed_derivatives) if delete
|
315
|
+
|
316
|
+
removed_derivatives
|
317
|
+
end
|
318
|
+
|
319
|
+
# Removes derivative with specified name from the derivatives hash.
|
320
|
+
#
|
321
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
322
|
+
# attacher.remove_derivative(:one) #=> #<Shrine::UploadedFile> (removed derivative)
|
323
|
+
# attacher.derivatives #=> { two: #<Shrine::UploadedFile> }
|
324
|
+
#
|
325
|
+
# Nested derivatives are also supported:
|
326
|
+
#
|
327
|
+
# attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
|
328
|
+
# attacher.remove_derivative([:nested, :one]) #=> #<Shrine::UploadedFile> (removed derivative)
|
329
|
+
# attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }
|
330
|
+
#
|
331
|
+
# The :delete option can be passed for deleting removed derivative:
|
332
|
+
#
|
333
|
+
# attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
|
334
|
+
# derivative = attacher.remove_derivatives(:two, delete: true)
|
335
|
+
# derivative.exists? #=> false
|
336
|
+
def remove_derivative(path, **options)
|
337
|
+
remove_derivatives(path, **options).first
|
338
|
+
end
|
339
|
+
|
340
|
+
# Deletes given hash of uploaded files.
|
341
|
+
#
|
342
|
+
# attacher.delete_derivatives(thumb: uploaded_file)
|
343
|
+
# uploaded_file.exists? #=> false
|
344
|
+
def delete_derivatives(derivatives = self.derivatives)
|
345
|
+
map_derivative(derivatives) { |_, derivative| derivative.delete }
|
346
|
+
end
|
347
|
+
|
348
|
+
# Sets the given hash of uploaded files as derivatives.
|
349
|
+
#
|
350
|
+
# attacher.set_derivatives(thumb: uploaded_file)
|
351
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
352
|
+
def set_derivatives(derivatives)
|
353
|
+
self.derivatives = derivatives
|
354
|
+
set file # trigger model writing
|
355
|
+
derivatives
|
356
|
+
end
|
357
|
+
|
358
|
+
# Adds derivative data into the hash.
|
359
|
+
#
|
360
|
+
# attacher.attach(io)
|
361
|
+
# attacher.add_derivatives(thumb: thumb)
|
362
|
+
# attacher.data
|
363
|
+
# #=>
|
364
|
+
# # {
|
365
|
+
# # "id" => "...",
|
366
|
+
# # "storage" => "store",
|
367
|
+
# # "metadata" => { ... },
|
368
|
+
# # "derivatives" => {
|
369
|
+
# # "thumb" => {
|
370
|
+
# # "id" => "...",
|
371
|
+
# # "storage" => "store",
|
372
|
+
# # "metadata" => { ... },
|
373
|
+
# # }
|
374
|
+
# # }
|
375
|
+
# # }
|
376
|
+
def data
|
377
|
+
result = super
|
378
|
+
|
379
|
+
if derivatives.any?
|
380
|
+
result ||= {}
|
381
|
+
result["derivatives"] = map_derivative(derivatives, transform_keys: :to_s) do |_, derivative|
|
382
|
+
derivative.data
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
result
|
387
|
+
end
|
388
|
+
|
389
|
+
# Loads derivatives from data generated by `Attacher#data`.
|
390
|
+
#
|
391
|
+
# attacher.load_data({
|
392
|
+
# "id" => "...",
|
393
|
+
# "storage" => "store",
|
394
|
+
# "metadata" => { ... },
|
395
|
+
# "derivatives" => {
|
396
|
+
# "thumb" => {
|
397
|
+
# "id" => "...",
|
398
|
+
# "storage" => "store",
|
399
|
+
# "metadata" => { ... },
|
400
|
+
# }
|
401
|
+
# }
|
402
|
+
# })
|
403
|
+
# attacher.file #=> #<Shrine::UploadedFile>
|
404
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
405
|
+
def load_data(data)
|
406
|
+
data ||= {}
|
407
|
+
data = data.dup
|
408
|
+
|
409
|
+
derivatives_data = data.delete("derivatives") || data.delete(:derivatives) || {}
|
410
|
+
@derivatives = shrine_class.derivatives(derivatives_data)
|
411
|
+
|
412
|
+
data = nil if data.empty?
|
413
|
+
|
414
|
+
super(data)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Clears derivatives when attachment changes.
|
418
|
+
#
|
419
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
420
|
+
# attacher.change(file)
|
421
|
+
# attacher.derivatives #=> {}
|
422
|
+
def change(*args)
|
423
|
+
result = super
|
424
|
+
set_derivatives({})
|
425
|
+
result
|
426
|
+
end
|
427
|
+
|
428
|
+
# Sets a hash of derivatives.
|
429
|
+
#
|
430
|
+
# attacher.derivatives = { thumb: Shrine.uploaded_file(...) }
|
431
|
+
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile ...> }
|
432
|
+
def derivatives=(derivatives)
|
433
|
+
unless derivatives.is_a?(Hash)
|
434
|
+
fail ArgumentError, "expected derivatives to be a Hash, got #{derivatives.inspect}"
|
435
|
+
end
|
436
|
+
|
437
|
+
@derivatives = derivatives
|
438
|
+
end
|
439
|
+
|
440
|
+
# Iterates through nested derivatives and maps results.
|
441
|
+
#
|
442
|
+
# attacher.map_derivative(derivatives) { |path, file| ... }
|
443
|
+
def map_derivative(*args, &block)
|
444
|
+
shrine_class.map_derivative(*args, &block)
|
445
|
+
end
|
446
|
+
|
447
|
+
private
|
448
|
+
|
449
|
+
# Sends a `derivatives.shrine` event for instrumentation plugin.
|
450
|
+
def instrument_derivatives(processor_name, processor_options, &block)
|
451
|
+
return yield unless shrine_class.respond_to?(:instrument)
|
452
|
+
|
453
|
+
shrine_class.instrument(
|
454
|
+
:derivatives,
|
455
|
+
processor: processor_name,
|
456
|
+
processor_options: processor_options,
|
457
|
+
&block
|
458
|
+
)
|
459
|
+
end
|
460
|
+
|
461
|
+
# Retrieves derivatives processor with specified name.
|
462
|
+
def derivatives_processor(name)
|
463
|
+
shrine_class.opts[:derivatives][:processors][name.to_sym] or
|
464
|
+
fail Error, "derivatives processor #{name.inspect} not registered"
|
465
|
+
end
|
466
|
+
|
467
|
+
# Returns symbolized array or single key.
|
468
|
+
def derivative_path(path)
|
469
|
+
path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
|
470
|
+
path = path.first if path.one?
|
471
|
+
path
|
472
|
+
end
|
473
|
+
|
474
|
+
# Storage to which derivatives will be uploaded to by default.
|
475
|
+
def derivative_storage(path)
|
476
|
+
storage = shrine_class.opts[:derivatives][:storage]
|
477
|
+
storage = instance_exec(path, &storage) if storage.respond_to?(:call)
|
478
|
+
storage
|
479
|
+
end
|
480
|
+
|
481
|
+
# Deep merge nested hashes/arrays.
|
482
|
+
def deep_merge_derivatives(o1, o2)
|
483
|
+
if o1.is_a?(Hash) && o2.is_a?(Hash)
|
484
|
+
o1.merge(o2) { |_, v1, v2| deep_merge_derivatives(v1, v2) }
|
485
|
+
elsif o1.is_a?(Array) && o2.is_a?(Array)
|
486
|
+
o1 + o2
|
487
|
+
else
|
488
|
+
o2
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
module ClassMethods
|
494
|
+
# Converts data into a Hash of derivatives.
|
495
|
+
#
|
496
|
+
# Shrine.derivatives('{"thumb":{"id":"foo","storage":"store","metadata":{}}}')
|
497
|
+
# #=> { thumb: #<Shrine::UploadedFile @id="foo" @storage_key="store" @metadata={}> }
|
498
|
+
#
|
499
|
+
# Shrine.derivatives({ "thumb" => { "id" => "foo", "storage" => "store", "metadata" => {} } })
|
500
|
+
# #=> { thumb: #<Shrine::UploadedFile @id="foo" @storage_key="store" @metadata={}> }
|
501
|
+
#
|
502
|
+
# Shrine.derivatives({ thumb: { id: "foo", storage: "store", metadata: {} } })
|
503
|
+
# #=> { thumb: #<Shrine::UploadedFile @id="foo" @storage_key="store" @metadata={}> }
|
504
|
+
def derivatives(object)
|
505
|
+
if object.is_a?(String)
|
506
|
+
derivatives JSON.parse(object)
|
507
|
+
elsif object.is_a?(Hash) || object.is_a?(Array)
|
508
|
+
map_derivative(
|
509
|
+
object,
|
510
|
+
transform_keys: :to_sym,
|
511
|
+
leaf: -> (value) { value.is_a?(Hash) && (value["id"] || value[:id]).is_a?(String) },
|
512
|
+
) { |_, value| uploaded_file(value) }
|
513
|
+
else
|
514
|
+
fail ArgumentError, "cannot convert #{object.inspect} to derivatives"
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
# Iterates over a nested collection, yielding on each part of the path.
|
519
|
+
# If the block returns a truthy value, that branch is terminated
|
520
|
+
def map_derivative(object, path = [], transform_keys: :to_sym, leaf: nil, &block)
|
521
|
+
return enum_for(__method__, object) unless block_given?
|
522
|
+
|
523
|
+
if leaf && leaf.call(object)
|
524
|
+
yield path, object
|
525
|
+
elsif object.is_a?(Hash)
|
526
|
+
object.inject({}) do |hash, (key, value)|
|
527
|
+
key = key.send(transform_keys)
|
528
|
+
|
529
|
+
hash.merge! key => map_derivative(
|
530
|
+
value, [*path, key],
|
531
|
+
transform_keys: transform_keys, leaf: leaf,
|
532
|
+
&block
|
533
|
+
)
|
534
|
+
end
|
535
|
+
elsif object.is_a?(Array)
|
536
|
+
object.map.with_index do |value, idx|
|
537
|
+
map_derivative(
|
538
|
+
value, [*path, idx],
|
539
|
+
transform_keys: transform_keys, leaf: leaf,
|
540
|
+
&block
|
541
|
+
)
|
542
|
+
end
|
543
|
+
else
|
544
|
+
yield path, object
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
module FileMethods
|
550
|
+
def [](*keys)
|
551
|
+
if keys.any? { |key| key.is_a?(Symbol) }
|
552
|
+
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)`?"
|
553
|
+
else
|
554
|
+
super
|
555
|
+
end
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
# Adds compatibility with how the versions plugin stores processed files.
|
560
|
+
module VersionsCompatibility
|
561
|
+
def load_data(data)
|
562
|
+
return super if data.nil?
|
563
|
+
return super if data["derivatives"] || data[:derivatives]
|
564
|
+
return super if (data["id"] || data[:id]).is_a?(String)
|
565
|
+
|
566
|
+
data = data.dup
|
567
|
+
original = data.delete("original") || data.delete(:original) || {}
|
568
|
+
|
569
|
+
super original.merge("derivatives" => data)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
register_plugin(:derivatives, Derivatives)
|
575
|
+
end
|
576
|
+
end
|