shrine 3.0.1 → 3.3.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 +82 -0
- data/LICENSE.txt +1 -1
- data/README.md +15 -5
- data/doc/advantages.md +33 -16
- data/doc/attacher.md +2 -2
- data/doc/carrierwave.md +78 -34
- data/doc/changing_derivatives.md +39 -39
- data/doc/design.md +134 -85
- data/doc/direct_s3.md +1 -0
- data/doc/external/articles.md +57 -45
- data/doc/external/extensions.md +41 -35
- data/doc/external/misc.md +23 -8
- data/doc/getting_started.md +177 -112
- data/doc/metadata.md +79 -43
- data/doc/multiple_files.md +6 -4
- data/doc/paperclip.md +119 -42
- data/doc/plugins/activerecord.md +1 -1
- data/doc/plugins/add_metadata.md +112 -35
- data/doc/plugins/atomic_helpers.md +41 -3
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/column.md +36 -7
- data/doc/plugins/data_uri.md +2 -2
- data/doc/plugins/default_url.md +6 -3
- data/doc/plugins/derivation_endpoint.md +26 -28
- data/doc/plugins/derivatives.md +238 -171
- data/doc/plugins/determine_mime_type.md +2 -2
- data/doc/plugins/download_endpoint.md +5 -5
- data/doc/plugins/dynamic_storage.md +1 -1
- data/doc/plugins/form_assign.md +5 -5
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/infer_extension.md +11 -2
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/metadata_attributes.md +22 -10
- data/doc/plugins/mirroring.md +1 -1
- data/doc/plugins/persistence.md +11 -1
- data/doc/plugins/refresh_metadata.md +5 -4
- data/doc/plugins/remote_url.md +8 -3
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/signature.md +11 -2
- data/doc/plugins/store_dimensions.md +12 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +7 -11
- data/doc/plugins/upload_options.md +1 -1
- data/doc/plugins/url_options.md +4 -4
- data/doc/plugins/validation.md +14 -4
- data/doc/plugins/validation_helpers.md +3 -3
- data/doc/plugins/versions.md +7 -7
- data/doc/processing.md +290 -127
- data/doc/refile.md +39 -18
- data/doc/release_notes/2.19.0.md +1 -1
- data/doc/release_notes/2.8.0.md +1 -1
- data/doc/release_notes/3.0.0.md +1 -1
- data/doc/release_notes/3.0.1.md +4 -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/securing_uploads.md +3 -3
- data/doc/storage/file_system.md +1 -1
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +105 -82
- data/doc/testing.md +2 -2
- data/doc/upgrading_to_3.md +97 -49
- data/doc/validation.md +3 -2
- data/lib/shrine.rb +8 -8
- data/lib/shrine/attacher.rb +24 -14
- data/lib/shrine/attachment.rb +5 -5
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/plugins/activerecord.rb +1 -1
- data/lib/shrine/plugins/add_metadata.rb +18 -7
- data/lib/shrine/plugins/backgrounding.rb +2 -2
- data/lib/shrine/plugins/default_storage.rb +6 -6
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +12 -7
- data/lib/shrine/plugins/derivatives.rb +61 -29
- data/lib/shrine/plugins/determine_mime_type.rb +3 -3
- data/lib/shrine/plugins/entity.rb +6 -6
- data/lib/shrine/plugins/mirroring.rb +8 -8
- data/lib/shrine/plugins/model.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +16 -4
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/remove_attachment.rb +5 -0
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/sequel.rb +1 -1
- data/lib/shrine/plugins/signature.rb +7 -6
- data/lib/shrine/plugins/store_dimensions.rb +22 -11
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +10 -5
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/url_options.rb +2 -2
- data/lib/shrine/plugins/validation.rb +9 -7
- data/lib/shrine/storage/linter.rb +4 -4
- data/lib/shrine/storage/memory.rb +5 -3
- data/lib/shrine/storage/s3.rb +117 -38
- data/lib/shrine/uploaded_file.rb +0 -1
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +7 -8
- metadata +25 -31
data/doc/validation.md
CHANGED
@@ -88,10 +88,11 @@ when defining more validations:
|
|
88
88
|
class ApplicationUploader < Shrine
|
89
89
|
Attacher.validate { validate_max_size 5*1024*1024 }
|
90
90
|
end
|
91
|
-
|
91
|
+
```
|
92
|
+
```rb
|
92
93
|
class ImageUploader < ApplicationUploader
|
93
94
|
Attacher.validate do
|
94
|
-
super() # empty
|
95
|
+
super() # empty parentheses are required
|
95
96
|
validate_mime_type %w[image/jpeg image/png image/webp]
|
96
97
|
end
|
97
98
|
end
|
data/lib/shrine.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "shrine/version"
|
4
3
|
require "shrine/uploaded_file"
|
5
4
|
require "shrine/attacher"
|
6
5
|
require "shrine/attachment"
|
7
6
|
require "shrine/plugins"
|
7
|
+
require "shrine/version"
|
8
8
|
|
9
9
|
require "securerandom"
|
10
10
|
require "json"
|
11
11
|
require "tempfile"
|
12
12
|
require "logger"
|
13
13
|
|
14
|
-
# Core class that
|
15
|
-
# Base implementation is defined in InstanceMethods and ClassMethods.
|
14
|
+
# Core class that handles uploading files to specified storage.
|
16
15
|
class Shrine
|
17
16
|
# A generic exception used by Shrine.
|
18
|
-
class Error < StandardError
|
17
|
+
class Error < StandardError
|
18
|
+
end
|
19
19
|
|
20
20
|
# Raised when a file is not a valid IO.
|
21
21
|
class InvalidFile < Error
|
@@ -68,9 +68,9 @@ class Shrine
|
|
68
68
|
#
|
69
69
|
# Shrine.plugin MyPlugin
|
70
70
|
# Shrine.plugin :my_plugin
|
71
|
-
def plugin(plugin, *args, &block)
|
71
|
+
def plugin(plugin, *args, **kwargs, &block)
|
72
72
|
plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
73
|
-
|
73
|
+
Plugins.load_dependencies(plugin, self, *args, **kwargs, &block)
|
74
74
|
self.include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
|
75
75
|
self.extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
|
76
76
|
self::UploadedFile.include(plugin::FileMethods) if defined?(plugin::FileMethods)
|
@@ -79,7 +79,7 @@ class Shrine
|
|
79
79
|
self::Attachment.extend(plugin::AttachmentClassMethods) if defined?(plugin::AttachmentClassMethods)
|
80
80
|
self::Attacher.include(plugin::AttacherMethods) if defined?(plugin::AttacherMethods)
|
81
81
|
self::Attacher.extend(plugin::AttacherClassMethods) if defined?(plugin::AttacherClassMethods)
|
82
|
-
|
82
|
+
Plugins.configure(plugin, self, *args, **kwargs, &block)
|
83
83
|
plugin
|
84
84
|
end
|
85
85
|
|
@@ -297,7 +297,7 @@ class Shrine
|
|
297
297
|
# Retrieves the location for the given IO and context. First it looks
|
298
298
|
# for the `:location` option, otherwise it calls #generate_location.
|
299
299
|
def get_location(io, location: nil, **options)
|
300
|
-
location ||= generate_location(io, options)
|
300
|
+
location ||= generate_location(io, **options)
|
301
301
|
location or fail Error, "location generated for #{io.inspect} was nil"
|
302
302
|
end
|
303
303
|
|
data/lib/shrine/attacher.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Shrine
|
4
|
-
# Core class
|
5
|
-
#
|
4
|
+
# Core class that handles attaching files. It uses Shrine and
|
5
|
+
# Shrine::UploadedFile objects internally.
|
6
6
|
class Attacher
|
7
7
|
@shrine_class = ::Shrine
|
8
8
|
|
@@ -39,16 +39,17 @@ class Shrine
|
|
39
39
|
|
40
40
|
# Initializes the attached file, temporary and permanent storage.
|
41
41
|
def initialize(file: nil, cache: :cache, store: :store)
|
42
|
-
@file
|
43
|
-
@cache
|
44
|
-
@store
|
45
|
-
@context
|
42
|
+
@file = file
|
43
|
+
@cache = cache
|
44
|
+
@store = store
|
45
|
+
@context = {}
|
46
|
+
@previous = nil
|
46
47
|
end
|
47
48
|
|
48
49
|
# Returns the temporary storage identifier.
|
49
|
-
def cache_key; @cache; end
|
50
|
+
def cache_key; @cache.to_sym; end
|
50
51
|
# Returns the permanent storage identifier.
|
51
|
-
def store_key; @store; end
|
52
|
+
def store_key; @store.to_sym; end
|
52
53
|
|
53
54
|
# Returns the uploader that is used for the temporary storage.
|
54
55
|
def cache; shrine_class.new(cache_key); end
|
@@ -69,6 +70,10 @@ class Shrine
|
|
69
70
|
def assign(value, **options)
|
70
71
|
return if value == "" # skip empty hidden field
|
71
72
|
|
73
|
+
if value.is_a?(Hash) || value.is_a?(String)
|
74
|
+
return if uploaded_file(value) == file # skip assignment for current file
|
75
|
+
end
|
76
|
+
|
72
77
|
attach_cached(value, **options)
|
73
78
|
end
|
74
79
|
|
@@ -89,7 +94,7 @@ class Shrine
|
|
89
94
|
# attacher.attach_cached({ "id" => "...", "storage" => "cache", "metadata" => {} })
|
90
95
|
def attach_cached(value, **options)
|
91
96
|
if value.is_a?(String) || value.is_a?(Hash)
|
92
|
-
change(cached(value, **options)
|
97
|
+
change(cached(value, **options))
|
93
98
|
else
|
94
99
|
attach(value, storage: cache_key, action: :cache, **options)
|
95
100
|
end
|
@@ -111,7 +116,7 @@ class Shrine
|
|
111
116
|
def attach(io, storage: store_key, **options)
|
112
117
|
file = upload(io, storage, **options) if io
|
113
118
|
|
114
|
-
change(file
|
119
|
+
change(file)
|
115
120
|
end
|
116
121
|
|
117
122
|
# Deletes any previous file and promotes newly attached cached file.
|
@@ -138,7 +143,7 @@ class Shrine
|
|
138
143
|
def finalize
|
139
144
|
destroy_previous
|
140
145
|
promote_cached
|
141
|
-
|
146
|
+
@previous = nil
|
142
147
|
end
|
143
148
|
|
144
149
|
# Plugins can override this if they want something to be done in a
|
@@ -211,8 +216,8 @@ class Shrine
|
|
211
216
|
# attacher.change(uploaded_file)
|
212
217
|
# attacher.file #=> #<Shrine::UploadedFile>
|
213
218
|
# attacher.changed? #=> true
|
214
|
-
def change(file
|
215
|
-
@previous = dup
|
219
|
+
def change(file)
|
220
|
+
@previous = dup if change?(file)
|
216
221
|
set(file)
|
217
222
|
end
|
218
223
|
|
@@ -254,7 +259,7 @@ class Shrine
|
|
254
259
|
# attacher.attach(file)
|
255
260
|
# attacher.changed? #=> true
|
256
261
|
def changed?
|
257
|
-
|
262
|
+
!!@previous
|
258
263
|
end
|
259
264
|
|
260
265
|
# Returns whether a file is attached.
|
@@ -368,6 +373,11 @@ class Shrine
|
|
368
373
|
attached? && !cached?
|
369
374
|
end
|
370
375
|
|
376
|
+
# Whether assigning the given file is considered a change.
|
377
|
+
def change?(file)
|
378
|
+
@file != file
|
379
|
+
end
|
380
|
+
|
371
381
|
# Returns whether the file is uploaded to specified storage.
|
372
382
|
def uploaded?(file, storage_key)
|
373
383
|
file&.storage_key == storage_key
|
data/lib/shrine/attachment.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Shrine
|
4
|
-
# Core class
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# Core class that provides an attachment interface for a specified attribute
|
5
|
+
# name, which can be added to model/entity classes. The model/entity plugins
|
6
|
+
# define the main interface, which delegates to a Shrine::Attacher object.
|
7
7
|
class Attachment < Module
|
8
8
|
@shrine_class = ::Shrine
|
9
9
|
|
@@ -22,8 +22,8 @@ class Shrine
|
|
22
22
|
# Shorthand for `Attachment.new`.
|
23
23
|
#
|
24
24
|
# Shrine::Attachment[:image]
|
25
|
-
def [](*args)
|
26
|
-
new(*args)
|
25
|
+
def [](*args, **options)
|
26
|
+
new(*args, **options)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
data/lib/shrine/plugins.rb
CHANGED
@@ -18,6 +18,28 @@ class Shrine
|
|
18
18
|
plugin
|
19
19
|
end
|
20
20
|
|
21
|
+
# Delegate call to the plugin in a way that works across Ruby versions.
|
22
|
+
def self.load_dependencies(plugin, uploader, *args, **kwargs, &block)
|
23
|
+
return unless plugin.respond_to?(:load_dependencies)
|
24
|
+
|
25
|
+
if kwargs.any?
|
26
|
+
plugin.load_dependencies(uploader, *args, **kwargs, &block)
|
27
|
+
else
|
28
|
+
plugin.load_dependencies(uploader, *args, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Delegate call to the plugin in a way that works across Ruby versions.
|
33
|
+
def self.configure(plugin, uploader, *args, **kwargs, &block)
|
34
|
+
return unless plugin.respond_to?(:configure)
|
35
|
+
|
36
|
+
if kwargs.any?
|
37
|
+
plugin.configure(uploader, *args, **kwargs, &block)
|
38
|
+
else
|
39
|
+
plugin.configure(uploader, *args, &block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
21
43
|
# Register the given plugin with Shrine, so that it can be loaded using
|
22
44
|
# `Shrine.plugin` with a symbol. Should be used by plugin files. Example:
|
23
45
|
#
|
@@ -9,8 +9,8 @@ class Shrine
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
def add_metadata(name = nil, &block)
|
13
|
-
opts[:add_metadata][:definitions] << [name, block]
|
12
|
+
def add_metadata(name = nil, **options, &block)
|
13
|
+
opts[:add_metadata][:definitions] << [name, options, block]
|
14
14
|
|
15
15
|
metadata_method(name) if name
|
16
16
|
end
|
@@ -22,7 +22,7 @@ class Shrine
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def _metadata_method(name)
|
25
|
-
|
25
|
+
self::UploadedFile.send(:define_method, name) do
|
26
26
|
metadata[name.to_s]
|
27
27
|
end
|
28
28
|
end
|
@@ -40,10 +40,12 @@ class Shrine
|
|
40
40
|
private
|
41
41
|
|
42
42
|
def extract_custom_metadata(io, **options)
|
43
|
-
opts[:add_metadata][:definitions].each do |name, block|
|
44
|
-
result = instance_exec(io, options, &block)
|
43
|
+
opts[:add_metadata][:definitions].each do |name, definition_options, block|
|
44
|
+
result = instance_exec(io, **options, &block)
|
45
45
|
|
46
|
-
if
|
46
|
+
if result.nil? && definition_options[:skip_nil]
|
47
|
+
# Do not store this metadata
|
48
|
+
elsif name
|
47
49
|
options[:metadata].merge! name.to_s => result
|
48
50
|
else
|
49
51
|
options[:metadata].merge! result.transform_keys(&:to_s) if result
|
@@ -55,8 +57,17 @@ class Shrine
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
60
|
+
module AttacherMethods
|
61
|
+
def add_metadata(new_metadata, &block)
|
62
|
+
file!.add_metadata(new_metadata, &block)
|
63
|
+
set(file) # trigger model write
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
58
67
|
module FileMethods
|
59
|
-
|
68
|
+
def add_metadata(new_metadata, &block)
|
69
|
+
@metadata = @metadata.merge(new_metadata, &block)
|
70
|
+
end
|
60
71
|
end
|
61
72
|
end
|
62
73
|
|
@@ -34,12 +34,12 @@ class Shrine
|
|
34
34
|
if @cache.respond_to?(:call)
|
35
35
|
if @cache.arity == 2
|
36
36
|
Shrine.deprecation("Passing record & name argument to default storage block is deprecated and will be removed in Shrine 4. Use a block without arguments instead.")
|
37
|
-
@cache.call(record, name)
|
37
|
+
@cache.call(record, name).to_sym
|
38
38
|
else
|
39
|
-
instance_exec(&@cache)
|
39
|
+
instance_exec(&@cache).to_sym
|
40
40
|
end
|
41
41
|
else
|
42
|
-
|
42
|
+
super
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -47,12 +47,12 @@ class Shrine
|
|
47
47
|
if @store.respond_to?(:call)
|
48
48
|
if @store.arity == 2
|
49
49
|
Shrine.deprecation("Passing record & name argument to default storage block is deprecated and will be removed in Shrine 4. Use a block without arguments instead.")
|
50
|
-
@store.call(record, name)
|
50
|
+
@store.call(record, name).to_sym
|
51
51
|
else
|
52
|
-
instance_exec(&@store)
|
52
|
+
instance_exec(&@store).to_sym
|
53
53
|
end
|
54
54
|
else
|
55
|
-
|
55
|
+
super
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -392,6 +392,7 @@ class Shrine
|
|
392
392
|
options[:type] = request.params["type"] if request.params["type"]
|
393
393
|
options[:disposition] = request.params["disposition"] if request.params["disposition"]
|
394
394
|
options[:filename] = request.params["filename"] if request.params["filename"]
|
395
|
+
options[:version] = request.params["version"] if request.params["version"]
|
395
396
|
options[:expires_in] = expires_in(request) if request.params["expires_at"]
|
396
397
|
|
397
398
|
derivation = uploaded_file.derivation(name, *args, **options)
|
@@ -475,9 +476,9 @@ class Shrine
|
|
475
476
|
file_response(derivative, env)
|
476
477
|
end
|
477
478
|
|
478
|
-
# Generates a Rack response triple from a local file
|
479
|
-
#
|
480
|
-
#
|
479
|
+
# Generates a Rack response triple from a local file. Fills in
|
480
|
+
# `Content-Type` and `Content-Disposition` response headers from derivation
|
481
|
+
# options and file extension of the derivation result.
|
481
482
|
def file_response(file, env)
|
482
483
|
response = rack_file_response(file.path, env)
|
483
484
|
|
@@ -511,7 +512,7 @@ class Shrine
|
|
511
512
|
end
|
512
513
|
|
513
514
|
if upload_redirect
|
514
|
-
redirect_url = uploaded_file.url(upload_redirect_url_options)
|
515
|
+
redirect_url = uploaded_file.url(**upload_redirect_url_options)
|
515
516
|
|
516
517
|
[302, { "Location" => redirect_url }, []]
|
517
518
|
else
|
@@ -528,10 +529,14 @@ class Shrine
|
|
528
529
|
end
|
529
530
|
end
|
530
531
|
|
531
|
-
# We call `Rack::
|
532
|
+
# We call `Rack::Files` with no default `Content-Type`, and make sure we
|
532
533
|
# stay compatible with both Rack 2.x and 1.6.x.
|
533
534
|
def rack_file_response(path, env)
|
534
|
-
|
535
|
+
if Rack.release >= "2.1"
|
536
|
+
server = Rack::Files.new("", {}, nil)
|
537
|
+
else
|
538
|
+
server = Rack::File.new("", {}, nil)
|
539
|
+
end
|
535
540
|
|
536
541
|
if Rack.release > "2"
|
537
542
|
server.serving(Rack::Request.new(env), path)
|
@@ -734,7 +739,7 @@ class Shrine
|
|
734
739
|
def verify_signature(string, signature)
|
735
740
|
if signature.nil?
|
736
741
|
fail InvalidSignature, "missing \"signature\" param"
|
737
|
-
elsif signature
|
742
|
+
elsif !Rack::Utils.secure_compare(signature, generate_signature(string))
|
738
743
|
fail InvalidSignature, "provided signature does not match the calculated signature"
|
739
744
|
end
|
740
745
|
end
|
@@ -4,8 +4,6 @@ class Shrine
|
|
4
4
|
module Plugins
|
5
5
|
# Documentation can be found on https://shrinerb.com/docs/plugins/derivatives
|
6
6
|
module Derivatives
|
7
|
-
NOOP_PROCESSOR = -> (*) { Hash.new }
|
8
|
-
|
9
7
|
LOG_SUBSCRIBER = -> (event) do
|
10
8
|
Shrine.logger.info "Derivatives (#{event.duration}ms) – #{{
|
11
9
|
processor: event[:processor],
|
@@ -21,7 +19,7 @@ class Shrine
|
|
21
19
|
end
|
22
20
|
|
23
21
|
def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
|
24
|
-
uploader.opts[:derivatives] ||= { processors: {}, storage: proc { store_key } }
|
22
|
+
uploader.opts[:derivatives] ||= { processors: {}, processor_settings: {}, storage: proc { store_key } }
|
25
23
|
uploader.opts[:derivatives].merge!(opts)
|
26
24
|
|
27
25
|
# instrumentation plugin integration
|
@@ -40,8 +38,8 @@ class Shrine
|
|
40
38
|
def define_model_methods(name)
|
41
39
|
super if defined?(super)
|
42
40
|
|
43
|
-
define_method(:"#{name}_derivatives!") do |*args|
|
44
|
-
send(:"#{name}_attacher").create_derivatives(*args)
|
41
|
+
define_method(:"#{name}_derivatives!") do |*args, **options|
|
42
|
+
send(:"#{name}_attacher").create_derivatives(*args, **options)
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
@@ -52,18 +50,36 @@ class Shrine
|
|
52
50
|
# Attacher.derivatives_processor :thumbnails do |original|
|
53
51
|
# # ...
|
54
52
|
# end
|
55
|
-
|
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)
|
56
66
|
if block
|
57
|
-
shrine_class.
|
67
|
+
shrine_class.derivatives_options[:processors][name.to_sym] = block
|
68
|
+
shrine_class.derivatives_options[:processor_settings][name.to_sym] = { download: download }
|
58
69
|
else
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
fail Error, "derivatives processor #{name.inspect} not registered" unless processor
|
63
|
-
|
64
|
-
processor
|
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
|
65
73
|
end
|
66
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
|
67
83
|
|
68
84
|
# Specifies default storage to which derivatives will be uploaded.
|
69
85
|
#
|
@@ -78,9 +94,9 @@ class Shrine
|
|
78
94
|
# end
|
79
95
|
def derivatives_storage(storage_key = nil, &block)
|
80
96
|
if storage_key || block
|
81
|
-
shrine_class.
|
97
|
+
shrine_class.derivatives_options[:storage] = storage_key || block
|
82
98
|
else
|
83
|
-
shrine_class.
|
99
|
+
shrine_class.derivatives_options[:storage]
|
84
100
|
end
|
85
101
|
end
|
86
102
|
end
|
@@ -146,6 +162,7 @@ class Shrine
|
|
146
162
|
def promote(**options)
|
147
163
|
super
|
148
164
|
promote_derivatives
|
165
|
+
create_derivatives if create_derivatives_on_promote?
|
149
166
|
end
|
150
167
|
|
151
168
|
# Uploads any cached derivatives to permanent storage.
|
@@ -179,9 +196,9 @@ class Shrine
|
|
179
196
|
# end
|
180
197
|
#
|
181
198
|
# attacher.create_derivatives(:my_processor)
|
182
|
-
def create_derivatives(*args)
|
183
|
-
files = process_derivatives(*args)
|
184
|
-
add_derivatives(files)
|
199
|
+
def create_derivatives(*args, storage: nil, **options)
|
200
|
+
files = process_derivatives(*args, **options)
|
201
|
+
add_derivatives(files, storage: storage)
|
185
202
|
end
|
186
203
|
|
187
204
|
# Uploads given hash of files and adds uploaded files to the
|
@@ -226,8 +243,6 @@ class Shrine
|
|
226
243
|
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
227
244
|
def upload_derivatives(files, **options)
|
228
245
|
map_derivative(files) do |path, file|
|
229
|
-
path = derivative_path(path)
|
230
|
-
|
231
246
|
upload_derivative(path, file, **options)
|
232
247
|
end
|
233
248
|
end
|
@@ -237,6 +252,7 @@ class Shrine
|
|
237
252
|
# hash = attacher.upload_derivative(:thumb, thumb)
|
238
253
|
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
239
254
|
def upload_derivative(path, file, storage: nil, **options)
|
255
|
+
path = derivative_path(path)
|
240
256
|
storage ||= derivative_storage(path)
|
241
257
|
|
242
258
|
file.open if file.is_a?(Tempfile) # refresh file descriptor
|
@@ -268,8 +284,10 @@ class Shrine
|
|
268
284
|
|
269
285
|
source ||= file!
|
270
286
|
|
271
|
-
|
272
|
-
|
287
|
+
processor_settings = self.class.derivatives_processor_settings(processor_name) || {}
|
288
|
+
|
289
|
+
if processor_settings[:download]
|
290
|
+
shrine_class.with_file(source) do |file|
|
273
291
|
_process_derivatives(processor_name, file, **options)
|
274
292
|
end
|
275
293
|
else
|
@@ -373,7 +391,7 @@ class Shrine
|
|
373
391
|
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
374
392
|
def set_derivatives(derivatives)
|
375
393
|
self.derivatives = derivatives
|
376
|
-
set file # trigger model
|
394
|
+
set file # trigger model write
|
377
395
|
derivatives
|
378
396
|
end
|
379
397
|
|
@@ -441,7 +459,7 @@ class Shrine
|
|
441
459
|
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
442
460
|
# attacher.change(file)
|
443
461
|
# attacher.derivatives #=> {}
|
444
|
-
def change(*
|
462
|
+
def change(*)
|
445
463
|
result = super
|
446
464
|
set_derivatives({})
|
447
465
|
result
|
@@ -462,8 +480,8 @@ class Shrine
|
|
462
480
|
# Iterates through nested derivatives and maps results.
|
463
481
|
#
|
464
482
|
# attacher.map_derivative(derivatives) { |path, file| ... }
|
465
|
-
def map_derivative(
|
466
|
-
shrine_class.map_derivative(
|
483
|
+
def map_derivative(derivatives, **options, &block)
|
484
|
+
shrine_class.map_derivative(derivatives, **options, &block)
|
467
485
|
end
|
468
486
|
|
469
487
|
private
|
@@ -472,7 +490,9 @@ class Shrine
|
|
472
490
|
def _process_derivatives(processor_name, source, **options)
|
473
491
|
processor = self.class.derivatives_processor(processor_name)
|
474
492
|
|
475
|
-
|
493
|
+
return {} unless processor
|
494
|
+
|
495
|
+
result = instrument_derivatives(processor_name, source, options) do
|
476
496
|
instance_exec(source, **options, &processor)
|
477
497
|
end
|
478
498
|
|
@@ -484,20 +504,22 @@ class Shrine
|
|
484
504
|
end
|
485
505
|
|
486
506
|
# Sends a `derivatives.shrine` event for instrumentation plugin.
|
487
|
-
def instrument_derivatives(processor_name, processor_options, &block)
|
507
|
+
def instrument_derivatives(processor_name, source, processor_options, &block)
|
488
508
|
return yield unless shrine_class.respond_to?(:instrument)
|
489
509
|
|
490
510
|
shrine_class.instrument(
|
491
511
|
:derivatives,
|
492
512
|
processor: processor_name,
|
493
513
|
processor_options: processor_options,
|
514
|
+
io: source,
|
515
|
+
attacher: self,
|
494
516
|
&block
|
495
517
|
)
|
496
518
|
end
|
497
519
|
|
498
520
|
# Returns symbolized array or single key.
|
499
521
|
def derivative_path(path)
|
500
|
-
path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
|
522
|
+
path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
|
501
523
|
path = path.first if path.one?
|
502
524
|
path
|
503
525
|
end
|
@@ -519,6 +541,11 @@ class Shrine
|
|
519
541
|
o2
|
520
542
|
end
|
521
543
|
end
|
544
|
+
|
545
|
+
# Whether to automatically create derivatives on promotion
|
546
|
+
def create_derivatives_on_promote?
|
547
|
+
shrine_class.derivatives_options[:create_on_promote]
|
548
|
+
end
|
522
549
|
end
|
523
550
|
|
524
551
|
module ClassMethods
|
@@ -575,6 +602,11 @@ class Shrine
|
|
575
602
|
yield path, object
|
576
603
|
end
|
577
604
|
end
|
605
|
+
|
606
|
+
# Returns derivatives plugin options.
|
607
|
+
def derivatives_options
|
608
|
+
opts[:derivatives]
|
609
|
+
end
|
578
610
|
end
|
579
611
|
|
580
612
|
module FileMethods
|