shrine 2.19.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +485 -43
  3. data/LICENSE.txt +1 -1
  4. data/README.md +81 -977
  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 +102 -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 +1115 -0
  20. data/doc/metadata.md +190 -109
  21. data/doc/multiple_files.md +62 -34
  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 +162 -101
  34. data/doc/plugins/derivatives.md +829 -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 +14 -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 +185 -167
  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 +4 -0
  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/retrieving_uploads.md +4 -1
  116. data/doc/securing_uploads.md +60 -37
  117. data/doc/storage/file_system.md +20 -3
  118. data/doc/storage/memory.md +19 -0
  119. data/doc/storage/s3.md +117 -83
  120. data/doc/testing.md +124 -144
  121. data/doc/upgrading_to_3.md +710 -0
  122. data/doc/validation.md +54 -90
  123. data/lib/shrine/attacher.rb +287 -171
  124. data/lib/shrine/attachment.rb +13 -46
  125. data/lib/shrine/plugins/_persistence.rb +93 -0
  126. data/lib/shrine/plugins/activerecord.rb +77 -34
  127. data/lib/shrine/plugins/add_metadata.rb +25 -17
  128. data/lib/shrine/plugins/atomic_helpers.rb +119 -0
  129. data/lib/shrine/plugins/backgrounding.rb +77 -113
  130. data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
  131. data/lib/shrine/plugins/column.rb +102 -0
  132. data/lib/shrine/plugins/data_uri.rb +38 -36
  133. data/lib/shrine/plugins/default_storage.rb +45 -15
  134. data/lib/shrine/plugins/default_url.rb +12 -24
  135. data/lib/shrine/plugins/default_url_options.rb +3 -30
  136. data/lib/shrine/plugins/delete_raw.rb +10 -16
  137. data/lib/shrine/plugins/derivation_endpoint.rb +89 -134
  138. data/lib/shrine/plugins/derivatives.rb +637 -0
  139. data/lib/shrine/plugins/determine_mime_type.rb +9 -21
  140. data/lib/shrine/plugins/download_endpoint.rb +109 -133
  141. data/lib/shrine/plugins/dynamic_storage.rb +5 -11
  142. data/lib/shrine/plugins/entity.rb +152 -0
  143. data/lib/shrine/plugins/form_assign.rb +108 -0
  144. data/lib/shrine/plugins/included.rb +6 -6
  145. data/lib/shrine/plugins/infer_extension.rb +13 -20
  146. data/lib/shrine/plugins/instrumentation.rb +54 -42
  147. data/lib/shrine/plugins/keep_files.rb +3 -15
  148. data/lib/shrine/plugins/metadata_attributes.rb +28 -19
  149. data/lib/shrine/plugins/mirroring.rb +142 -0
  150. data/lib/shrine/plugins/model.rb +158 -0
  151. data/lib/shrine/plugins/module_include.rb +3 -3
  152. data/lib/shrine/plugins/multi_cache.rb +27 -0
  153. data/lib/shrine/plugins/presign_endpoint.rb +18 -22
  154. data/lib/shrine/plugins/pretty_location.rb +15 -9
  155. data/lib/shrine/plugins/processing.rb +22 -9
  156. data/lib/shrine/plugins/rack_file.rb +2 -42
  157. data/lib/shrine/plugins/rack_response.rb +15 -10
  158. data/lib/shrine/plugins/recache.rb +6 -5
  159. data/lib/shrine/plugins/refresh_metadata.rb +13 -11
  160. data/lib/shrine/plugins/remote_url.rb +49 -49
  161. data/lib/shrine/plugins/remove_attachment.rb +10 -6
  162. data/lib/shrine/plugins/remove_invalid.rb +19 -8
  163. data/lib/shrine/plugins/restore_cached_data.rb +13 -7
  164. data/lib/shrine/plugins/sequel.rb +86 -36
  165. data/lib/shrine/plugins/signature.rb +10 -16
  166. data/lib/shrine/plugins/store_dimensions.rb +35 -40
  167. data/lib/shrine/plugins/tempfile.rb +1 -3
  168. data/lib/shrine/plugins/type_predicates.rb +113 -0
  169. data/lib/shrine/plugins/upload_endpoint.rb +25 -23
  170. data/lib/shrine/plugins/upload_options.rb +14 -15
  171. data/lib/shrine/plugins/url_options.rb +31 -0
  172. data/lib/shrine/plugins/validation.rb +80 -0
  173. data/lib/shrine/plugins/validation_helpers.rb +34 -57
  174. data/lib/shrine/plugins/versions.rb +107 -87
  175. data/lib/shrine/plugins.rb +22 -0
  176. data/lib/shrine/storage/file_system.rb +46 -64
  177. data/lib/shrine/storage/linter.rb +42 -7
  178. data/lib/shrine/storage/memory.rb +49 -0
  179. data/lib/shrine/storage/s3.rb +154 -158
  180. data/lib/shrine/uploaded_file.rb +28 -30
  181. data/lib/shrine/version.rb +3 -3
  182. data/lib/shrine.rb +86 -149
  183. data/shrine.gemspec +9 -10
  184. metadata +79 -83
  185. data/doc/migrating_storage.md +0 -76
  186. data/doc/plugins/backup.md +0 -31
  187. data/doc/plugins/copy.md +0 -24
  188. data/doc/plugins/delete_promoted.md +0 -12
  189. data/doc/plugins/direct_upload.md +0 -172
  190. data/doc/plugins/hooks.md +0 -58
  191. data/doc/plugins/logging.md +0 -42
  192. data/doc/plugins/migration_helpers.md +0 -60
  193. data/doc/plugins/moving.md +0 -19
  194. data/doc/plugins/multi_delete.md +0 -20
  195. data/doc/plugins/parallelize.md +0 -16
  196. data/doc/plugins/parsed_json.md +0 -23
  197. data/doc/regenerating_versions.md +0 -143
  198. data/lib/shrine/plugins/background_helpers.rb +0 -5
  199. data/lib/shrine/plugins/backup.rb +0 -90
  200. data/lib/shrine/plugins/copy.rb +0 -50
  201. data/lib/shrine/plugins/delete_promoted.rb +0 -20
  202. data/lib/shrine/plugins/direct_upload.rb +0 -217
  203. data/lib/shrine/plugins/hooks.rb +0 -90
  204. data/lib/shrine/plugins/logging.rb +0 -142
  205. data/lib/shrine/plugins/migration_helpers.rb +0 -70
  206. data/lib/shrine/plugins/moving.rb +0 -57
  207. data/lib/shrine/plugins/multi_delete.rb +0 -32
  208. data/lib/shrine/plugins/parallelize.rb +0 -78
  209. data/lib/shrine/plugins/parsed_json.rb +0 -29
@@ -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,11 +135,10 @@ 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
@@ -159,11 +147,10 @@ class Shrine
159
147
  #
160
148
  # validate_max_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)
@@ -1,113 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ Shrine.deprecation("The versions plugin is deprecated and will be removed in Shrine 4. Use the new derivatives plugin instead.")
4
+
3
5
  class Shrine
4
6
  module Plugins
5
- # Documentation lives in [doc/plugins/versions.md] on GitHub.
6
- #
7
- # [doc/plugins/versions.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/versions.md
7
+ # Documentation can be found on https://shrinerb.com/docs/plugins/versions
8
8
  module Versions
9
- def self.load_dependencies(uploader, *)
9
+ def self.load_dependencies(uploader, **)
10
+ uploader.plugin :processing
10
11
  uploader.plugin :default_url
11
12
  end
12
13
 
13
- def self.configure(uploader, opts = {})
14
- Shrine.deprecation("The versions Shrine plugin doesn't need the :names option anymore, you can safely remove it.") if opts.key?(:names)
15
-
16
- uploader.opts[:version_names] = opts.fetch(:names, uploader.opts[:version_names])
17
- uploader.opts[:version_fallbacks] = opts.fetch(:fallbacks, uploader.opts.fetch(:version_fallbacks, {}))
18
- uploader.opts[:versions_fallback_to_original] = opts.fetch(:fallback_to_original, uploader.opts.fetch(:versions_fallback_to_original, true))
14
+ def self.configure(uploader, **opts)
15
+ uploader.opts[:versions] ||= { fallbacks: {}, fallback_to_original: true }
16
+ uploader.opts[:versions].merge!(opts)
19
17
  end
20
18
 
21
19
  module ClassMethods
22
- def version_names
23
- Shrine.deprecation("Shrine.version_names is deprecated and will be removed in Shrine 3.")
24
- opts[:version_names]
25
- end
26
-
27
20
  def version_fallbacks
28
- opts[:version_fallbacks]
29
- end
30
-
31
- # Checks that the identifier is a registered version.
32
- def version?(name)
33
- Shrine.deprecation("Shrine.version? is deprecated and will be removed in Shrine 3.")
34
- version_names.nil? || version_names.map(&:to_s).include?(name.to_s)
21
+ opts[:versions][:fallbacks]
35
22
  end
36
23
 
37
24
  # Converts a hash of data into a hash of versions.
38
- def uploaded_file(object, &block)
39
- if object.is_a?(Hash) && object.values.none? { |value| value.is_a?(String) }
40
- object.inject({}) do |result, (name, value)|
41
- result.merge!(name.to_sym => uploaded_file(value, &block))
25
+ def uploaded_file(object)
26
+ object = JSON.parse(object) if object.is_a?(String)
27
+
28
+ Utils.deep_map(object, transform_keys: :to_sym) do |path, value|
29
+ if value.is_a?(Hash) && (value["id"].is_a?(String) || value[:id].is_a?(String))
30
+ file = super(value)
31
+ elsif value.is_a?(UploadedFile)
32
+ file = value
33
+ end
34
+
35
+ if file
36
+ yield file if block_given?
37
+ file
42
38
  end
43
- elsif object.is_a?(Array)
44
- object.map { |value| uploaded_file(value, &block) }
45
- else
46
- super
47
39
  end
48
40
  end
49
41
  end
50
42
 
51
43
  module InstanceMethods
52
- # Checks whether all versions are uploaded by this uploader.
53
- def uploaded?(object)
54
- if object.is_a?(Hash)
55
- object.all? { |name, version| uploaded?(version) }
56
- elsif object.is_a?(Array)
57
- object.all? { |version| uploaded?(version) }
58
- else
59
- super
60
- end
61
- end
44
+ def upload(io, **options)
45
+ files = process(io, **options) || io
62
46
 
63
- private
64
-
65
- # Stores each version individually. It asserts that all versions are
66
- # known, because later the versions will be silently filtered, so
67
- # we want to let the user know that they forgot to register a new
68
- # version.
69
- def _store(io, context)
70
- if (hash = io).is_a?(Hash)
71
- raise Error, ":location is not applicable to versions" if context.key?(:location)
72
- raise Error, "detected multiple versions that point to the same IO object: given versions: #{hash.keys}, unique versions: #{hash.invert.invert.keys}" if hash.invert.invert != hash
73
-
74
- hash.inject({}) do |result, (name, value)|
75
- result.merge!(name.to_sym => _store(value, context.merge(version: name.to_sym){|_, v1, v2| Array(v1) + Array(v2)}))
76
- end
77
- elsif (array = io).is_a?(Array)
78
- array.map.with_index { |value, idx| _store(value, context.merge(version: idx){|_, v1, v2| Array(v1) + Array(v2)}) }
79
- else
80
- super
81
- end
82
- end
47
+ Utils.map_file(files) do |name, version|
48
+ options.merge!(version: name.one? ? name.first : name) if name
83
49
 
84
- # Deletes each file individually
85
- def _delete(uploaded_file, context)
86
- if (hash = uploaded_file).is_a?(Hash)
87
- hash.each do |name, value|
88
- _delete(value, context)
89
- end
90
- elsif (array = uploaded_file).is_a?(Array)
91
- array.each do |value|
92
- _delete(value, context)
93
- end
94
- else
95
- super
50
+ super(version, **options, process: false)
96
51
  end
97
52
  end
98
53
  end
99
54
 
100
55
  module AttacherMethods
56
+ def destroy(*)
57
+ Utils.each_file(self.file) { |_, file| file.delete }
58
+ end
59
+
101
60
  # Smart versioned URLs, which include the version name in the default
102
61
  # URL, and properly forwards any options to the underlying storage.
103
62
  def url(version = nil, **options)
104
- attachment = get
105
-
106
- if attachment.is_a?(Hash)
63
+ if file.is_a?(Hash)
107
64
  if version
108
65
  version = version.to_sym
109
- if attachment.key?(version)
110
- attachment[version].url(**options)
66
+ if file.key?(version)
67
+ file[version].url(**options)
111
68
  elsif fallback = shrine_class.version_fallbacks[version]
112
69
  url(fallback, **options)
113
70
  else
@@ -118,8 +75,8 @@ class Shrine
118
75
  end
119
76
  else
120
77
  if version
121
- if attachment && fallback_to_original?
122
- attachment.url(**options)
78
+ if file && shrine_class.opts[:versions][:fallback_to_original]
79
+ file.url(**options)
123
80
  else
124
81
  default_url(**options, version: version)
125
82
  end
@@ -129,22 +86,85 @@ class Shrine
129
86
  end
130
87
  end
131
88
 
89
+ # Converts the Hash/Array of UploadedFile objects into a Hash/Array of data.
90
+ def data
91
+ Utils.map_file(file, transform_keys: :to_s) do |_, version|
92
+ version.data
93
+ end
94
+ end
95
+
96
+ def file=(file)
97
+ if file.is_a?(Hash) || file.is_a?(Array)
98
+ @file = file
99
+ else
100
+ super
101
+ end
102
+ end
103
+
104
+ def uploaded_file(value, &block)
105
+ shrine_class.uploaded_file(value, &block)
106
+ end
107
+
132
108
  private
133
109
 
134
- def fallback_to_original?
135
- shrine_class.opts[:versions_fallback_to_original]
110
+ def uploaded?(file, storage_key)
111
+ if file.is_a?(Hash) || file.is_a?(Array)
112
+ Utils.each_file(file).all? { |_, f| f.storage_key == storage_key }
113
+ else
114
+ super
115
+ end
136
116
  end
117
+ end
137
118
 
138
- # Converts the Hash/Array of UploadedFile objects into a Hash/Array of data.
139
- def convert_to_data(object)
119
+ module Utils
120
+ module_function
121
+
122
+ def each_file(object)
123
+ return enum_for(__method__, object) unless block_given?
124
+
125
+ map_file(object) do |path, file|
126
+ yield path, file
127
+ file
128
+ end
129
+ end
130
+
131
+ def map_file(object, transform_keys: :to_sym)
132
+ if object.is_a?(Hash) || object.is_a?(Array)
133
+ deep_map(object, transform_keys: transform_keys) do |path, value|
134
+ yield path, value unless value.is_a?(Hash) || value.is_a?(Array)
135
+ end
136
+ elsif object
137
+ yield nil, object
138
+ else
139
+ object
140
+ end
141
+ end
142
+
143
+ def deep_map(object, path = [], transform_keys:, &block)
140
144
  if object.is_a?(Hash)
141
- object.inject({}) do |hash, (name, value)|
142
- hash.merge!(name => convert_to_data(value))
145
+ result = yield path, object
146
+
147
+ return result if result
148
+
149
+ object.inject({}) do |hash, (key, value)|
150
+ key = key.send(transform_keys)
151
+ result = yield [*path, key], value
152
+
153
+ hash.merge! key => (result || deep_map(value, [*path, key], transform_keys: transform_keys, &block))
143
154
  end
144
155
  elsif object.is_a?(Array)
145
- object.map { |value| convert_to_data(value) }
156
+ result = yield path, object
157
+
158
+ return result if result
159
+
160
+ object.map.with_index do |value, idx|
161
+ result = yield [*path, idx], value
162
+
163
+ result || deep_map(value, [*path, idx], transform_keys: transform_keys, &block)
164
+ end
146
165
  else
147
- super
166
+ result = yield path, object
167
+ result or fail Shrine::Error, "leaf reached"
148
168
  end
149
169
  end
150
170
  end
@@ -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
  #