shrine 2.19.3 → 3.6.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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +83 -979
  5. data/doc/advantages.md +231 -204
  6. data/doc/attacher.md +304 -153
  7. data/doc/carrierwave.md +297 -226
  8. data/doc/changing_derivatives.md +308 -0
  9. data/doc/changing_location.md +103 -21
  10. data/doc/changing_storage.md +110 -0
  11. data/doc/creating_persistence_plugins.md +132 -0
  12. data/doc/creating_plugins.md +43 -23
  13. data/doc/creating_storages.md +19 -5
  14. data/doc/design.md +147 -97
  15. data/doc/direct_s3.md +38 -28
  16. data/doc/external/articles.md +63 -0
  17. data/doc/external/extensions.md +53 -0
  18. data/doc/external/misc.md +32 -0
  19. data/doc/getting_started.md +1156 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +93 -30
  22. data/doc/paperclip.md +384 -262
  23. data/doc/plugins/activerecord.md +177 -46
  24. data/doc/plugins/add_metadata.md +139 -38
  25. data/doc/plugins/atomic_helpers.md +217 -0
  26. data/doc/plugins/backgrounding.md +156 -98
  27. data/doc/plugins/cached_attachment_data.md +7 -5
  28. data/doc/plugins/column.md +121 -0
  29. data/doc/plugins/data_uri.md +23 -22
  30. data/doc/plugins/default_storage.md +36 -10
  31. data/doc/plugins/default_url.md +30 -13
  32. data/doc/plugins/delete_raw.md +4 -2
  33. data/doc/plugins/derivation_endpoint.md +186 -101
  34. data/doc/plugins/derivatives.md +839 -0
  35. data/doc/plugins/determine_mime_type.md +4 -2
  36. data/doc/plugins/download_endpoint.md +64 -8
  37. data/doc/plugins/dynamic_storage.md +5 -3
  38. data/doc/plugins/entity.md +263 -0
  39. data/doc/plugins/form_assign.md +55 -0
  40. data/doc/plugins/included.md +31 -8
  41. data/doc/plugins/infer_extension.md +21 -10
  42. data/doc/plugins/instrumentation.md +38 -16
  43. data/doc/plugins/keep_files.md +16 -17
  44. data/doc/plugins/metadata_attributes.md +42 -13
  45. data/doc/plugins/mirroring.md +118 -0
  46. data/doc/plugins/model.md +210 -0
  47. data/doc/plugins/module_include.md +4 -2
  48. data/doc/plugins/multi_cache.md +24 -0
  49. data/doc/plugins/persistence.md +101 -0
  50. data/doc/plugins/presign_endpoint.md +9 -4
  51. data/doc/plugins/pretty_location.md +16 -3
  52. data/doc/plugins/processing.md +4 -2
  53. data/doc/plugins/rack_file.md +8 -2
  54. data/doc/plugins/rack_response.md +6 -2
  55. data/doc/plugins/recache.md +4 -2
  56. data/doc/plugins/refresh_metadata.md +49 -9
  57. data/doc/plugins/remote_url.md +84 -47
  58. data/doc/plugins/remove_attachment.md +27 -6
  59. data/doc/plugins/remove_invalid.md +21 -6
  60. data/doc/plugins/restore_cached_data.md +11 -3
  61. data/doc/plugins/sequel.md +159 -35
  62. data/doc/plugins/signature.md +16 -5
  63. data/doc/plugins/store_dimensions.md +14 -2
  64. data/doc/plugins/tempfile.md +4 -2
  65. data/doc/plugins/type_predicates.md +96 -0
  66. data/doc/plugins/upload_endpoint.md +13 -13
  67. data/doc/plugins/upload_options.md +6 -4
  68. data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
  69. data/doc/plugins/validation.md +97 -0
  70. data/doc/plugins/validation_helpers.md +16 -13
  71. data/doc/plugins/versions.md +15 -19
  72. data/doc/processing.md +438 -221
  73. data/doc/refile.md +188 -170
  74. data/doc/release_notes/1.0.0.md +4 -0
  75. data/doc/release_notes/1.1.0.md +6 -2
  76. data/doc/release_notes/1.2.0.md +4 -0
  77. data/doc/release_notes/1.3.0.md +4 -0
  78. data/doc/release_notes/1.4.0.md +4 -0
  79. data/doc/release_notes/1.4.1.md +4 -0
  80. data/doc/release_notes/1.4.2.md +4 -0
  81. data/doc/release_notes/2.0.0.md +4 -0
  82. data/doc/release_notes/2.0.1.md +4 -0
  83. data/doc/release_notes/2.1.0.md +5 -1
  84. data/doc/release_notes/2.1.1.md +4 -0
  85. data/doc/release_notes/2.10.0.md +4 -0
  86. data/doc/release_notes/2.10.1.md +4 -0
  87. data/doc/release_notes/2.11.0.md +4 -0
  88. data/doc/release_notes/2.12.0.md +4 -0
  89. data/doc/release_notes/2.13.0.md +4 -0
  90. data/doc/release_notes/2.14.0.md +5 -1
  91. data/doc/release_notes/2.15.0.md +11 -7
  92. data/doc/release_notes/2.16.0.md +4 -0
  93. data/doc/release_notes/2.17.0.md +4 -0
  94. data/doc/release_notes/2.18.0.md +4 -0
  95. data/doc/release_notes/2.19.0.md +6 -3
  96. data/doc/release_notes/2.2.0.md +4 -0
  97. data/doc/release_notes/2.3.0.md +4 -0
  98. data/doc/release_notes/2.3.1.md +4 -0
  99. data/doc/release_notes/2.4.0.md +4 -0
  100. data/doc/release_notes/2.4.1.md +4 -0
  101. data/doc/release_notes/2.5.0.md +4 -0
  102. data/doc/release_notes/2.6.0.md +4 -0
  103. data/doc/release_notes/2.6.1.md +4 -0
  104. data/doc/release_notes/2.7.0.md +4 -0
  105. data/doc/release_notes/2.8.0.md +4 -0
  106. data/doc/release_notes/2.9.0.md +4 -0
  107. data/doc/release_notes/3.0.0.md +981 -0
  108. data/doc/release_notes/3.0.1.md +22 -0
  109. data/doc/release_notes/3.1.0.md +73 -0
  110. data/doc/release_notes/3.2.0.md +96 -0
  111. data/doc/release_notes/3.2.1.md +31 -0
  112. data/doc/release_notes/3.2.2.md +14 -0
  113. data/doc/release_notes/3.3.0.md +105 -0
  114. data/doc/release_notes/3.4.0.md +35 -0
  115. data/doc/release_notes/3.5.0.md +63 -0
  116. data/doc/release_notes/3.6.0.md +23 -0
  117. data/doc/retrieving_uploads.md +5 -2
  118. data/doc/securing_uploads.md +60 -37
  119. data/doc/storage/file_system.md +20 -3
  120. data/doc/storage/memory.md +19 -0
  121. data/doc/storage/s3.md +122 -78
  122. data/doc/testing.md +141 -133
  123. data/doc/upgrading_to_3.md +708 -0
  124. data/doc/validation.md +54 -90
  125. data/lib/shrine/attacher.rb +292 -169
  126. data/lib/shrine/attachment.rb +13 -46
  127. data/lib/shrine/plugins/_persistence.rb +93 -0
  128. data/lib/shrine/plugins/activerecord.rb +77 -34
  129. data/lib/shrine/plugins/add_metadata.rb +25 -17
  130. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  131. data/lib/shrine/plugins/backgrounding.rb +77 -113
  132. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  133. data/lib/shrine/plugins/column.rb +102 -0
  134. data/lib/shrine/plugins/data_uri.rb +38 -36
  135. data/lib/shrine/plugins/default_storage.rb +45 -15
  136. data/lib/shrine/plugins/default_url.rb +12 -24
  137. data/lib/shrine/plugins/default_url_options.rb +3 -30
  138. data/lib/shrine/plugins/delete_raw.rb +10 -16
  139. data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
  140. data/lib/shrine/plugins/derivatives.rb +645 -0
  141. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  142. data/lib/shrine/plugins/download_endpoint.rb +118 -133
  143. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  144. data/lib/shrine/plugins/entity.rb +158 -0
  145. data/lib/shrine/plugins/form_assign.rb +108 -0
  146. data/lib/shrine/plugins/included.rb +6 -6
  147. data/lib/shrine/plugins/infer_extension.rb +17 -20
  148. data/lib/shrine/plugins/instrumentation.rb +59 -43
  149. data/lib/shrine/plugins/keep_files.rb +3 -15
  150. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  151. data/lib/shrine/plugins/mirroring.rb +142 -0
  152. data/lib/shrine/plugins/model.rb +160 -0
  153. data/lib/shrine/plugins/module_include.rb +3 -3
  154. data/lib/shrine/plugins/multi_cache.rb +27 -0
  155. data/lib/shrine/plugins/presign_endpoint.rb +27 -28
  156. data/lib/shrine/plugins/pretty_location.rb +15 -9
  157. data/lib/shrine/plugins/processing.rb +22 -9
  158. data/lib/shrine/plugins/rack_file.rb +2 -42
  159. data/lib/shrine/plugins/rack_response.rb +21 -10
  160. data/lib/shrine/plugins/recache.rb +6 -5
  161. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  162. data/lib/shrine/plugins/remote_url.rb +49 -49
  163. data/lib/shrine/plugins/remove_attachment.rb +12 -6
  164. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  165. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  166. data/lib/shrine/plugins/sequel.rb +86 -36
  167. data/lib/shrine/plugins/signature.rb +10 -16
  168. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  169. data/lib/shrine/plugins/tempfile.rb +1 -3
  170. data/lib/shrine/plugins/type_predicates.rb +113 -0
  171. data/lib/shrine/plugins/upload_endpoint.rb +28 -24
  172. data/lib/shrine/plugins/upload_options.rb +14 -15
  173. data/lib/shrine/plugins/url_options.rb +31 -0
  174. data/lib/shrine/plugins/validation.rb +80 -0
  175. data/lib/shrine/plugins/validation_helpers.rb +35 -58
  176. data/lib/shrine/plugins/versions.rb +107 -87
  177. data/lib/shrine/plugins.rb +22 -0
  178. data/lib/shrine/storage/file_system.rb +46 -64
  179. data/lib/shrine/storage/linter.rb +42 -7
  180. data/lib/shrine/storage/memory.rb +49 -0
  181. data/lib/shrine/storage/s3.rb +173 -160
  182. data/lib/shrine/uploaded_file.rb +32 -32
  183. data/lib/shrine/version.rb +3 -3
  184. data/lib/shrine.rb +87 -150
  185. data/shrine.gemspec +11 -12
  186. metadata +92 -82
  187. data/doc/migrating_storage.md +0 -76
  188. data/doc/plugins/backup.md +0 -31
  189. data/doc/plugins/copy.md +0 -24
  190. data/doc/plugins/delete_promoted.md +0 -12
  191. data/doc/plugins/direct_upload.md +0 -172
  192. data/doc/plugins/hooks.md +0 -58
  193. data/doc/plugins/logging.md +0 -42
  194. data/doc/plugins/migration_helpers.md +0 -60
  195. data/doc/plugins/moving.md +0 -19
  196. data/doc/plugins/multi_delete.md +0 -20
  197. data/doc/plugins/parallelize.md +0 -16
  198. data/doc/plugins/parsed_json.md +0 -23
  199. data/doc/regenerating_versions.md +0 -143
  200. data/lib/shrine/plugins/background_helpers.rb +0 -5
  201. data/lib/shrine/plugins/backup.rb +0 -90
  202. data/lib/shrine/plugins/copy.rb +0 -50
  203. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  204. data/lib/shrine/plugins/direct_upload.rb +0 -217
  205. data/lib/shrine/plugins/hooks.rb +0 -90
  206. data/lib/shrine/plugins/logging.rb +0 -142
  207. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  208. data/lib/shrine/plugins/moving.rb +0 -57
  209. data/lib/shrine/plugins/multi_delete.rb +0 -32
  210. data/lib/shrine/plugins/parallelize.rb +0 -78
  211. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/type_predicates
6
+ module TypePredicates
7
+ def self.configure(uploader, methods: [], **opts)
8
+ uploader.opts[:type_predicates] ||= { mime: :mini_mime }
9
+ uploader.opts[:type_predicates].merge!(opts)
10
+
11
+ methods.each do |name|
12
+ uploader::UploadedFile.send(:define_method, "#{name}?") { type?(name) }
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def type_lookup(extension, database = nil)
18
+ database ||= opts[:type_predicates][:mime]
19
+ database = MimeDatabase.new(database) if database.is_a?(Symbol)
20
+ database.call(extension.to_s)
21
+ end
22
+ end
23
+
24
+ module FileMethods
25
+ def image?
26
+ general_type?("image")
27
+ end
28
+
29
+ def video?
30
+ general_type?("video")
31
+ end
32
+
33
+ def audio?
34
+ general_type?("audio")
35
+ end
36
+
37
+ def text?
38
+ general_type?("text")
39
+ end
40
+
41
+ def type?(type)
42
+ matching_mime_type = shrine_class.type_lookup(type)
43
+
44
+ fail Error, "type #{type.inspect} is not recognized by the MIME library" unless matching_mime_type
45
+
46
+ mime_type! == matching_mime_type
47
+ end
48
+
49
+ private
50
+
51
+ def general_type?(type)
52
+ mime_type!.start_with?(type)
53
+ end
54
+
55
+ def mime_type!
56
+ mime_type or fail Error, "mime_type metadata value is missing"
57
+ end
58
+ end
59
+
60
+ class MimeDatabase
61
+ SUPPORTED_TOOLS = %i[mini_mime mime_types mimemagic marcel rack_mime]
62
+
63
+ def initialize(tool)
64
+ raise Error, "unknown type database #{tool.inspect}, supported databases are: #{SUPPORTED_TOOLS.join(",")}" unless SUPPORTED_TOOLS.include?(tool)
65
+
66
+ @tool = tool
67
+ end
68
+
69
+ def call(extension)
70
+ send(:"lookup_with_#{@tool}", extension)
71
+ end
72
+
73
+ private
74
+
75
+ def lookup_with_mini_mime(extension)
76
+ require "mini_mime"
77
+
78
+ info = MiniMime.lookup_by_extension(extension)
79
+ info&.content_type
80
+ end
81
+
82
+ def lookup_with_mime_types(extension)
83
+ require "mime/types"
84
+
85
+ mime_type = MIME::Types.of(".#{extension}").first
86
+ mime_type&.content_type
87
+ end
88
+
89
+ def lookup_with_mimemagic(extension)
90
+ require "mimemagic"
91
+
92
+ magic = MimeMagic.by_extension(".#{extension}")
93
+ magic&.type
94
+ end
95
+
96
+ def lookup_with_marcel(extension)
97
+ require "marcel"
98
+
99
+ type = Marcel::MimeType.for(extension: ".#{extension}")
100
+ type unless type == "application/octet-stream"
101
+ end
102
+
103
+ def lookup_with_rack_mime(extension)
104
+ require "rack/mime"
105
+
106
+ Rack::Mime.mime_type(".#{extension}", nil)
107
+ end
108
+ end
109
+ end
110
+
111
+ register_plugin(:type_predicates, TypePredicates)
112
+ end
113
+ end
@@ -7,15 +7,13 @@ require "digest"
7
7
 
8
8
  class Shrine
9
9
  module Plugins
10
- # Documentation lives in [doc/plugins/upload_endpoint.md] on GitHub.
11
- #
12
- # [doc/plugins/upload_endpoint.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/upload_endpoint.md
10
+ # Documentation can be found on https://shrinerb.com/docs/plugins/upload_endpoint
13
11
  module UploadEndpoint
14
- def self.load_dependencies(uploader, opts = {})
12
+ def self.load_dependencies(uploader, **)
15
13
  uploader.plugin :rack_file
16
14
  end
17
15
 
18
- def self.configure(uploader, opts = {})
16
+ def self.configure(uploader, **opts)
19
17
  uploader.opts[:upload_endpoint] ||= {}
20
18
  uploader.opts[:upload_endpoint].merge!(opts)
21
19
  end
@@ -93,7 +91,9 @@ class Shrine
93
91
  handle_request(request)
94
92
  end
95
93
 
96
- headers["Content-Length"] ||= body.map(&:bytesize).inject(0, :+).to_s
94
+ headers = Rack::Headers[headers] if Rack.release >= "3"
95
+ headers["Content-Length"] ||= body.respond_to?(:bytesize) ? body.bytesize.to_s :
96
+ body.map(&:bytesize).inject(0, :+).to_s
97
97
 
98
98
  [status, headers, body]
99
99
  end
@@ -137,16 +137,21 @@ class Shrine
137
137
  end
138
138
 
139
139
  error!(400, "Upload Not Found") if value.nil?
140
- error!(400, "Upload Not Valid") unless value.is_a?(Hash) && value[:tempfile]
141
140
 
142
- @shrine_class.rack_file(value)
141
+ if value.is_a?(Hash) && value[:tempfile]
142
+ @shrine_class.rack_file(value)
143
+ elsif %i[read rewind eof? close].all? { |m| value.respond_to?(m) }
144
+ value
145
+ else
146
+ error!(400, "Upload Not Valid")
147
+ end
143
148
  end
144
149
 
145
150
  # Returns a hash of information containing `:action` and `:request`
146
151
  # keys, which is to be passed to `Shrine#upload`. Calls
147
152
  # `:upload_context` option if given.
148
153
  def get_context(request)
149
- context = { action: :upload, phase: :upload, request: request }
154
+ context = { action: :upload }
150
155
  context.merge! @upload_context.call(request) if @upload_context
151
156
  context
152
157
  end
@@ -158,7 +163,7 @@ class Shrine
158
163
  if @upload
159
164
  @upload.call(io, context, request)
160
165
  else
161
- uploader.upload(io, context)
166
+ uploader.upload(io, **context)
162
167
  end
163
168
  end
164
169
 
@@ -166,26 +171,29 @@ class Shrine
166
171
  # a Rack response triple - an array consisting of a status number, hash
167
172
  # of headers, and a body enumerable. If a `:rack_response` option is
168
173
  # given, calls that instead.
169
- def make_response(object, request)
174
+ def make_response(uploaded_file, request)
170
175
  if @rack_response
171
- @rack_response.call(object, request)
176
+ @rack_response.call(uploaded_file, request)
172
177
  else
173
178
  if @url
174
- url = case @url
175
- when true then object.url
176
- when Hash then object.url(**@url)
177
- else @url.call(object, request)
178
- end
179
-
180
- body = { data: object, url: url }.to_json
179
+ url = resolve_url(uploaded_file, request)
180
+ body = { data: uploaded_file, url: url }.to_json
181
181
  else
182
- body = object.to_json
182
+ body = uploaded_file.to_json
183
183
  end
184
184
 
185
185
  [200, { "Content-Type" => CONTENT_TYPE_JSON }, [body]]
186
186
  end
187
187
  end
188
188
 
189
+ def resolve_url(uploaded_file, request)
190
+ case @url
191
+ when true then uploaded_file.url
192
+ when Hash then uploaded_file.url(**@url)
193
+ else @url.call(uploaded_file, request)
194
+ end
195
+ end
196
+
189
197
  def verify_size!(file, request)
190
198
  error!(413, "Upload Too Large") if @max_size && file.size > @max_size
191
199
  end
@@ -212,8 +220,4 @@ class Shrine
212
220
  @shrine_class.new(@storage_key)
213
221
  end
214
222
  end
215
-
216
- # backwards compatibility
217
- Plugins::UploadEndpoint.const_set(:App, UploadEndpoint)
218
- Plugins::UploadEndpoint.deprecate_constant(:App)
219
223
  end
@@ -2,28 +2,27 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/upload_options.md] on GitHub.
6
- #
7
- # [doc/plugins/upload_options.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/upload_options.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/upload_options
8
6
  module UploadOptions
9
- def self.configure(uploader, options = {})
7
+ def self.configure(uploader, **opts)
10
8
  uploader.opts[:upload_options] ||= {}
11
- uploader.opts[:upload_options].merge!(options)
9
+ uploader.opts[:upload_options].merge!(opts)
12
10
  end
13
11
 
14
12
  module InstanceMethods
15
- def put(io, context)
16
- upload_options = get_upload_options(io, context)
17
- context = { upload_options: upload_options }.merge(context)
18
- super
19
- end
20
-
21
13
  private
22
14
 
23
- def get_upload_options(io, context)
24
- options = opts[:upload_options][storage_key]
25
- options = options.call(io, context) if options.respond_to?(:call)
26
- options
15
+ def _upload(io, **options)
16
+ upload_options = get_upload_options(io, options)
17
+
18
+ super(io, **options, upload_options: upload_options)
19
+ end
20
+
21
+ def get_upload_options(io, options)
22
+ upload_options = opts[:upload_options][storage_key] || {}
23
+ upload_options = upload_options.call(io, options) if upload_options.respond_to?(:call)
24
+ upload_options = upload_options.merge(options[:upload_options]) if options[:upload_options]
25
+ upload_options
27
26
  end
28
27
  end
29
28
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/url_options
6
+ module UrlOptions
7
+ def self.configure(uploader, **opts)
8
+ uploader.opts[:url_options] ||= {}
9
+ uploader.opts[:url_options].merge!(opts)
10
+ end
11
+
12
+ module FileMethods
13
+ def url(**options)
14
+ default_options = url_options(options)
15
+
16
+ super(**default_options, **options)
17
+ end
18
+
19
+ private
20
+
21
+ def url_options(options)
22
+ default_options = shrine_class.opts[:url_options][storage_key]
23
+ default_options = default_options.call(self, options) if default_options.respond_to?(:call)
24
+ default_options || {}
25
+ end
26
+ end
27
+ end
28
+
29
+ register_plugin(:url_options, UrlOptions)
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/validation
6
+ module Validation
7
+ module AttacherClassMethods
8
+ # Block that is executed in context of Shrine::Attacher during
9
+ # validation. Example:
10
+ #
11
+ # Shrine::Attacher.validate do
12
+ # if file.size > 5*1024*1024
13
+ # errors << "is too big (max is 5 MB)"
14
+ # end
15
+ # end
16
+ def validate(&block)
17
+ private define_method(:validate_block, &block)
18
+ end
19
+ end
20
+
21
+ module AttacherMethods
22
+ # Returns an array of validation errors created on file assignment in
23
+ # the `Attacher.validate` block.
24
+ attr_reader :errors
25
+
26
+ # Initializes validation errors to an empty array.
27
+ def initialize(**options)
28
+ super
29
+ @errors = []
30
+ end
31
+
32
+ # Performs validations after attaching cached file.
33
+ def attach_cached(value, validate: nil, **options)
34
+ result = super(value, validate: false, **options)
35
+ validation(validate)
36
+ result
37
+ end
38
+
39
+ # Performs validations after attaching file.
40
+ def attach(io, validate: nil, **options)
41
+ result = super(io, **options)
42
+ validation(validate)
43
+ result
44
+ end
45
+
46
+ # Runs the validation defined by `Attacher.validate`.
47
+ def validate(**options)
48
+ errors.clear
49
+ _validate(**options) if attached?
50
+ end
51
+
52
+ private
53
+
54
+ # Calls validation appropriately based on the :validate value.
55
+ def validation(argument)
56
+ case argument
57
+ when Hash then validate(**argument)
58
+ when false then errors.clear # skip validation
59
+ else validate
60
+ end
61
+ end
62
+
63
+ # Calls #validate_block, passing it accepted parameters.
64
+ def _validate(**options)
65
+ if method(:validate_block).arity.zero?
66
+ validate_block
67
+ else
68
+ validate_block(**options)
69
+ end
70
+ end
71
+
72
+ # Overridden by the `Attacher.validate` block.
73
+ def validate_block(**options)
74
+ end
75
+ end
76
+ end
77
+
78
+ register_plugin(:validation, Validation)
79
+ end
80
+ end
@@ -2,15 +2,8 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # Documentation lives in [doc/plugins/validation_helpers.md] on GitHub.
6
- #
7
- # [doc/plugins/validation_helpers.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/validation_helpers.md
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/validation_helpers
8
6
  module ValidationHelpers
9
- def self.configure(uploader, opts = {})
10
- uploader.opts[:validation_default_messages] ||= {}
11
- uploader.opts[:validation_default_messages].merge!(opts[:default_messages] || {})
12
- end
13
-
14
7
  DEFAULT_MESSAGES = {
15
8
  max_size: -> (max) { "size must not be greater than #{PRETTY_FILESIZE.call(max)}" },
16
9
  min_size: -> (min) { "size must not be less than #{PRETTY_FILESIZE.call(min)}" },
@@ -24,7 +17,7 @@ class Shrine
24
17
  mime_type_exclusion: -> (list) { "type must not be one of: #{list.join(", ")}" },
25
18
  extension_inclusion: -> (list) { "extension must be one of: #{list.join(", ")}" },
26
19
  extension_exclusion: -> (list) { "extension must not be one of: #{list.join(", ")}" },
27
- }
20
+ }.freeze
28
21
 
29
22
  FILESIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"].freeze
30
23
 
@@ -39,10 +32,18 @@ class Shrine
39
32
  "%.1f %s" % [bytes.to_f / 1024 ** exp, FILESIZE_UNITS[exp]]
40
33
  end
41
34
 
35
+ def self.load_dependencies(uploader, *)
36
+ uploader.plugin :validation
37
+ end
38
+
39
+ def self.configure(uploader, default_messages: {}, **opts)
40
+ uploader.opts[:validation_helpers] ||= { default_messages: DEFAULT_MESSAGES.dup }
41
+ uploader.opts[:validation_helpers][:default_messages].merge!(default_messages)
42
+ end
43
+
42
44
  module AttacherClassMethods
43
45
  def default_validation_messages
44
- @default_validation_messages ||= DEFAULT_MESSAGES.merge(
45
- shrine_class.opts[:validation_default_messages])
46
+ shrine_class.opts[:validation_helpers][:default_messages]
46
47
  end
47
48
  end
48
49
 
@@ -51,14 +52,14 @@ class Shrine
51
52
  #
52
53
  # validate_max_size 5*1024*1024
53
54
  def validate_max_size(max, message: nil)
54
- validate_result(get.size <= max, :max_size, message, max)
55
+ validate_result(file.size <= max, :max_size, message, max)
55
56
  end
56
57
 
57
58
  # Validates that the `size` metadata is not smaller than `min`.
58
59
  #
59
60
  # validate_min_size 1024
60
61
  def validate_min_size(min, message: nil)
61
- validate_result(get.size >= min, :min_size, message, min)
62
+ validate_result(file.size >= min, :min_size, message, min)
62
63
  end
63
64
 
64
65
  # Validates that the `size` metadata is in the given range.
@@ -76,12 +77,9 @@ class Shrine
76
77
  #
77
78
  # validate_max_width 5000
78
79
  def validate_max_width(max, message: nil)
79
- fail Error, ":store_dimensions plugin is required" unless get.respond_to?(:width)
80
- if get.width
81
- validate_result(get.width <= max, :max_width, message, max)
82
- else
83
- Shrine.deprecation("Width of the uploaded file is nil, and Shrine skipped the validation. In Shrine 3 the validation will fail if width is nil.")
84
- end
80
+ fail Error, "width metadata is missing" unless file["width"]
81
+
82
+ validate_result(file["width"] <= max, :max_width, message, max)
85
83
  end
86
84
 
87
85
  # Validates that the `width` metadata is not smaller than `min`.
@@ -89,12 +87,9 @@ class Shrine
89
87
  #
90
88
  # validate_min_width 100
91
89
  def validate_min_width(min, message: nil)
92
- fail Error, ":store_dimensions plugin is required" unless get.respond_to?(:width)
93
- if get.width
94
- validate_result(get.width >= min, :min_width, message, min)
95
- else
96
- Shrine.deprecation("Width of the uploaded file is nil, and Shrine skipped the validation. In Shrine 3 the validation will fail if width is nil.")
97
- end
90
+ fail Error, "width metadata is missing" unless file["width"]
91
+
92
+ validate_result(file["width"] >= min, :min_width, message, min)
98
93
  end
99
94
 
100
95
  # Validates that the `width` metadata is in the given range.
@@ -112,12 +107,9 @@ class Shrine
112
107
  #
113
108
  # validate_max_height 5000
114
109
  def validate_max_height(max, message: nil)
115
- fail Error, ":store_dimensions plugin is required" unless get.respond_to?(:height)
116
- if get.height
117
- validate_result(get.height <= max, :max_height, message, max)
118
- else
119
- Shrine.deprecation("Height of the uploaded file is nil, and Shrine skipped the validation. In Shrine 3 the validation will fail if height is nil.")
120
- end
110
+ fail Error, "height metadata is missing" unless file["height"]
111
+
112
+ validate_result(file["height"] <= max, :max_height, message, max)
121
113
  end
122
114
 
123
115
  # Validates that the `height` metadata is not smaller than `min`.
@@ -125,12 +117,9 @@ class Shrine
125
117
  #
126
118
  # validate_min_height 100
127
119
  def validate_min_height(min, message: nil)
128
- fail Error, ":store_dimensions plugin is required" unless get.respond_to?(:height)
129
- if get.height
130
- validate_result(get.height >= min, :min_height, message, min)
131
- else
132
- Shrine.deprecation("Height of the uploaded file is nil, and Shrine skipped the validation. In Shrine 3 the validation will fail if height is nil.")
133
- end
120
+ fail Error, "height metadata is missing" unless file["height"]
121
+
122
+ validate_result(file["height"] >= min, :min_height, message, min)
134
123
  end
135
124
 
136
125
  # Validates that the `height` metadata is in the given range.
@@ -146,24 +135,22 @@ class Shrine
146
135
  #
147
136
  # validate_max_dimensions [5000, 5000]
148
137
  def validate_max_dimensions((max_width, max_height), message: nil)
149
- fail Error, ":store_dimensions plugin is required" unless get.respond_to?(:width) && get.respond_to?(:height)
150
- fail Error, "width or height metadata is nil" unless get.width && get.height
138
+ fail Error, "width and/or height metadata is missing" unless file["width"] && file["height"]
151
139
 
152
140
  validate_result(
153
- get.width <= max_width && get.height <= max_height,
141
+ file["width"] <= max_width && file["height"] <= max_height,
154
142
  :max_dimensions, message, [max_width, max_height]
155
143
  )
156
144
  end
157
145
 
158
146
  # Validates that the dimensions are not smaller than specified.
159
147
  #
160
- # validate_max_dimensions [100, 100]
148
+ # validate_min_dimensions [100, 100]
161
149
  def validate_min_dimensions((min_width, min_height), message: nil)
162
- fail Error, ":store_dimensions plugin is required" unless get.respond_to?(:width) && get.respond_to?(:height)
163
- fail Error, "width or height metadata is nil" unless get.width && get.height
150
+ fail Error, "width and/or height metadata is missing" unless file["width"] && file["height"]
164
151
 
165
152
  validate_result(
166
- get.width >= min_width && get.height >= min_height,
153
+ file["width"] >= min_width && file["height"] >= min_height,
167
154
  :min_dimensions, message, [min_width, min_height]
168
155
  )
169
156
  end
@@ -184,7 +171,7 @@ class Shrine
184
171
  # validate_mime_type_inclusion %w[audio/mp3 audio/flac]
185
172
  def validate_mime_type_inclusion(types, message: nil)
186
173
  validate_result(
187
- types.any? { |type| regex(type) =~ get.mime_type.to_s },
174
+ types.include?(file.mime_type),
188
175
  :mime_type_inclusion, message, types
189
176
  )
190
177
  end
@@ -196,7 +183,7 @@ class Shrine
196
183
  # validate_mime_type_exclusion %w[text/x-php]
197
184
  def validate_mime_type_exclusion(types, message: nil)
198
185
  validate_result(
199
- types.none? { |type| regex(type) =~ get.mime_type.to_s },
186
+ !types.include?(file.mime_type),
200
187
  :mime_type_exclusion, message, types
201
188
  )
202
189
  end
@@ -207,7 +194,7 @@ class Shrine
207
194
  # validate_extension_inclusion %w[jpg jpeg png gif]
208
195
  def validate_extension_inclusion(extensions, message: nil)
209
196
  validate_result(
210
- extensions.any? { |extension| regex(extension) =~ get.extension.to_s },
197
+ extensions.any? { |extension| extension.casecmp(file.extension.to_s) == 0 },
211
198
  :extension_inclusion, message, extensions
212
199
  )
213
200
  end
@@ -219,7 +206,7 @@ class Shrine
219
206
  # validate_extension_exclusion %[php jar]
220
207
  def validate_extension_exclusion(extensions, message: nil)
221
208
  validate_result(
222
- extensions.none? { |extension| regex(extension) =~ get.extension.to_s },
209
+ extensions.none? { |extension| extension.casecmp(file.extension.to_s) == 0 },
223
210
  :extension_exclusion, message, extensions
224
211
  )
225
212
  end
@@ -236,16 +223,6 @@ class Shrine
236
223
  end
237
224
  end
238
225
 
239
- # Converts a string to a regex.
240
- def regex(value)
241
- if value.is_a?(Regexp)
242
- Shrine.deprecation("Passing regexes to type/extension whitelists/blacklists in validation_helpers plugin is deprecated and will be removed in Shrine 3. Use strings instead.")
243
- value
244
- else
245
- /\A#{Regexp.escape(value)}\z/i
246
- end
247
- end
248
-
249
226
  # Generates an error message and appends it to errors array.
250
227
  def add_error(*args)
251
228
  errors << error_message(*args)