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.

Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -11
  3. data/README.md +9 -3
  4. data/doc/advantages.md +1 -1
  5. data/doc/carrierwave.md +4 -4
  6. data/doc/creating_persistence_plugins.md +172 -0
  7. data/doc/creating_plugins.md +1 -1
  8. data/doc/creating_storages.md +3 -1
  9. data/doc/design.md +2 -2
  10. data/doc/direct_s3.md +0 -22
  11. data/doc/paperclip.md +3 -3
  12. data/doc/plugins/activerecord.md +211 -42
  13. data/doc/plugins/atomic_helpers.md +153 -0
  14. data/doc/plugins/column.md +90 -0
  15. data/doc/plugins/derivation_endpoint.md +54 -62
  16. data/doc/plugins/derivatives.md +752 -0
  17. data/doc/plugins/entity.md +204 -0
  18. data/doc/plugins/infer_extension.md +8 -8
  19. data/doc/plugins/instrumentation.md +33 -13
  20. data/doc/plugins/keep_files.md +5 -15
  21. data/doc/plugins/model.md +157 -0
  22. data/doc/plugins/presign_endpoint.md +2 -1
  23. data/doc/plugins/refresh_metadata.md +44 -7
  24. data/doc/plugins/sequel.md +190 -33
  25. data/doc/plugins/{default_url_options.md → url_options.md} +5 -5
  26. data/doc/processing.md +1 -1
  27. data/doc/release_notes/1.1.0.md +2 -2
  28. data/doc/release_notes/2.15.0.md +1 -1
  29. data/doc/storage/s3.md +2 -2
  30. data/doc/testing.md +1 -1
  31. data/lib/shrine.rb +72 -138
  32. data/lib/shrine/attacher.rb +272 -176
  33. data/lib/shrine/attachment.rb +2 -42
  34. data/lib/shrine/plugins/activerecord.rb +103 -26
  35. data/lib/shrine/plugins/add_metadata.rb +9 -10
  36. data/lib/shrine/plugins/atomic_helpers.rb +111 -0
  37. data/lib/shrine/plugins/attacher_options.rb +55 -0
  38. data/lib/shrine/plugins/backgrounding.rb +147 -115
  39. data/lib/shrine/plugins/cached_attachment_data.rb +6 -9
  40. data/lib/shrine/plugins/column.rb +104 -0
  41. data/lib/shrine/plugins/data_uri.rb +35 -38
  42. data/lib/shrine/plugins/default_storage.rb +18 -12
  43. data/lib/shrine/plugins/default_url.rb +11 -21
  44. data/lib/shrine/plugins/default_url_options.rb +3 -30
  45. data/lib/shrine/plugins/delete_raw.rb +9 -13
  46. data/lib/shrine/plugins/derivation_endpoint.rb +75 -114
  47. data/lib/shrine/plugins/derivatives.rb +576 -0
  48. data/lib/shrine/plugins/determine_mime_type.rb +3 -15
  49. data/lib/shrine/plugins/download_endpoint.rb +83 -131
  50. data/lib/shrine/plugins/dynamic_storage.rb +4 -8
  51. data/lib/shrine/plugins/entity.rb +128 -0
  52. data/lib/shrine/plugins/form_assign.rb +107 -0
  53. data/lib/shrine/plugins/included.rb +4 -3
  54. data/lib/shrine/plugins/infer_extension.rb +10 -17
  55. data/lib/shrine/plugins/instrumentation.rb +45 -25
  56. data/lib/shrine/plugins/keep_files.rb +2 -12
  57. data/lib/shrine/plugins/metadata_attributes.rb +15 -14
  58. data/lib/shrine/plugins/model.rb +137 -0
  59. data/lib/shrine/plugins/module_include.rb +2 -0
  60. data/lib/shrine/plugins/presign_endpoint.rb +1 -15
  61. data/lib/shrine/plugins/pretty_location.rb +5 -5
  62. data/lib/shrine/plugins/processing.rb +21 -6
  63. data/lib/shrine/plugins/rack_file.rb +1 -39
  64. data/lib/shrine/plugins/rack_response.rb +14 -7
  65. data/lib/shrine/plugins/recache.rb +5 -2
  66. data/lib/shrine/plugins/refresh_metadata.rb +12 -8
  67. data/lib/shrine/plugins/remote_url.rb +44 -53
  68. data/lib/shrine/plugins/remove_attachment.rb +7 -2
  69. data/lib/shrine/plugins/remove_invalid.rb +8 -4
  70. data/lib/shrine/plugins/restore_cached_data.rb +12 -4
  71. data/lib/shrine/plugins/sequel.rb +115 -27
  72. data/lib/shrine/plugins/signature.rb +2 -7
  73. data/lib/shrine/plugins/store_dimensions.rb +13 -27
  74. data/lib/shrine/plugins/upload_endpoint.rb +14 -15
  75. data/lib/shrine/plugins/upload_options.rb +9 -8
  76. data/lib/shrine/plugins/url_options.rb +33 -0
  77. data/lib/shrine/plugins/validation.rb +87 -0
  78. data/lib/shrine/plugins/validation_helpers.rb +33 -54
  79. data/lib/shrine/plugins/versions.rb +106 -84
  80. data/lib/shrine/storage/file_system.rb +32 -57
  81. data/lib/shrine/storage/linter.rb +9 -1
  82. data/lib/shrine/storage/memory.rb +42 -0
  83. data/lib/shrine/storage/s3.rb +38 -146
  84. data/lib/shrine/uploaded_file.rb +22 -29
  85. data/lib/shrine/version.rb +4 -4
  86. data/shrine.gemspec +2 -3
  87. metadata +27 -54
  88. data/doc/plugins/backup.md +0 -31
  89. data/doc/plugins/copy.md +0 -24
  90. data/doc/plugins/delete_promoted.md +0 -12
  91. data/doc/plugins/direct_upload.md +0 -172
  92. data/doc/plugins/hooks.md +0 -58
  93. data/doc/plugins/logging.md +0 -42
  94. data/doc/plugins/migration_helpers.md +0 -60
  95. data/doc/plugins/moving.md +0 -19
  96. data/doc/plugins/multi_delete.md +0 -20
  97. data/doc/plugins/parallelize.md +0 -16
  98. data/doc/plugins/parsed_json.md +0 -23
  99. data/lib/shrine/plugins/background_helpers.rb +0 -5
  100. data/lib/shrine/plugins/backup.rb +0 -90
  101. data/lib/shrine/plugins/copy.rb +0 -50
  102. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  103. data/lib/shrine/plugins/direct_upload.rb +0 -217
  104. data/lib/shrine/plugins/hooks.rb +0 -90
  105. data/lib/shrine/plugins/logging.rb +0 -142
  106. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  107. data/lib/shrine/plugins/moving.rb +0 -57
  108. data/lib/shrine/plugins/multi_delete.rb +0 -32
  109. data/lib/shrine/plugins/parallelize.rb +0 -78
  110. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ Shrine.deprecation("The module_include plugin is deprecated and will be removed in Shrine 4. Override core classes directly instead.")
4
+
3
5
  class Shrine
4
6
  module Plugins
5
7
  # Documentation lives in [doc/plugins/module_include.md] on GitHub.
@@ -140,17 +140,7 @@ class Shrine
140
140
  data = storage.presign(location, options)
141
141
  end
142
142
 
143
- if data.respond_to?(:to_h)
144
- { fields: {}, headers: {} }.merge(data.to_h)
145
- else
146
- Shrine.deprecation("Returning a custom object in Storage#presign is deprecated, presign_endpoint will not support it in Shrine 3. Storage#presign should return a Hash instead.")
147
-
148
- url = data.url
149
- fields = data.fields
150
- headers = data.headers if data.respond_to?(:headers)
151
-
152
- { url: url, fields: fields.to_h, headers: headers.to_h }
153
- end
143
+ { fields: {}, headers: {} }.merge(data.to_h)
154
144
  end
155
145
 
156
146
  # Transforms the presign hash into a JSON response. It returns a Rack
@@ -185,8 +175,4 @@ class Shrine
185
175
  @shrine_class.find_storage(@storage_key)
186
176
  end
187
177
  end
188
-
189
- # backwards compatibility
190
- Plugins::PresignEndpoint.const_set(:App, PresignEndpoint)
191
- Plugins::PresignEndpoint.deprecate_constant(:App)
192
178
  end
@@ -6,24 +6,24 @@ class Shrine
6
6
  #
7
7
  # [doc/plugins/pretty_location.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/pretty_location.md
8
8
  module PrettyLocation
9
- def self.configure(uploader, opts = {})
9
+ def self.configure(uploader, **opts)
10
10
  uploader.opts[:pretty_location] ||= { identifier: :id }
11
11
  uploader.opts[:pretty_location].merge!(opts)
12
12
  end
13
13
 
14
14
  module InstanceMethods
15
- def generate_location(io, context)
16
- pretty_location(io, context)
15
+ def generate_location(io, **options)
16
+ pretty_location(io, options)
17
17
  end
18
18
 
19
- def pretty_location(io, name: nil, record: nil, version: nil, identifier: nil, metadata: {}, **)
19
+ def pretty_location(io, name: nil, record: nil, version: nil, derivative: nil, identifier: nil, metadata: {}, **)
20
20
  if record
21
21
  namespace = record_namespace(record)
22
22
  identifier ||= record_identifier(record)
23
23
  end
24
24
 
25
25
  basename = basic_location(io, metadata: metadata)
26
- basename = "#{version}-#{basename}" if version
26
+ basename = [*(version || derivative), basename].join("-")
27
27
 
28
28
  [*namespace, *identifier, *name, basename].join("/")
29
29
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ Shrine.deprecation("The processing plugin is deprecated and will be removed in Shrine 4. If you were using it with versions plugin, use the new derivatives plugin instead.")
4
+
3
5
  class Shrine
4
6
  module Plugins
5
7
  # Documentation lives in [doc/plugins/processing.md] on GitHub.
@@ -13,19 +15,32 @@ class Shrine
13
15
  module ClassMethods
14
16
  def process(action, &block)
15
17
  opts[:processing][action] ||= []
16
- opts[:processing][action] += [block]
18
+ opts[:processing][action] << block
17
19
  end
18
20
  end
19
21
 
20
22
  module InstanceMethods
21
- def process(io, context = {})
22
- pipeline = opts[:processing][context[:action]] || []
23
+ def upload(io, process: true, **options)
24
+ if process
25
+ input = process(io, **options)
26
+ else
27
+ input = io
28
+ end
29
+
30
+ super(input, **options)
31
+ end
32
+
33
+ private
23
34
 
24
- result = pipeline.inject(io) do |input, processing|
25
- instance_exec(input, context, &processing) || input
35
+ def process(io, **options)
36
+ pipeline = processing_pipeline(options[:action])
37
+ pipeline.inject(io) do |input, processor|
38
+ instance_exec(input, options, &processor) || input
26
39
  end
40
+ end
27
41
 
28
- result unless result == io
42
+ def processing_pipeline(key)
43
+ opts[:processing][key] || []
29
44
  end
30
45
  end
31
46
  end
@@ -23,46 +23,12 @@ class Shrine
23
23
  end
24
24
  end
25
25
 
26
- module InstanceMethods
27
- # If `io` is a Rack uploaded file hash, converts it to an IO-like
28
- # object and calls `super`.
29
- def upload(io, context = {})
30
- super(convert_rack_file(io), context)
31
- end
32
-
33
- # If `io` is a Rack uploaded file hash, converts it to an IO-like
34
- # object and calls `super`.
35
- def store(io, context = {})
36
- super(convert_rack_file(io), context)
37
- end
38
-
39
- private
40
-
41
- # If given a Rack uploaded file hash, returns a
42
- # `Shrine::Plugins::RackFile::UploadedFile` IO-like object wrapping that
43
- # hash, otherwise returns the value unchanged.
44
- def convert_rack_file(value)
45
- if rack_file?(value)
46
- Shrine.deprecation("Passing a Rack uploaded file hash to Shrine#upload is deprecated, use Shrine.rack_file to convert the Rack file hash into an IO object.")
47
- self.class.rack_file(value)
48
- else
49
- value
50
- end
51
- end
52
-
53
- # Returns whether a given value is a Rack uploaded file hash, by
54
- # checking whether it's a hash with `:tempfile` and `:name` keys.
55
- def rack_file?(value)
56
- value.is_a?(Hash) && value.key?(:tempfile) && value.key?(:name)
57
- end
58
- end
59
-
60
26
  module AttacherMethods
61
27
  # Checks whether a file is a Rack file hash, and in that case wraps the
62
28
  # hash in an IO-like object.
63
29
  def assign(value, **options)
64
30
  if rack_file?(value)
65
- assign(shrine_class.rack_file(value), **options)
31
+ assign shrine_class.rack_file(value), **options
66
32
  else
67
33
  super
68
34
  end
@@ -101,8 +67,4 @@ class Shrine
101
67
  extend Forwardable
102
68
  delegate [:read, :size, :rewind, :eof?, :close] => :@tempfile
103
69
  end
104
-
105
- # backwards compatibility
106
- Plugins::RackFile.const_set(:UploadedFile, RackFile)
107
- Plugins::RackFile.deprecate_constant(:UploadedFile)
108
70
  end
@@ -101,16 +101,23 @@ class Shrine
101
101
  FileBody.new(file, range: range)
102
102
  end
103
103
 
104
- # Parses the value of a "Range" HTTP header.
104
+ # Retrieves a range value parsed from HTTP "Range" header.
105
105
  def parse_content_range(range_header)
106
- if Rack.release >= "2.0"
107
- ranges = Rack::Utils.get_byte_ranges(range_header, file.size)
108
- else
109
- ranges = Rack::Utils.byte_ranges({ "HTTP_RANGE" => range_header }, file.size)
110
- end
111
-
106
+ ranges = get_byte_ranges(range_header)
112
107
  ranges.first if ranges && ranges.one?
113
108
  end
109
+
110
+ if Rack.release >= "2.0"
111
+ def get_byte_ranges(range_header)
112
+ Rack::Utils.get_byte_ranges(range_header, file.size)
113
+ end
114
+ else
115
+ # :nocov:
116
+ def get_byte_ranges(range_header)
117
+ Rack::Utils.byte_ranges({ "HTTP_RANGE" => range_header }, file.size)
118
+ end
119
+ # :nocov:
120
+ end
114
121
  end
115
122
 
116
123
  # Implements the interface of a Rack response body object.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ Shrine.deprecation("The recache plugin is deprecated and will be removed in Shrine 4. If you were using it with versions plugin, use the new derivatives plugin instead.")
4
+
3
5
  class Shrine
4
6
  module Plugins
5
7
  # Documentation lives in [doc/plugins/recache.md] on GitHub.
@@ -14,8 +16,9 @@ class Shrine
14
16
 
15
17
  def recache
16
18
  if cached?
17
- recached = cache!(get, action: :recache)
18
- _set(recached)
19
+ result = upload(file, cache_key, action: :recache)
20
+
21
+ set(result)
19
22
  end
20
23
  end
21
24
  end
@@ -6,16 +6,20 @@ class Shrine
6
6
  #
7
7
  # [doc/plugins/refresh_metadata.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/refresh_metadata.md
8
8
  module RefreshMetadata
9
+ module AttacherMethods
10
+ def refresh_metadata!(**options)
11
+ file.refresh_metadata!(**context, **options)
12
+ set(file)
13
+ end
14
+ end
15
+
9
16
  module FileMethods
10
- def refresh_metadata!(**context)
11
- refreshed_metadata =
12
- if opened?
13
- uploader.send(:get_metadata, self, metadata: true, **context)
14
- else
15
- open { uploader.send(:get_metadata, self, metadata: true, **context) }
16
- end
17
+ def refresh_metadata!(**options)
18
+ return open { refresh_metadata!(**options) } unless opened?
19
+
20
+ refreshed_metadata = uploader.send(:get_metadata, self, metadata: true, **options)
17
21
 
18
- @data = @data.merge("metadata" => metadata.merge(refreshed_metadata))
22
+ @metadata = @metadata.merge(refreshed_metadata)
19
23
  end
20
24
  end
21
25
  end
@@ -8,6 +8,8 @@ class Shrine
8
8
  #
9
9
  # [doc/plugins/remote_url.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remote_url.md
10
10
  module RemoteUrl
11
+ class DownloadError < Error; end
12
+
11
13
  LOG_SUBSCRIBER = -> (event) do
12
14
  Shrine.logger.info "Remote URL (#{event.duration}ms) – #{{
13
15
  remote_url: event[:remote_url],
@@ -16,8 +18,20 @@ class Shrine
16
18
  }.inspect}"
17
19
  end
18
20
 
19
- def self.configure(uploader, opts = {})
20
- uploader.opts[:remote_url] ||= { downloader: Down.method(:download), log_subscriber: LOG_SUBSCRIBER }
21
+ DOWNLOADER = -> (url, options) do
22
+ begin
23
+ Down.download(url, options)
24
+ rescue Down::Error => error
25
+ raise DownloadError, error.message
26
+ end
27
+ end
28
+
29
+ def self.load_dependencies(uploader, *)
30
+ uploader.plugin :validation
31
+ end
32
+
33
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
34
+ uploader.opts[:remote_url] ||= { downloader: DOWNLOADER }
21
35
  uploader.opts[:remote_url].merge!(opts)
22
36
 
23
37
  unless uploader.opts[:remote_url].key?(:max_size)
@@ -25,8 +39,24 @@ class Shrine
25
39
  end
26
40
 
27
41
  # instrumentation plugin integration
28
- if uploader.respond_to?(:subscribe)
29
- uploader.subscribe(:remote_url, &uploader.opts[:remote_url][:log_subscriber])
42
+ uploader.subscribe(:remote_url, &log_subscriber) if uploader.respond_to?(:subscribe)
43
+ end
44
+
45
+ module AttachmentMethods
46
+ def included(klass)
47
+ super
48
+
49
+ return unless options[:type] == :model
50
+
51
+ name = attachment_name
52
+
53
+ define_method :"#{name}_remote_url=" do |url|
54
+ send(:"#{name}_attacher").assign_remote_url(url)
55
+ end
56
+
57
+ define_method :"#{name}_remote_url" do
58
+ # form builders require the reader method
59
+ end
30
60
  end
31
61
  end
32
62
 
@@ -52,20 +82,6 @@ class Shrine
52
82
  end
53
83
  end
54
84
 
55
- module AttachmentMethods
56
- def initialize(name, **options)
57
- super
58
-
59
- define_method :"#{name}_remote_url=" do |url|
60
- send(:"#{name}_attacher").remote_url = url
61
- end
62
-
63
- define_method :"#{name}_remote_url" do
64
- send(:"#{name}_attacher").remote_url
65
- end
66
- end
67
- end
68
-
69
85
  module AttacherMethods
70
86
  # Downloads the remote file and assigns it. If download failed, sets
71
87
  # the error message and assigns the url to an instance variable so that
@@ -73,45 +89,20 @@ class Shrine
73
89
  def assign_remote_url(url, downloader: {}, **options)
74
90
  return if url == "" || url.nil?
75
91
 
76
- begin
77
- downloaded_file = shrine_class.remote_url(url, downloader)
78
- rescue => error
79
- download_error = error
80
- end
81
-
82
- if downloaded_file
83
- assign(downloaded_file, **options)
84
- else
85
- message = download_error_message(url, download_error)
86
- errors.replace [message]
87
- @remote_url = url
88
- end
89
- end
90
-
91
- # Alias for #assign_remote_url.
92
- def remote_url=(url)
93
- assign_remote_url(url)
94
- end
95
-
96
- # Form builders require the reader as well.
97
- def remote_url
98
- @remote_url
92
+ downloaded_file = shrine_class.remote_url(url, downloader)
93
+ attach_cached(downloaded_file, **options)
94
+ rescue DownloadError => error
95
+ errors.clear << remote_url_error_message(url, error)
96
+ false
99
97
  end
100
98
 
101
99
  private
102
100
 
103
- def download_error_message(url, error)
104
- if message = shrine_class.opts[:remote_url][:error_message]
105
- if message.respond_to?(:call)
106
- args = [url, error].take(message.arity.abs)
107
- message = message.call(*args)
108
- end
109
- else
110
- message = "download failed"
111
- message = "#{message}: #{error.message}" if error
112
- end
113
-
114
- message
101
+ # Generates an error message for failed remote URL download.
102
+ def remote_url_error_message(url, error)
103
+ message = shrine_class.opts[:remote_url][:error_message]
104
+ message = message.call *[url, error].take(message.arity.abs) if message.respond_to?(:call)
105
+ message || "download failed: #{error.message}"
115
106
  end
116
107
  end
117
108
  end
@@ -7,9 +7,13 @@ class Shrine
7
7
  # [doc/plugins/remove_attachment.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remove_attachment.md
8
8
  module RemoveAttachment
9
9
  module AttachmentMethods
10
- def initialize(name, **options)
10
+ def included(klass)
11
11
  super
12
12
 
13
+ return unless options[:type] == :model
14
+
15
+ name = attachment_name
16
+
13
17
  define_method :"remove_#{name}=" do |value|
14
18
  send(:"#{name}_attacher").remove = value
15
19
  end
@@ -24,7 +28,8 @@ class Shrine
24
28
  # We remove the attachment if the value evaluates to true.
25
29
  def remove=(value)
26
30
  @remove = value
27
- assign(nil) if remove?
31
+
32
+ change(nil) if remove?
28
33
  end
29
34
 
30
35
  def remove
@@ -6,14 +6,18 @@ class Shrine
6
6
  #
7
7
  # [doc/plugins/remove_invalid.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remove_invalid.md
8
8
  module RemoveInvalid
9
+ def self.load_dependencies(uploader)
10
+ uploader.plugin :validation
11
+ end
12
+
9
13
  module AttacherMethods
10
- def validate
14
+ def validate(*)
11
15
  super
12
16
  ensure
13
17
  if errors.any? && changed?
14
- _delete(get, action: :validate)
15
- _set(@old)
16
- remove_instance_variable(:@old)
18
+ destroy(background: true)
19
+ set @previous.file
20
+ remove_instance_variable(:@previous)
17
21
  end
18
22
  end
19
23
  end
@@ -6,16 +6,24 @@ class Shrine
6
6
  #
7
7
  # [doc/plugins/restore_cached_data.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/restore_cached_data.md
8
8
  module RestoreCachedData
9
- def self.load_dependencies(uploader, *)
9
+ def self.load_dependencies(uploader)
10
10
  uploader.plugin :refresh_metadata
11
11
  end
12
12
 
13
13
  module AttacherMethods
14
14
  private
15
15
 
16
- def assign_cached(cached_file)
17
- uploaded_file(cached_file) { |file| file.refresh_metadata!(context) }
18
- super(cached_file)
16
+ def cached(value, **options)
17
+ cached_file = super
18
+
19
+ # TODO: Remove this conditional when we remove the versions plugin
20
+ if cached_file.is_a?(Hash) || cached_file.is_a?(Array)
21
+ uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **options) }
22
+ else
23
+ cached_file.refresh_metadata!(**context, **options)
24
+ end
25
+
26
+ cached_file
19
27
  end
20
28
  end
21
29
  end