shrine 3.0.0 → 3.2.2

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.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -33
  3. data/LICENSE.txt +1 -1
  4. data/README.md +94 -4
  5. data/doc/advantages.md +35 -18
  6. data/doc/attacher.md +16 -17
  7. data/doc/carrierwave.md +75 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/external/articles.md +56 -41
  11. data/doc/external/extensions.md +38 -34
  12. data/doc/getting_started.md +182 -112
  13. data/doc/metadata.md +79 -43
  14. data/doc/multiple_files.md +5 -3
  15. data/doc/paperclip.md +110 -42
  16. data/doc/plugins/activerecord.md +5 -5
  17. data/doc/plugins/add_metadata.md +92 -35
  18. data/doc/plugins/backgrounding.md +12 -2
  19. data/doc/plugins/column.md +36 -7
  20. data/doc/plugins/data_uri.md +2 -2
  21. data/doc/plugins/default_url.md +6 -3
  22. data/doc/plugins/derivation_endpoint.md +26 -28
  23. data/doc/plugins/derivatives.md +205 -169
  24. data/doc/plugins/determine_mime_type.md +2 -2
  25. data/doc/plugins/entity.md +3 -3
  26. data/doc/plugins/form_assign.md +5 -5
  27. data/doc/plugins/included.md +25 -5
  28. data/doc/plugins/infer_extension.md +2 -2
  29. data/doc/plugins/instrumentation.md +1 -1
  30. data/doc/plugins/metadata_attributes.md +21 -10
  31. data/doc/plugins/model.md +4 -4
  32. data/doc/plugins/persistence.md +1 -0
  33. data/doc/plugins/refresh_metadata.md +5 -4
  34. data/doc/plugins/remote_url.md +8 -3
  35. data/doc/plugins/remove_invalid.md +9 -1
  36. data/doc/plugins/sequel.md +4 -4
  37. data/doc/plugins/signature.md +11 -2
  38. data/doc/plugins/store_dimensions.md +2 -2
  39. data/doc/plugins/type_predicates.md +96 -0
  40. data/doc/plugins/upload_endpoint.md +7 -11
  41. data/doc/plugins/upload_options.md +1 -1
  42. data/doc/plugins/url_options.md +2 -2
  43. data/doc/plugins/validation.md +14 -4
  44. data/doc/plugins/validation_helpers.md +3 -3
  45. data/doc/plugins/versions.md +11 -11
  46. data/doc/processing.md +289 -125
  47. data/doc/refile.md +39 -18
  48. data/doc/release_notes/2.19.0.md +1 -1
  49. data/doc/release_notes/3.0.0.md +275 -258
  50. data/doc/release_notes/3.0.1.md +22 -0
  51. data/doc/release_notes/3.1.0.md +73 -0
  52. data/doc/release_notes/3.2.0.md +96 -0
  53. data/doc/release_notes/3.2.1.md +32 -0
  54. data/doc/release_notes/3.2.2.md +14 -0
  55. data/doc/securing_uploads.md +3 -3
  56. data/doc/storage/file_system.md +1 -1
  57. data/doc/storage/memory.md +19 -0
  58. data/doc/storage/s3.md +105 -86
  59. data/doc/testing.md +2 -2
  60. data/doc/upgrading_to_3.md +115 -33
  61. data/doc/validation.md +3 -2
  62. data/lib/shrine.rb +8 -8
  63. data/lib/shrine/attacher.rb +19 -14
  64. data/lib/shrine/attachment.rb +5 -5
  65. data/lib/shrine/plugins.rb +22 -0
  66. data/lib/shrine/plugins/add_metadata.rb +12 -3
  67. data/lib/shrine/plugins/default_storage.rb +6 -6
  68. data/lib/shrine/plugins/default_url.rb +1 -1
  69. data/lib/shrine/plugins/derivation_endpoint.rb +10 -6
  70. data/lib/shrine/plugins/derivatives.rb +19 -17
  71. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  72. data/lib/shrine/plugins/entity.rb +6 -6
  73. data/lib/shrine/plugins/metadata_attributes.rb +1 -1
  74. data/lib/shrine/plugins/model.rb +3 -3
  75. data/lib/shrine/plugins/presign_endpoint.rb +2 -2
  76. data/lib/shrine/plugins/pretty_location.rb +1 -1
  77. data/lib/shrine/plugins/processing.rb +1 -1
  78. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  79. data/lib/shrine/plugins/remote_url.rb +3 -3
  80. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  81. data/lib/shrine/plugins/signature.rb +7 -6
  82. data/lib/shrine/plugins/store_dimensions.rb +18 -9
  83. data/lib/shrine/plugins/type_predicates.rb +113 -0
  84. data/lib/shrine/plugins/upload_endpoint.rb +3 -3
  85. data/lib/shrine/plugins/upload_options.rb +2 -2
  86. data/lib/shrine/plugins/url_options.rb +2 -2
  87. data/lib/shrine/plugins/validation.rb +9 -7
  88. data/lib/shrine/storage/linter.rb +4 -4
  89. data/lib/shrine/storage/s3.rb +62 -38
  90. data/lib/shrine/uploaded_file.rb +5 -1
  91. data/lib/shrine/version.rb +2 -2
  92. data/shrine.gemspec +6 -7
  93. metadata +23 -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
  #
@@ -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
@@ -41,7 +41,7 @@ class Shrine
41
41
 
42
42
  def extract_custom_metadata(io, **options)
43
43
  opts[:add_metadata][:definitions].each do |name, block|
44
- result = instance_exec(io, options, &block)
44
+ result = instance_exec(io, **options, &block)
45
45
 
46
46
  if name
47
47
  options[:metadata].merge! name.to_s => result
@@ -55,8 +55,17 @@ class Shrine
55
55
  end
56
56
  end
57
57
 
58
+ module AttacherMethods
59
+ def add_metadata(new_metadata, &block)
60
+ file!.add_metadata(new_metadata, &block)
61
+ set(file) # trigger model write
62
+ end
63
+ end
64
+
58
65
  module FileMethods
59
- # methods will be dynamically defined here through `Shrine.add_metadata`
66
+ def add_metadata(new_metadata, &block)
67
+ @metadata = @metadata.merge(new_metadata, &block)
68
+ end
60
69
  end
61
70
  end
62
71
 
@@ -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
@@ -475,9 +475,9 @@ class Shrine
475
475
  file_response(derivative, env)
476
476
  end
477
477
 
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.
478
+ # Generates a Rack response triple from a local file. Fills in
479
+ # `Content-Type` and `Content-Disposition` response headers from derivation
480
+ # options and file extension of the derivation result.
481
481
  def file_response(file, env)
482
482
  response = rack_file_response(file.path, env)
483
483
 
@@ -511,7 +511,7 @@ class Shrine
511
511
  end
512
512
 
513
513
  if upload_redirect
514
- redirect_url = uploaded_file.url(upload_redirect_url_options)
514
+ redirect_url = uploaded_file.url(**upload_redirect_url_options)
515
515
 
516
516
  [302, { "Location" => redirect_url }, []]
517
517
  else
@@ -528,10 +528,14 @@ class Shrine
528
528
  end
529
529
  end
530
530
 
531
- # We call `Rack::File` with no default `Content-Type`, and make sure we
531
+ # We call `Rack::Files` with no default `Content-Type`, and make sure we
532
532
  # stay compatible with both Rack 2.x and 1.6.x.
533
533
  def rack_file_response(path, env)
534
- server = Rack::File.new("", {}, nil)
534
+ if Rack.release >= "2.1"
535
+ server = Rack::Files.new("", {}, nil)
536
+ else
537
+ server = Rack::File.new("", {}, nil)
538
+ end
535
539
 
536
540
  if Rack.release > "2"
537
541
  server.serving(Rack::Request.new(env), path)
@@ -40,8 +40,8 @@ class Shrine
40
40
  def define_model_methods(name)
41
41
  super if defined?(super)
42
42
 
43
- define_method(:"#{name}_derivatives!") do |*args|
44
- send(:"#{name}_attacher").create_derivatives(*args)
43
+ define_method(:"#{name}_derivatives!") do |*args, **options|
44
+ send(:"#{name}_attacher").create_derivatives(*args, **options)
45
45
  end
46
46
  end
47
47
  end
@@ -64,6 +64,7 @@ class Shrine
64
64
  processor
65
65
  end
66
66
  end
67
+ alias derivatives derivatives_processor
67
68
 
68
69
  # Specifies default storage to which derivatives will be uploaded.
69
70
  #
@@ -179,9 +180,9 @@ class Shrine
179
180
  # end
180
181
  #
181
182
  # attacher.create_derivatives(:my_processor)
182
- def create_derivatives(*args)
183
- files = process_derivatives(*args)
184
- add_derivatives(files)
183
+ def create_derivatives(*args, storage: nil, **options)
184
+ files = process_derivatives(*args, **options)
185
+ add_derivatives(files, storage: storage)
185
186
  end
186
187
 
187
188
  # Uploads given hash of files and adds uploaded files to the
@@ -226,8 +227,6 @@ class Shrine
226
227
  # hash[:thumb] #=> #<Shrine::UploadedFile>
227
228
  def upload_derivatives(files, **options)
228
229
  map_derivative(files) do |path, file|
229
- path = derivative_path(path)
230
-
231
230
  upload_derivative(path, file, **options)
232
231
  end
233
232
  end
@@ -237,6 +236,7 @@ class Shrine
237
236
  # hash = attacher.upload_derivative(:thumb, thumb)
238
237
  # hash[:thumb] #=> #<Shrine::UploadedFile>
239
238
  def upload_derivative(path, file, storage: nil, **options)
239
+ path = derivative_path(path)
240
240
  storage ||= derivative_storage(path)
241
241
 
242
242
  file.open if file.is_a?(Tempfile) # refresh file descriptor
@@ -373,7 +373,7 @@ class Shrine
373
373
  # attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
374
374
  def set_derivatives(derivatives)
375
375
  self.derivatives = derivatives
376
- set file # trigger model writing
376
+ set file # trigger model write
377
377
  derivatives
378
378
  end
379
379
 
@@ -441,7 +441,7 @@ class Shrine
441
441
  # attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
442
442
  # attacher.change(file)
443
443
  # attacher.derivatives #=> {}
444
- def change(*args)
444
+ def change(*)
445
445
  result = super
446
446
  set_derivatives({})
447
447
  result
@@ -462,8 +462,8 @@ class Shrine
462
462
  # Iterates through nested derivatives and maps results.
463
463
  #
464
464
  # attacher.map_derivative(derivatives) { |path, file| ... }
465
- def map_derivative(*args, &block)
466
- shrine_class.map_derivative(*args, &block)
465
+ def map_derivative(derivatives, **options, &block)
466
+ shrine_class.map_derivative(derivatives, **options, &block)
467
467
  end
468
468
 
469
469
  private
@@ -472,7 +472,7 @@ class Shrine
472
472
  def _process_derivatives(processor_name, source, **options)
473
473
  processor = self.class.derivatives_processor(processor_name)
474
474
 
475
- result = instrument_derivatives(processor_name, options) do
475
+ result = instrument_derivatives(processor_name, source, options) do
476
476
  instance_exec(source, **options, &processor)
477
477
  end
478
478
 
@@ -484,20 +484,22 @@ class Shrine
484
484
  end
485
485
 
486
486
  # Sends a `derivatives.shrine` event for instrumentation plugin.
487
- def instrument_derivatives(processor_name, processor_options, &block)
487
+ def instrument_derivatives(processor_name, source, processor_options, &block)
488
488
  return yield unless shrine_class.respond_to?(:instrument)
489
489
 
490
490
  shrine_class.instrument(
491
491
  :derivatives,
492
492
  processor: processor_name,
493
493
  processor_options: processor_options,
494
+ io: source,
495
+ attacher: self,
494
496
  &block
495
497
  )
496
498
  end
497
499
 
498
500
  # Returns symbolized array or single key.
499
501
  def derivative_path(path)
500
- path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
502
+ path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
501
503
  path = path.first if path.one?
502
504
  path
503
505
  end
@@ -525,13 +527,13 @@ class Shrine
525
527
  # Converts data into a Hash of derivatives.
526
528
  #
527
529
  # Shrine.derivatives('{"thumb":{"id":"foo","storage":"store","metadata":{}}}')
528
- # #=> { thumb: #<Shrine::UploadedFile @id="foo" @storage_key="store" @metadata={}> }
530
+ # #=> { thumb: #<Shrine::UploadedFile id="foo" storage=:store metadata={}> }
529
531
  #
530
532
  # Shrine.derivatives({ "thumb" => { "id" => "foo", "storage" => "store", "metadata" => {} } })
531
- # #=> { thumb: #<Shrine::UploadedFile @id="foo" @storage_key="store" @metadata={}> }
533
+ # #=> { thumb: #<Shrine::UploadedFile id="foo" storage=:store metadata={}> }
532
534
  #
533
535
  # Shrine.derivatives({ thumb: { id: "foo", storage: "store", metadata: {} } })
534
- # #=> { thumb: #<Shrine::UploadedFile @id="foo" @storage_key="store" @metadata={}> }
536
+ # #=> { thumb: #<Shrine::UploadedFile id="foo" storage=:store metadata={}> }
535
537
  def derivatives(object)
536
538
  if object.is_a?(String)
537
539
  derivatives JSON.parse(object)
@@ -141,7 +141,7 @@ class Shrine
141
141
  require "mimemagic"
142
142
 
143
143
  mime = MimeMagic.by_magic(io)
144
- mime.type if mime
144
+ mime&.type
145
145
  end
146
146
 
147
147
  def extract_with_marcel(io, options)
@@ -158,7 +158,7 @@ class Shrine
158
158
 
159
159
  if filename = extract_filename(io)
160
160
  mime_type = MIME::Types.of(filename).first
161
- mime_type.content_type if mime_type
161
+ mime_type&.content_type
162
162
  end
163
163
  end
164
164
 
@@ -167,7 +167,7 @@ class Shrine
167
167
 
168
168
  if filename = extract_filename(io)
169
169
  info = MiniMime.lookup_by_filename(filename)
170
- info.content_type if info
170
+ info&.content_type
171
171
  end
172
172
  end
173
173
 
@@ -41,27 +41,27 @@ class Shrine
41
41
  end
42
42
 
43
43
  # Returns the URL to the attached file.
44
- define_method :"#{name}_url" do |*args|
45
- send(:"#{name}_attacher").url(*args)
44
+ define_method :"#{name}_url" do |*args, **options|
45
+ send(:"#{name}_attacher").url(*args, **options)
46
46
  end
47
47
 
48
48
  # Returns an attacher instance.
49
49
  define_method :"#{name}_attacher" do |**options|
50
- attachment.send(:attacher, self, options)
50
+ attachment.send(:attacher, self, **options)
51
51
  end
52
52
  end
53
53
 
54
54
  # Returns the class attacher instance with loaded entity. It's not
55
55
  # memoized because the entity object could be frozen.
56
- def attacher(record, options)
57
- attacher = class_attacher(options)
56
+ def attacher(record, **options)
57
+ attacher = class_attacher(**options)
58
58
  attacher.load_entity(record, @name)
59
59
  attacher
60
60
  end
61
61
 
62
62
  # Creates an instance of the corresponding attacher class with set
63
63
  # name.
64
- def class_attacher(options)
64
+ def class_attacher(**options)
65
65
  attacher = shrine_class::Attacher.new(**@options, **options)
66
66
  attacher.instance_variable_set(:@name, @name)
67
67
  attacher
@@ -38,7 +38,7 @@ class Shrine
38
38
 
39
39
  next unless record.respond_to?(metadata_attribute)
40
40
 
41
- values[metadata_attribute] = file&.metadata[source.to_s]
41
+ values[metadata_attribute] = file && file.metadata[source.to_s]
42
42
  end
43
43
 
44
44
  values
@@ -55,11 +55,11 @@ class Shrine
55
55
  end
56
56
 
57
57
  # Memoizes the attacher instance into an instance variable.
58
- def attacher(record, options)
58
+ def attacher(record, **options)
59
59
  return super unless model?
60
60
 
61
61
  if !record.instance_variable_get(:"@#{@name}_attacher") || options.any?
62
- attacher = class_attacher(options)
62
+ attacher = class_attacher(**options)
63
63
  attacher.load_model(record, @name)
64
64
 
65
65
  record.instance_variable_set(:"@#{@name}_attacher", attacher)
@@ -118,7 +118,7 @@ class Shrine
118
118
  end
119
119
 
120
120
  # Writes uploaded file data into the model.
121
- def set(*args)
121
+ def set(*)
122
122
  result = super
123
123
  write if model?
124
124
  result
@@ -8,7 +8,7 @@ class Shrine
8
8
  module Plugins
9
9
  # Documentation can be found on https://shrinerb.com/docs/plugins/presign_endpoint
10
10
  module PresignEndpoint
11
- def self.configure(uploader, opts = {})
11
+ def self.configure(uploader, **opts)
12
12
  uploader.opts[:presign_endpoint] ||= {}
13
13
  uploader.opts[:presign_endpoint].merge!(opts)
14
14
  end
@@ -135,7 +135,7 @@ class Shrine
135
135
  if @presign
136
136
  data = @presign.call(location, options, request)
137
137
  else
138
- data = storage.presign(location, options)
138
+ data = storage.presign(location, **options)
139
139
  end
140
140
 
141
141
  { fields: {}, headers: {} }.merge(data.to_h)
@@ -11,7 +11,7 @@ class Shrine
11
11
 
12
12
  module InstanceMethods
13
13
  def generate_location(io, **options)
14
- pretty_location(io, options)
14
+ pretty_location(io, **options)
15
15
  end
16
16
 
17
17
  def pretty_location(io, name: nil, record: nil, version: nil, derivative: nil, identifier: nil, metadata: {}, **)
@@ -33,7 +33,7 @@ class Shrine
33
33
  def process(io, **options)
34
34
  pipeline = processing_pipeline(options[:action])
35
35
  pipeline.inject(io) do |input, processor|
36
- instance_exec(input, options, &processor) || input
36
+ instance_exec(input, **options, &processor) || input
37
37
  end
38
38
  end
39
39
 
@@ -6,8 +6,8 @@ class Shrine
6
6
  module RefreshMetadata
7
7
  module AttacherMethods
8
8
  def refresh_metadata!(**options)
9
- file.refresh_metadata!(**context, **options)
10
- set(file)
9
+ file!.refresh_metadata!(**context, **options)
10
+ set(file) # trigger model write
11
11
  end
12
12
  end
13
13
 
@@ -16,7 +16,7 @@ class Shrine
16
16
  }.inspect}"
17
17
  end
18
18
 
19
- DOWNLOADER = -> (url, options) { Down.download(url, options) }
19
+ DOWNLOADER = -> (url, **options) { Down.download(url, **options) }
20
20
 
21
21
  def self.load_dependencies(uploader, *)
22
22
  uploader.plugin :validation
@@ -63,7 +63,7 @@ class Shrine
63
63
  private
64
64
 
65
65
  def download_remote_url(url, options)
66
- opts[:remote_url][:downloader].call(url, options)
66
+ opts[:remote_url][:downloader].call(url, **options)
67
67
  rescue Down::TooLarge
68
68
  fail DownloadError, "remote file too large"
69
69
  rescue Down::Error
@@ -87,7 +87,7 @@ class Shrine
87
87
  def assign_remote_url(url, downloader: {}, **options)
88
88
  return if url == "" || url.nil?
89
89
 
90
- downloaded_file = shrine_class.remote_url(url, downloader)
90
+ downloaded_file = shrine_class.remote_url(url, **downloader)
91
91
  attach_cached(downloaded_file, **options)
92
92
  rescue DownloadError => error
93
93
  errors.clear << remote_url_error_message(url, error)
@@ -9,18 +9,23 @@ class Shrine
9
9
  end
10
10
 
11
11
  module AttacherMethods
12
- def change(*)
12
+ def validate(*)
13
13
  super
14
14
  ensure
15
- revert_change if errors.any?
15
+ deassign if errors.any?
16
16
  end
17
17
 
18
18
  private
19
19
 
20
- def revert_change
20
+ def deassign
21
21
  destroy
22
- set @previous.file
23
- remove_instance_variable(:@previous)
22
+
23
+ if changed?
24
+ load_data @previous.data
25
+ @previous = nil
26
+ else
27
+ load_data nil
28
+ end
24
29
  end
25
30
  end
26
31
  end