shrine 2.8.0 → 2.9.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.

Potentially problematic release.


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

Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +681 -0
  3. data/README.md +73 -21
  4. data/doc/carrierwave.md +75 -20
  5. data/doc/creating_storages.md +15 -26
  6. data/doc/direct_s3.md +113 -31
  7. data/doc/multiple_files.md +4 -8
  8. data/doc/paperclip.md +98 -31
  9. data/doc/refile.md +4 -6
  10. data/doc/testing.md +24 -21
  11. data/lib/shrine.rb +32 -20
  12. data/lib/shrine/plugins/activerecord.rb +2 -0
  13. data/lib/shrine/plugins/add_metadata.rb +2 -0
  14. data/lib/shrine/plugins/background_helpers.rb +2 -0
  15. data/lib/shrine/plugins/backgrounding.rb +11 -4
  16. data/lib/shrine/plugins/backup.rb +2 -0
  17. data/lib/shrine/plugins/cached_attachment_data.rb +2 -0
  18. data/lib/shrine/plugins/copy.rb +2 -0
  19. data/lib/shrine/plugins/data_uri.rb +20 -12
  20. data/lib/shrine/plugins/default_storage.rb +2 -0
  21. data/lib/shrine/plugins/default_url.rb +2 -0
  22. data/lib/shrine/plugins/default_url_options.rb +2 -0
  23. data/lib/shrine/plugins/delete_promoted.rb +2 -0
  24. data/lib/shrine/plugins/delete_raw.rb +2 -0
  25. data/lib/shrine/plugins/determine_mime_type.rb +18 -2
  26. data/lib/shrine/plugins/direct_upload.rb +6 -6
  27. data/lib/shrine/plugins/download_endpoint.rb +2 -0
  28. data/lib/shrine/plugins/dynamic_storage.rb +2 -0
  29. data/lib/shrine/plugins/hooks.rb +2 -0
  30. data/lib/shrine/plugins/included.rb +2 -0
  31. data/lib/shrine/plugins/infer_extension.rb +131 -0
  32. data/lib/shrine/plugins/keep_files.rb +2 -0
  33. data/lib/shrine/plugins/logging.rb +6 -4
  34. data/lib/shrine/plugins/metadata_attributes.rb +2 -0
  35. data/lib/shrine/plugins/migration_helpers.rb +2 -0
  36. data/lib/shrine/plugins/module_include.rb +2 -0
  37. data/lib/shrine/plugins/moving.rb +2 -0
  38. data/lib/shrine/plugins/multi_delete.rb +4 -0
  39. data/lib/shrine/plugins/parallelize.rb +2 -0
  40. data/lib/shrine/plugins/parsed_json.rb +2 -0
  41. data/lib/shrine/plugins/presign_endpoint.rb +7 -7
  42. data/lib/shrine/plugins/pretty_location.rb +2 -0
  43. data/lib/shrine/plugins/processing.rb +2 -0
  44. data/lib/shrine/plugins/rack_file.rb +2 -0
  45. data/lib/shrine/plugins/rack_response.rb +2 -0
  46. data/lib/shrine/plugins/recache.rb +2 -0
  47. data/lib/shrine/plugins/refresh_metadata.rb +2 -0
  48. data/lib/shrine/plugins/remote_url.rb +12 -1
  49. data/lib/shrine/plugins/remove_attachment.rb +2 -0
  50. data/lib/shrine/plugins/remove_invalid.rb +2 -0
  51. data/lib/shrine/plugins/restore_cached_data.rb +2 -0
  52. data/lib/shrine/plugins/sequel.rb +2 -0
  53. data/lib/shrine/plugins/signature.rb +10 -8
  54. data/lib/shrine/plugins/store_dimensions.rb +5 -3
  55. data/lib/shrine/plugins/upload_endpoint.rb +7 -8
  56. data/lib/shrine/plugins/upload_options.rb +2 -0
  57. data/lib/shrine/plugins/validation_helpers.rb +2 -0
  58. data/lib/shrine/plugins/versions.rb +72 -31
  59. data/lib/shrine/storage/file_system.rb +11 -4
  60. data/lib/shrine/storage/linter.rb +5 -13
  61. data/lib/shrine/storage/s3.rb +16 -13
  62. data/lib/shrine/version.rb +3 -1
  63. data/shrine.gemspec +7 -6
  64. metadata +26 -10
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `backup` plugin allows you to automatically back up stored files to
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `cached_attachment_data` plugin adds the ability to retain the cached
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `copy` plugin allows copying attachment from one record to another.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "base64"
2
4
  require "strscan"
3
5
  require "cgi"
@@ -32,6 +34,18 @@ class Shrine
32
34
  # plugin :data_uri, error_message: "data URI was invalid"
33
35
  # plugin :data_uri, error_message: ->(uri) { I18n.t("errors.data_uri_invalid") }
34
36
  #
37
+ # ## File extension
38
+ #
39
+ # A data URI doesn't convey any information about the file extension, so
40
+ # when attaching from a data URI, the uploaded file location will be
41
+ # missing an extension. If you want the upload location to always have an
42
+ # extension, you can load the `infer_extension` plugin to infer it from the
43
+ # MIME type.
44
+ #
45
+ # plugin :infer_extension
46
+ #
47
+ # ## `Shrine.data_uri`
48
+ #
35
49
  # If you just want to parse the data URI and create an IO object from it,
36
50
  # you can do that with `Shrine.data_uri`. If the data URI cannot be parsed,
37
51
  # a `Shrine::Plugins::DataUri::ParseError` will be raised.
@@ -50,19 +64,11 @@ class Shrine
50
64
  # io.size #=> 11
51
65
  # io.read #=> "raw content"
52
66
  #
53
- # The created IO object won't convey any file extension (because it doesn't
54
- # have a filename), but you can generate a filename based on the content
55
- # type of the data URI:
56
- #
57
- # require "mime/types"
67
+ # ## `UploadedFile#data_uri` and `UploadedFile#base64`
58
68
  #
59
- # plugin :data_uri, filename: ->(content_type) do
60
- # extension = MIME::Types[content_type].first.preferred_extension
61
- # "data_uri.#{extension}"
62
- # end
63
- #
64
- # This plugin also adds a `UploadedFile#data_uri` method (and `#base64`),
65
- # which returns a base64-encoded data URI of any UploadedFile:
69
+ # This plugin also adds UploadedFile#data_uri method, which returns a
70
+ # base64-encoded data URI of the file content, and UploadedFile#base64,
71
+ # which simply returns the file content base64-encoded.
66
72
  #
67
73
  # uploaded_file.data_uri #=> "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA"
68
74
  # uploaded_file.base64 #=> "iVBORw0KGgoAAAANSUhEUgAAAAUA"
@@ -81,6 +87,8 @@ class Shrine
81
87
  def self.configure(uploader, opts = {})
82
88
  uploader.opts[:data_uri_filename] = opts.fetch(:filename, uploader.opts[:data_uri_filename])
83
89
  uploader.opts[:data_uri_error_message] = opts.fetch(:error_message, uploader.opts[:data_uri_error_message])
90
+
91
+ Shrine.deprecation("The :filename option is deprecated for the data_uri plugin, and will be removed in Shrine 3. Use the infer_extension plugin instead.") if opts[:filename]
84
92
  end
85
93
 
86
94
  module ClassMethods
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `default_storage` plugin enables you to change which storages are going
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `default_url` plugin allows setting the URL which will be returned when
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `default_url_options` plugin allows you to specify URL options that
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `delete_promoted` plugin deletes files that have been promoted, after
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `delete_raw` plugin will automatically delete raw files that have been
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `determine_mime_type` plugin allows you to determine and store the
@@ -28,6 +30,13 @@ class Shrine
28
30
  # Unlike ruby-filemagic, mimemagic is a pure-ruby solution, so it will
29
31
  # work across all Ruby implementations.
30
32
  #
33
+ # :marcel
34
+ # : Uses the [marcel] gem to determine the MIME type from file contents.
35
+ # Marcel is Basecamp's wrapper around mimemagic, it adds priority logic
36
+ # (preferring magic over name when given both), some extra type
37
+ # definitions, and common type subclasses (including Keynote, Pages,
38
+ # etc).
39
+ #
31
40
  # :mime_types
32
41
  # : Uses the [mime-types] gem to determine the MIME type from the file
33
42
  # extension. Note that unlike other solutions, this analyzer is not
@@ -49,7 +58,7 @@ class Shrine
49
58
  # correctly determine MIME type of .css, .js, .json, .csv, .xml, or similar
50
59
  # text-based files, you can combine `file` and `mime_types` analyzers:
51
60
  #
52
- # plugin :determine_mime_type, analyzer: ->(io, analyzers) do
61
+ # plugin :determine_mime_type, analyzer: -> (io, analyzers) do
53
62
  # mime_type = analyzers[:file].call(io)
54
63
  # mime_type = analyzers[:mime_types].call(io) if mime_type == "text/plain"
55
64
  # mime_type
@@ -69,6 +78,7 @@ class Shrine
69
78
  # [Windows equivalent]: http://gnuwin32.sourceforge.net/packages/file.htm
70
79
  # [ruby-filemagic]: https://github.com/blackwinter/ruby-filemagic
71
80
  # [mimemagic]: https://github.com/minad/mimemagic
81
+ # [marcel]: https://github.com/basecamp/marcel
72
82
  # [mime-types]: https://github.com/mime-types/ruby-mime-types
73
83
  # [mini_mime]: https://github.com/discourse/mini_mime
74
84
  module DetermineMimeType
@@ -125,7 +135,7 @@ class Shrine
125
135
  end
126
136
 
127
137
  class MimeTypeAnalyzer
128
- SUPPORTED_TOOLS = [:file, :filemagic, :mimemagic, :mime_types, :mini_mime]
138
+ SUPPORTED_TOOLS = [:file, :filemagic, :mimemagic, :marcel, :mime_types, :mini_mime]
129
139
  MAGIC_NUMBER = 256 * 1024
130
140
 
131
141
  def initialize(tool)
@@ -181,6 +191,12 @@ class Shrine
181
191
  mime.type if mime
182
192
  end
183
193
 
194
+ def extract_with_marcel(io)
195
+ require "marcel"
196
+
197
+ Marcel::MimeType.for(io)
198
+ end
199
+
184
200
  def extract_with_mime_types(io)
185
201
  require "mime/types"
186
202
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "roda"
2
4
  require "json"
3
5
 
@@ -28,9 +30,9 @@ class Shrine
28
30
  # end
29
31
  #
30
32
  # Now your application will get `POST /images/cache/upload` and `GET
31
- # /images/cache/presign` routes. Whether you upload files to your app or to
32
- # to a 3rd-party service, you'll probably want to use a JavaScript file
33
- # upload library like [jQuery-File-Upload], [Dropzone] or [FineUploader].
33
+ # /images/cache/presign` routes. On the client side it is recommended to
34
+ # use [Uppy] for uploading files to the app or directly to the 3rd-party
35
+ # service.
34
36
  #
35
37
  # ## Uploads
36
38
  #
@@ -155,9 +157,7 @@ class Shrine
155
157
  # configuration.
156
158
  #
157
159
  # [Roda]: https://github.com/jeremyevans/roda
158
- # [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
159
- # [Dropzone]: https://github.com/enyo/dropzone
160
- # [FineUploader]: https://github.com/FineUploader/fine-uploader
160
+ # [Uppy]: https://uppy.io
161
161
  # [Roda request]: http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Base/RequestMethods.html
162
162
  # [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
163
163
  module DirectUpload
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "roda"
2
4
 
3
5
  require "base64"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `dynamic_storage` plugin allows you to register a storage using a
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `hooks` plugin allows you to trigger some code around
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `included` plugin allows you to hook up to the `.included` hook of the
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # The `infer_extension` plugin allows deducing the appropriate file
6
+ # extension for the upload location based on the MIME type of the file.
7
+ # This is useful when using `data_uri` and `remote_url` plugins, where the
8
+ # file extension is not or might not be known.
9
+ #
10
+ # plugin :infer_extension
11
+ #
12
+ # The upload location will gain the inferred extension only if couldn't be
13
+ # determined from the filename. By default `Rack::Mime` will be used for
14
+ # inferring the extension, but you can also choose a different inferrer:
15
+ #
16
+ # plugin :infer_extension, inferrer: :mime_types
17
+ #
18
+ # The following inferrers are accepted:
19
+ #
20
+ # :rack_mime
21
+ # : (Default). Uses the `Rack::Mime` module to infer the appropriate
22
+ # extension from MIME type.
23
+ #
24
+ # :mime_types
25
+ # : Uses the [mime-types] gem to infer the appropriate extension from MIME
26
+ # type.
27
+ #
28
+ # :mini_mime
29
+ # : Uses the [mini_mime] gem to infer the appropriate extension from MIME
30
+ # type.
31
+ #
32
+ # You can also define your own inferrer, with the possibility to call the
33
+ # built-in inferrers:
34
+ #
35
+ # plugin :infer_extension, inferrer: -> (mime_type, inferrers) do
36
+ # # don't add extension if the file is a text file
37
+ # inferrrs[:rack_mime].call(mime_type) unless mime_type == "text/plain"
38
+ # end
39
+ #
40
+ # You can also use methods for inferring extension directly:
41
+ #
42
+ # Shrine.infer_extension("image/jpeg")
43
+ # # => ".jpeg"
44
+ #
45
+ # Shrine.extension_inferrers[:mime_types].call("image/jpeg")
46
+ # # => ".jpeg"
47
+ #
48
+ # [mime-types]: https://github.com/mime-types/ruby-mime-types
49
+ # [mini_mime]: https://github.com/discourse/mini_mime
50
+ module InferExtension
51
+ def self.configure(uploader, opts = {})
52
+ uploader.opts[:extension_inferrer] = opts.fetch(:inferrer, uploader.opts.fetch(:infer_extension_inferrer, :rack_mime))
53
+ end
54
+
55
+ module ClassMethods
56
+ def infer_extension(mime_type)
57
+ inferrer = opts[:extension_inferrer]
58
+ inferrer = extension_inferrers[inferrer] if inferrer.is_a?(Symbol)
59
+ args = [mime_type, extension_inferrers].take(inferrer.arity.abs)
60
+
61
+ inferrer.call(*args)
62
+ end
63
+
64
+ def extension_inferrers
65
+ @extension_inferrers ||= ExtensionInferrer::SUPPORTED_TOOLS.inject({}) do |hash, tool|
66
+ hash.merge!(tool => ExtensionInferrer.new(tool).method(:call))
67
+ end
68
+ end
69
+ end
70
+
71
+ module InstanceMethods
72
+ def generate_location(io, context = {})
73
+ mime_type = (context[:metadata] || {})["mime_type"]
74
+
75
+ location = super
76
+ location += infer_extension(mime_type) if File.extname(location).empty?
77
+ location
78
+ end
79
+
80
+ private
81
+
82
+ def infer_extension(mime_type)
83
+ self.class.infer_extension(mime_type).to_s
84
+ end
85
+ end
86
+
87
+ class ExtensionInferrer
88
+ SUPPORTED_TOOLS = [:rack_mime, :mime_types, :mini_mime]
89
+
90
+ def initialize(tool)
91
+ raise ArgumentError, "unsupported extension inferrer tool: #{tool}" unless SUPPORTED_TOOLS.include?(tool)
92
+
93
+ @tool = tool
94
+ end
95
+
96
+ def call(mime_type)
97
+ return nil if mime_type.nil?
98
+
99
+ extension = send(:"infer_with_#{@tool}", mime_type)
100
+ extension = ".#{extension}" unless extension.nil? || extension.start_with?(".")
101
+ extension
102
+ end
103
+
104
+ private
105
+
106
+ def infer_with_rack_mime(mime_type)
107
+ require "rack/mime"
108
+
109
+ mime_types = Rack::Mime::MIME_TYPES
110
+ mime_types.key(mime_type)
111
+ end
112
+
113
+ def infer_with_mime_types(mime_type)
114
+ require "mime/types"
115
+
116
+ mime_type = MIME::Types[mime_type].first
117
+ mime_type.preferred_extension if mime_type
118
+ end
119
+
120
+ def infer_with_mini_mime(mime_type)
121
+ require "mini_mime"
122
+
123
+ info = MiniMime.lookup_by_content_type(mime_type)
124
+ info.extension if info
125
+ end
126
+ end
127
+ end
128
+
129
+ register_plugin(:infer_extension, InferExtension)
130
+ end
131
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `keep_files` plugin gives you the ability to prevent files from being
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "logger"
2
4
  require "json"
3
5
  require "time"
@@ -74,7 +76,7 @@ class Shrine
74
76
  # doesn't output timestamps if on Heroku.
75
77
  def pretty_formatter
76
78
  proc do |severity, time, program_name, message|
77
- output = "#{Process.pid}: #{message}\n"
79
+ output = "#{Process.pid}: #{message}\n".dup
78
80
  output.prepend "#{time.utc.iso8601(3)} " unless ENV["DYNO"]
79
81
  output
80
82
  end
@@ -123,11 +125,11 @@ class Shrine
123
125
  def _log_message_human(data)
124
126
  components = []
125
127
  components << "#{data[:action].upcase}"
126
- components.last << "[#{data[:phase]}]" if data[:phase]
128
+ components[-1] += "[#{data[:phase]}]" if data[:phase]
127
129
  components << "#{data[:uploader]}"
128
- components.last << "[:#{data[:attachment]}]" if data[:attachment]
130
+ components[-1] += "[:#{data[:attachment]}]" if data[:attachment]
129
131
  components << "#{data[:record_class]}" if data[:record_class]
130
- components.last << "[#{data[:record_id]}]" if data[:record_id]
132
+ components[-1] += "[#{data[:record_id]}]" if data[:record_id]
131
133
  components << "#{Array(data[:files]).join("-")} #{"file#{"s" if Array(data[:files]).any?{|n| n > 1}}"}"
132
134
  components << "(#{data[:duration]}s)"
133
135
  components.join(" ")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `metadata_attributes` plugin allows you to sync attachment metadata
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Shrine.deprecation("The migration_helpers plugin is deprecated and will be removed in Shrine 3. Attacher#cached? and Attacher#stored? have been moved to base.")
2
4
 
3
5
  class Shrine
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `module_include` plugin allows you to extend Shrine's core classes for
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `moving` plugin will *move* files to storages instead of copying them,
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ Shrine.deprecation("The multi_delete plugin is deprecated and will be removed in Shrine 3.")
4
+
1
5
  class Shrine
2
6
  module Plugins
3
7
  # The `multi_delete` plugins allows you to leverage your storage's multi
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thread"
2
4
 
3
5
  class Shrine
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `parsed_json` plugin is suitable for the case when your framework is
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack"
2
4
 
3
5
  require "json"
@@ -6,10 +8,10 @@ class Shrine
6
8
  module Plugins
7
9
  # The `presign_endpoint` plugin provides a Rack endpoint which generates
8
10
  # the URL, fields, and headers that can be used to upload files directly to
9
- # a storage service. It can be used with client-side file upload libraries
10
- # like [FineUploader], [Dropzone] or [jQuery-File-Upload] for asynchronous
11
- # uploads. Storage services that support direct uploads include [Amazon
12
- # S3], [Google Cloud Storage], [Microsoft Azure Storage] and more.
11
+ # a storage service. On the client side it's recommended to use [Uppy] for
12
+ # asynchronous uploads. Storage services that support direct uploads
13
+ # include [Amazon S3], [Google Cloud Storage], [Microsoft Azure Storage]
14
+ # and more.
13
15
  #
14
16
  # plugin :presign_endpoint
15
17
  #
@@ -112,9 +114,7 @@ class Shrine
112
114
  #
113
115
  # Shrine.presign_endpoint(:cache, presign_location: "${filename}")
114
116
  #
115
- # [FineUploader]: https://github.com/FineUploader/fine-uploader
116
- # [Dropzone]: https://github.com/enyo/dropzone
117
- # [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
117
+ # [Uppy]: https://uppy.io
118
118
  # [Amazon S3]: https://aws.amazon.com/s3/
119
119
  # [Google Cloud Storage]: https://cloud.google.com/storage/
120
120
  # [Microsoft Azure Storage]: https://azure.microsoft.com/en-us/services/storage/