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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +15 -5
  5. data/doc/advantages.md +33 -16
  6. data/doc/attacher.md +2 -2
  7. data/doc/carrierwave.md +78 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/direct_s3.md +1 -0
  11. data/doc/external/articles.md +57 -45
  12. data/doc/external/extensions.md +41 -35
  13. data/doc/external/misc.md +23 -8
  14. data/doc/getting_started.md +177 -112
  15. data/doc/metadata.md +79 -43
  16. data/doc/multiple_files.md +6 -4
  17. data/doc/paperclip.md +119 -42
  18. data/doc/plugins/activerecord.md +1 -1
  19. data/doc/plugins/add_metadata.md +112 -35
  20. data/doc/plugins/atomic_helpers.md +41 -3
  21. data/doc/plugins/backgrounding.md +12 -2
  22. data/doc/plugins/column.md +36 -7
  23. data/doc/plugins/data_uri.md +2 -2
  24. data/doc/plugins/default_url.md +6 -3
  25. data/doc/plugins/derivation_endpoint.md +26 -28
  26. data/doc/plugins/derivatives.md +238 -171
  27. data/doc/plugins/determine_mime_type.md +2 -2
  28. data/doc/plugins/download_endpoint.md +5 -5
  29. data/doc/plugins/dynamic_storage.md +1 -1
  30. data/doc/plugins/form_assign.md +5 -5
  31. data/doc/plugins/included.md +25 -5
  32. data/doc/plugins/infer_extension.md +11 -2
  33. data/doc/plugins/instrumentation.md +1 -1
  34. data/doc/plugins/metadata_attributes.md +22 -10
  35. data/doc/plugins/mirroring.md +1 -1
  36. data/doc/plugins/persistence.md +11 -1
  37. data/doc/plugins/refresh_metadata.md +5 -4
  38. data/doc/plugins/remote_url.md +8 -3
  39. data/doc/plugins/remove_invalid.md +9 -1
  40. data/doc/plugins/signature.md +11 -2
  41. data/doc/plugins/store_dimensions.md +12 -2
  42. data/doc/plugins/type_predicates.md +96 -0
  43. data/doc/plugins/upload_endpoint.md +7 -11
  44. data/doc/plugins/upload_options.md +1 -1
  45. data/doc/plugins/url_options.md +4 -4
  46. data/doc/plugins/validation.md +14 -4
  47. data/doc/plugins/validation_helpers.md +3 -3
  48. data/doc/plugins/versions.md +7 -7
  49. data/doc/processing.md +290 -127
  50. data/doc/refile.md +39 -18
  51. data/doc/release_notes/2.19.0.md +1 -1
  52. data/doc/release_notes/2.8.0.md +1 -1
  53. data/doc/release_notes/3.0.0.md +1 -1
  54. data/doc/release_notes/3.0.1.md +4 -0
  55. data/doc/release_notes/3.1.0.md +73 -0
  56. data/doc/release_notes/3.2.0.md +96 -0
  57. data/doc/release_notes/3.2.1.md +31 -0
  58. data/doc/release_notes/3.2.2.md +14 -0
  59. data/doc/release_notes/3.3.0.md +105 -0
  60. data/doc/securing_uploads.md +3 -3
  61. data/doc/storage/file_system.md +1 -1
  62. data/doc/storage/memory.md +19 -0
  63. data/doc/storage/s3.md +105 -82
  64. data/doc/testing.md +2 -2
  65. data/doc/upgrading_to_3.md +97 -49
  66. data/doc/validation.md +3 -2
  67. data/lib/shrine.rb +8 -8
  68. data/lib/shrine/attacher.rb +24 -14
  69. data/lib/shrine/attachment.rb +5 -5
  70. data/lib/shrine/plugins.rb +22 -0
  71. data/lib/shrine/plugins/activerecord.rb +1 -1
  72. data/lib/shrine/plugins/add_metadata.rb +18 -7
  73. data/lib/shrine/plugins/backgrounding.rb +2 -2
  74. data/lib/shrine/plugins/default_storage.rb +6 -6
  75. data/lib/shrine/plugins/default_url.rb +1 -1
  76. data/lib/shrine/plugins/derivation_endpoint.rb +12 -7
  77. data/lib/shrine/plugins/derivatives.rb +61 -29
  78. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  79. data/lib/shrine/plugins/entity.rb +6 -6
  80. data/lib/shrine/plugins/mirroring.rb +8 -8
  81. data/lib/shrine/plugins/model.rb +3 -3
  82. data/lib/shrine/plugins/presign_endpoint.rb +16 -4
  83. data/lib/shrine/plugins/pretty_location.rb +1 -1
  84. data/lib/shrine/plugins/processing.rb +1 -1
  85. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  86. data/lib/shrine/plugins/remote_url.rb +3 -3
  87. data/lib/shrine/plugins/remove_attachment.rb +5 -0
  88. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  89. data/lib/shrine/plugins/sequel.rb +1 -1
  90. data/lib/shrine/plugins/signature.rb +7 -6
  91. data/lib/shrine/plugins/store_dimensions.rb +22 -11
  92. data/lib/shrine/plugins/type_predicates.rb +113 -0
  93. data/lib/shrine/plugins/upload_endpoint.rb +10 -5
  94. data/lib/shrine/plugins/upload_options.rb +2 -2
  95. data/lib/shrine/plugins/url_options.rb +2 -2
  96. data/lib/shrine/plugins/validation.rb +9 -7
  97. data/lib/shrine/storage/linter.rb +4 -4
  98. data/lib/shrine/storage/memory.rb +5 -3
  99. data/lib/shrine/storage/s3.rb +117 -38
  100. data/lib/shrine/uploaded_file.rb +0 -1
  101. data/lib/shrine/version.rb +2 -2
  102. data/shrine.gemspec +7 -8
  103. metadata +25 -31
@@ -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 braces are required
95
+ super() # empty parentheses are required
95
96
  validate_mime_type %w[image/jpeg image/png image/webp]
96
97
  end
97
98
  end
@@ -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 represents uploader.
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; end
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
- plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
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
- plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
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
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Shrine
4
- # Core class which handles attaching files to model instances.
5
- # Base implementation is defined in InstanceMethods and ClassMethods.
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 = file
43
- @cache = cache
44
- @store = 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), **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, **options)
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
- remove_instance_variable(:@previous) if changed?
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 unless @file == file
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
- instance_variable_defined?(:@previous)
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
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Shrine
4
- # Core class which creates attachment modules for specified attribute names
5
- # that are included into model classes.
6
- # Base implementation is defined in InstanceMethods and ClassMethods.
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
 
@@ -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
  #
@@ -55,7 +55,7 @@ class Shrine
55
55
  # reload the attacher on record reload
56
56
  define_method :reload do |*args|
57
57
  result = super(*args)
58
- instance_variable_set(:"@#{name}_attacher", nil)
58
+ send(:"#{name}_attacher").reload
59
59
  result
60
60
  end
61
61
  end
@@ -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
- FileMethods.send(:define_method, name) do
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 name
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
- # methods will be dynamically defined here through `Shrine.add_metadata`
68
+ def add_metadata(new_metadata, &block)
69
+ @metadata = @metadata.merge(new_metadata, &block)
70
+ end
60
71
  end
61
72
  end
62
73
 
@@ -36,8 +36,8 @@ class Shrine
36
36
 
37
37
  module AttacherMethods
38
38
  # Inherits global hooks if defined.
39
- def initialize(*args)
40
- super
39
+ def initialize(**args)
40
+ super(**args)
41
41
  @destroy_block = self.class.destroy_block
42
42
  @promote_block = self.class.promote_block
43
43
  end
@@ -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
- @cache
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
- @store
55
+ super
56
56
  end
57
57
  end
58
58
  end
@@ -25,7 +25,7 @@ class Shrine
25
25
  def default_url(**options)
26
26
  return unless default_url_block
27
27
 
28
- url = instance_exec(options, &default_url_block)
28
+ url = instance_exec(**options, &default_url_block)
29
29
 
30
30
  [*default_url_host, url].join
31
31
  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 using `Rack::File`.
479
- # Fills in `Content-Type` and `Content-Disposition` response headers from
480
- # derivation options and file extension of the derivation result.
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::File` with no default `Content-Type`, and make sure we
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
- server = Rack::File.new("", {}, nil)
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 != generate_signature(string)
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
- def derivatives_processor(name = :default, &block)
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.opts[:derivatives][:processors][name.to_sym] = block
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
- processor = shrine_class.opts[:derivatives][:processors][name.to_sym]
60
- processor ||= NOOP_PROCESSOR if name == :default
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.opts[:derivatives][:storage] = storage_key || block
97
+ shrine_class.derivatives_options[:storage] = storage_key || block
82
98
  else
83
- shrine_class.opts[:derivatives][:storage]
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
- if source.is_a?(UploadedFile)
272
- source.download do |file|
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 writing
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(*args)
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(*args, &block)
466
- shrine_class.map_derivative(*args, &block)
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
- result = instrument_derivatives(processor_name, options) do
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