activestorage 5.2.6.3 → 6.0.0.beta1

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 activestorage might be problematic. Click here for more details.

Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -134
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +6 -5
  5. data/app/assets/javascripts/activestorage.js +4 -1
  6. data/app/controllers/active_storage/base_controller.rb +3 -5
  7. data/app/controllers/active_storage/blobs_controller.rb +1 -1
  8. data/app/controllers/active_storage/disk_controller.rb +4 -1
  9. data/app/controllers/active_storage/representations_controller.rb +1 -1
  10. data/app/controllers/concerns/active_storage/set_current.rb +15 -0
  11. data/app/javascript/activestorage/blob_record.js +6 -1
  12. data/app/jobs/active_storage/analyze_job.rb +4 -0
  13. data/app/jobs/active_storage/base_job.rb +0 -1
  14. data/app/jobs/active_storage/purge_job.rb +3 -0
  15. data/app/models/active_storage/attachment.rb +18 -9
  16. data/app/models/active_storage/blob/representable.rb +5 -5
  17. data/app/models/active_storage/blob.rb +63 -22
  18. data/app/models/active_storage/filename.rb +0 -6
  19. data/app/models/active_storage/preview.rb +3 -3
  20. data/app/models/active_storage/variant.rb +51 -52
  21. data/app/models/active_storage/variation.rb +23 -384
  22. data/config/routes.rb +13 -12
  23. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +7 -0
  24. data/lib/active_storage/analyzer/video_analyzer.rb +2 -4
  25. data/lib/active_storage/analyzer.rb +9 -4
  26. data/lib/active_storage/attached/changes/create_many.rb +46 -0
  27. data/lib/active_storage/attached/changes/create_one.rb +68 -0
  28. data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
  29. data/lib/active_storage/attached/changes/delete_many.rb +23 -0
  30. data/lib/active_storage/attached/changes/delete_one.rb +19 -0
  31. data/lib/active_storage/attached/changes.rb +16 -0
  32. data/lib/active_storage/attached/many.rb +16 -10
  33. data/lib/active_storage/attached/model.rb +140 -0
  34. data/lib/active_storage/attached/one.rb +16 -19
  35. data/lib/active_storage/attached.rb +7 -22
  36. data/lib/active_storage/downloader.rb +44 -0
  37. data/lib/active_storage/downloading.rb +8 -0
  38. data/lib/active_storage/engine.rb +36 -23
  39. data/lib/active_storage/errors.rb +22 -3
  40. data/lib/active_storage/gem_version.rb +4 -4
  41. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +3 -3
  42. data/lib/active_storage/previewer/video_previewer.rb +2 -3
  43. data/lib/active_storage/previewer.rb +21 -11
  44. data/lib/active_storage/reflection.rb +64 -0
  45. data/lib/active_storage/service/azure_storage_service.rb +30 -14
  46. data/lib/active_storage/service/configurator.rb +3 -1
  47. data/lib/active_storage/service/disk_service.rb +20 -16
  48. data/lib/active_storage/service/gcs_service.rb +48 -46
  49. data/lib/active_storage/service/mirror_service.rb +1 -1
  50. data/lib/active_storage/service/s3_service.rb +10 -9
  51. data/lib/active_storage/service.rb +5 -6
  52. data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
  53. data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
  54. data/lib/active_storage/transformers/transformer.rb +42 -0
  55. data/lib/active_storage.rb +13 -4
  56. data/lib/tasks/activestorage.rake +7 -0
  57. metadata +31 -19
  58. data/app/models/active_storage/filename/parameters.rb +0 -36
  59. data/lib/active_storage/attached/macros.rb +0 -110
@@ -6,315 +6,12 @@
6
6
  # In case you do need to use this directly, it's instantiated using a hash of transformations where
7
7
  # the key is the command and the value is the arguments. Example:
8
8
  #
9
- # ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
9
+ # ActiveStorage::Variation.new(resize_to_fit: [100, 100], monochrome: true, trim: true, rotate: "-90")
10
10
  #
11
- # You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
12
- #
13
- # ActiveStorage::Variation.new(combine_options: {
14
- # resize: "100x100^",
15
- # gravity: "center",
16
- # crop: "100x100+0+0",
17
- # })
18
- #
19
- # A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
11
+ # The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
20
12
  class ActiveStorage::Variation
21
13
  attr_reader :transformations
22
14
 
23
- class UnsupportedImageProcessingMethod < StandardError; end
24
- class UnsupportedImageProcessingArgument < StandardError; end
25
-
26
- SUPPORTED_IMAGE_PROCESSING_METHODS = [
27
- "adaptive_blur",
28
- "adaptive_resize",
29
- "adaptive_sharpen",
30
- "adjoin",
31
- "affine",
32
- "alpha",
33
- "annotate",
34
- "antialias",
35
- "append",
36
- "apply",
37
- "attenuate",
38
- "authenticate",
39
- "auto_gamma",
40
- "auto_level",
41
- "auto_orient",
42
- "auto_threshold",
43
- "backdrop",
44
- "background",
45
- "bench",
46
- "bias",
47
- "bilateral_blur",
48
- "black_point_compensation",
49
- "black_threshold",
50
- "blend",
51
- "blue_primary",
52
- "blue_shift",
53
- "blur",
54
- "border",
55
- "bordercolor",
56
- "borderwidth",
57
- "brightness_contrast",
58
- "cache",
59
- "canny",
60
- "caption",
61
- "channel",
62
- "channel_fx",
63
- "charcoal",
64
- "chop",
65
- "clahe",
66
- "clamp",
67
- "clip",
68
- "clip_path",
69
- "clone",
70
- "clut",
71
- "coalesce",
72
- "colorize",
73
- "colormap",
74
- "color_matrix",
75
- "colors",
76
- "colorspace",
77
- "colourspace",
78
- "color_threshold",
79
- "combine",
80
- "combine_options",
81
- "comment",
82
- "compare",
83
- "complex",
84
- "compose",
85
- "composite",
86
- "compress",
87
- "connected_components",
88
- "contrast",
89
- "contrast_stretch",
90
- "convert",
91
- "convolve",
92
- "copy",
93
- "crop",
94
- "cycle",
95
- "deconstruct",
96
- "define",
97
- "delay",
98
- "delete",
99
- "density",
100
- "depth",
101
- "descend",
102
- "deskew",
103
- "despeckle",
104
- "direction",
105
- "displace",
106
- "dispose",
107
- "dissimilarity_threshold",
108
- "dissolve",
109
- "distort",
110
- "dither",
111
- "draw",
112
- "duplicate",
113
- "edge",
114
- "emboss",
115
- "encoding",
116
- "endian",
117
- "enhance",
118
- "equalize",
119
- "evaluate",
120
- "evaluate_sequence",
121
- "extent",
122
- "extract",
123
- "family",
124
- "features",
125
- "fft",
126
- "fill",
127
- "filter",
128
- "flatten",
129
- "flip",
130
- "floodfill",
131
- "flop",
132
- "font",
133
- "foreground",
134
- "format",
135
- "frame",
136
- "function",
137
- "fuzz",
138
- "fx",
139
- "gamma",
140
- "gaussian_blur",
141
- "geometry",
142
- "gravity",
143
- "grayscale",
144
- "green_primary",
145
- "hald_clut",
146
- "highlight_color",
147
- "hough_lines",
148
- "iconGeometry",
149
- "iconic",
150
- "identify",
151
- "ift",
152
- "illuminant",
153
- "immutable",
154
- "implode",
155
- "insert",
156
- "intensity",
157
- "intent",
158
- "interlace",
159
- "interline_spacing",
160
- "interpolate",
161
- "interpolative_resize",
162
- "interword_spacing",
163
- "kerning",
164
- "kmeans",
165
- "kuwahara",
166
- "label",
167
- "lat",
168
- "layers",
169
- "level",
170
- "level_colors",
171
- "limit",
172
- "limits",
173
- "linear_stretch",
174
- "linewidth",
175
- "liquid_rescale",
176
- "list",
177
- "loader",
178
- "log",
179
- "loop",
180
- "lowlight_color",
181
- "magnify",
182
- "map",
183
- "mattecolor",
184
- "median",
185
- "mean_shift",
186
- "metric",
187
- "mode",
188
- "modulate",
189
- "moments",
190
- "monitor",
191
- "monochrome",
192
- "morph",
193
- "morphology",
194
- "mosaic",
195
- "motion_blur",
196
- "name",
197
- "negate",
198
- "noise",
199
- "normalize",
200
- "opaque",
201
- "ordered_dither",
202
- "orient",
203
- "page",
204
- "paint",
205
- "pause",
206
- "perceptible",
207
- "ping",
208
- "pointsize",
209
- "polaroid",
210
- "poly",
211
- "posterize",
212
- "precision",
213
- "preview",
214
- "process",
215
- "quality",
216
- "quantize",
217
- "quiet",
218
- "radial_blur",
219
- "raise",
220
- "random_threshold",
221
- "range_threshold",
222
- "red_primary",
223
- "regard_warnings",
224
- "region",
225
- "remote",
226
- "render",
227
- "repage",
228
- "resample",
229
- "resize",
230
- "resize_to_fill",
231
- "resize_to_fit",
232
- "resize_to_limit",
233
- "resize_and_pad",
234
- "respect_parentheses",
235
- "reverse",
236
- "roll",
237
- "rotate",
238
- "sample",
239
- "sampling_factor",
240
- "saver",
241
- "scale",
242
- "scene",
243
- "screen",
244
- "seed",
245
- "segment",
246
- "selective_blur",
247
- "separate",
248
- "sepia_tone",
249
- "shade",
250
- "shadow",
251
- "shared_memory",
252
- "sharpen",
253
- "shave",
254
- "shear",
255
- "sigmoidal_contrast",
256
- "silent",
257
- "similarity_threshold",
258
- "size",
259
- "sketch",
260
- "smush",
261
- "snaps",
262
- "solarize",
263
- "sort_pixels",
264
- "sparse_color",
265
- "splice",
266
- "spread",
267
- "statistic",
268
- "stegano",
269
- "stereo",
270
- "storage_type",
271
- "stretch",
272
- "strip",
273
- "stroke",
274
- "strokewidth",
275
- "style",
276
- "subimage_search",
277
- "swap",
278
- "swirl",
279
- "synchronize",
280
- "taint",
281
- "text_font",
282
- "threshold",
283
- "thumbnail",
284
- "tile_offset",
285
- "tint",
286
- "title",
287
- "transform",
288
- "transparent",
289
- "transparent_color",
290
- "transpose",
291
- "transverse",
292
- "treedepth",
293
- "trim",
294
- "type",
295
- "undercolor",
296
- "unique_colors",
297
- "units",
298
- "unsharp",
299
- "update",
300
- "valid_image",
301
- "view",
302
- "vignette",
303
- "virtual_pixel",
304
- "visual",
305
- "watermark",
306
- "wave",
307
- "wavelet_denoise",
308
- "weight",
309
- "white_balance",
310
- "white_point",
311
- "white_threshold",
312
- "window",
313
- "window_group",
314
- ].concat(ActiveStorage.supported_image_processing_methods)
315
-
316
- UNSUPPORTED_IMAGE_PROCESSING_ARGUMENTS = ActiveStorage.unsupported_image_processing_arguments
317
-
318
15
  class << self
319
16
  # Returns a Variation instance based on the given variator. If the variator is a Variation, it is
320
17
  # returned unmodified. If it is a String, it is passed to ActiveStorage::Variation.decode. Otherwise,
@@ -346,24 +43,13 @@ class ActiveStorage::Variation
346
43
  @transformations = transformations
347
44
  end
348
45
 
349
- # Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
350
- # and performs the +transformations+ against it. The transformed image instance is then returned.
351
- def transform(image)
46
+ # Accepts a File object, performs the +transformations+ against it, and
47
+ # saves the transformed image into a temporary file. If +format+ is specified
48
+ # it will be the format of the result image, otherwise the result image
49
+ # retains the source format.
50
+ def transform(file, format: nil, &block)
352
51
  ActiveSupport::Notifications.instrument("transform.active_storage") do
353
- transformations.each do |name, argument_or_subtransformations|
354
- validate_transformation(name, argument_or_subtransformations)
355
- image.mogrify do |command|
356
- if name.to_s == "combine_options"
357
- argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
358
- validate_transformation(subtransformation_name, subtransformation_argument)
359
- pass_transform_argument(command, subtransformation_name, subtransformation_argument)
360
- end
361
- else
362
- validate_transformation(name, argument_or_subtransformations)
363
- pass_transform_argument(command, name, argument_or_subtransformations)
364
- end
365
- end
366
- end
52
+ transformer.transform(file, format: format, &block)
367
53
  end
368
54
  end
369
55
 
@@ -373,69 +59,22 @@ class ActiveStorage::Variation
373
59
  end
374
60
 
375
61
  private
376
- def pass_transform_argument(command, method, argument)
377
- if eligible_argument?(argument)
378
- command.public_send(method, argument)
379
- else
380
- command.public_send(method)
381
- end
382
- end
383
-
384
- def eligible_argument?(argument)
385
- argument.present? && argument != true
386
- end
387
-
388
- def validate_transformation(name, argument)
389
- method_name = name.to_s.gsub("-","_")
390
-
391
- unless SUPPORTED_IMAGE_PROCESSING_METHODS.any? { |method| method_name == method }
392
- raise UnsupportedImageProcessingMethod, <<~ERROR.squish
393
- One or more of the provided transformation methods is not supported.
394
- ERROR
395
- end
396
-
397
- if argument.present?
398
- if argument.is_a?(String) || argument.is_a?(Symbol)
399
- validate_arg_string(argument)
400
- elsif argument.is_a?(Array)
401
- validate_arg_array(argument)
402
- elsif argument.is_a?(Hash)
403
- validate_arg_hash(argument)
404
- end
405
- end
406
- end
407
-
408
- def validate_arg_string(argument)
409
- if UNSUPPORTED_IMAGE_PROCESSING_ARGUMENTS.any? { |bad_arg| argument.to_s.downcase.include?(bad_arg) }; raise UnsupportedImageProcessingArgument end
410
- end
411
-
412
- def validate_arg_array(argument)
413
- argument.each do |arg|
414
- if arg.is_a?(Integer) || arg.is_a?(Float)
415
- next
416
- elsif arg.is_a?(String) || arg.is_a?(Symbol)
417
- validate_arg_string(arg)
418
- elsif arg.is_a?(Array)
419
- validate_arg_array(arg)
420
- elsif arg.is_a?(Hash)
421
- validate_arg_hash(arg)
422
- end
423
- end
424
- end
425
-
426
- def validate_arg_hash(argument)
427
- argument.each do |key, value|
428
- validate_arg_string(key)
429
-
430
- if value.is_a?(Integer) || value.is_a?(Float)
431
- next
432
- elsif value.is_a?(String) || value.is_a?(Symbol)
433
- validate_arg_string(value)
434
- elsif value.is_a?(Array)
435
- validate_arg_array(value)
436
- elsif value.is_a?(Hash)
437
- validate_arg_hash(value)
62
+ def transformer
63
+ if ActiveStorage.variant_processor
64
+ begin
65
+ require "image_processing"
66
+ rescue LoadError
67
+ ActiveSupport::Deprecation.warn <<~WARNING
68
+ Generating image variants will require the image_processing gem in Rails 6.1.
69
+ Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
70
+ WARNING
71
+
72
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
73
+ else
74
+ ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations)
438
75
  end
76
+ else
77
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
439
78
  end
440
79
  end
441
80
  end
data/config/routes.rb CHANGED
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rails.application.routes.draw do
4
- get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
5
-
6
- direct :rails_blob do |blob, options|
7
- route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
8
- end
9
-
10
- resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
11
- resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
4
+ scope ActiveStorage.routes_prefix do
5
+ get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
12
6
 
7
+ get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
13
8
 
14
- get "/rails/active_storage/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
9
+ get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
10
+ put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
11
+ post "/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
12
+ end
15
13
 
16
14
  direct :rails_representation do |representation, options|
17
15
  signed_blob_id = representation.blob.signed_id
@@ -25,7 +23,10 @@ Rails.application.routes.draw do
25
23
  resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
26
24
 
27
25
 
28
- get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
29
- put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
30
- post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
26
+ direct :rails_blob do |blob, options|
27
+ route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
28
+ end
29
+
30
+ resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
31
+ resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
31
32
  end
@@ -0,0 +1,7 @@
1
+ class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
2
+ def up
3
+ unless foreign_key_exists?(:active_storage_attachments, column: :blob_id)
4
+ add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
5
+ end
6
+ end
7
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/compact"
4
-
5
3
  module ActiveStorage
6
4
  # Extracts the following from a video blob:
7
5
  #
@@ -18,7 +16,7 @@ module ActiveStorage
18
16
  #
19
17
  # When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
20
18
  #
21
- # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
19
+ # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
22
20
  class Analyzer::VideoAnalyzer < Analyzer
23
21
  def self.accept?(blob)
24
22
  blob.video?
@@ -109,7 +107,7 @@ module ActiveStorage
109
107
  JSON.parse(output.read)
110
108
  end
111
109
  rescue Errno::ENOENT
112
- logger.info "Skipping video analysis because ffmpeg isn't installed"
110
+ logger.info "Skipping video analysis because FFmpeg isn't installed"
113
111
  {}
114
112
  end
115
113
 
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_storage/downloading"
4
-
5
3
  module ActiveStorage
6
4
  # This is an abstract base class for analyzers, which extract metadata from blobs. See
7
5
  # ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
8
6
  class Analyzer
9
- include Downloading
10
-
11
7
  attr_reader :blob
12
8
 
13
9
  # Implement this method in a concrete subclass. Have it return true when given a blob from which
@@ -26,8 +22,17 @@ module ActiveStorage
26
22
  end
27
23
 
28
24
  private
25
+ # Downloads the blob to a tempfile on disk. Yields the tempfile.
26
+ def download_blob_to_tempfile(&block) #:doc:
27
+ blob.open tempdir: tempdir, &block
28
+ end
29
+
29
30
  def logger #:doc:
30
31
  ActiveStorage.logger
31
32
  end
33
+
34
+ def tempdir #:doc:
35
+ Dir.tmpdir
36
+ end
32
37
  end
33
38
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::CreateMany #:nodoc:
5
+ attr_reader :name, :record, :attachables
6
+
7
+ def initialize(name, record, attachables)
8
+ @name, @record, @attachables = name, record, Array(attachables)
9
+ end
10
+
11
+ def attachments
12
+ @attachments ||= subchanges.collect(&:attachment)
13
+ end
14
+
15
+ def blobs
16
+ @blobs ||= subchanges.collect(&:blob)
17
+ end
18
+
19
+ def upload
20
+ subchanges.each(&:upload)
21
+ end
22
+
23
+ def save
24
+ assign_associated_attachments
25
+ reset_associated_blobs
26
+ end
27
+
28
+ private
29
+ def subchanges
30
+ @subchanges ||= attachables.collect { |attachable| build_subchange_from(attachable) }
31
+ end
32
+
33
+ def build_subchange_from(attachable)
34
+ ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
35
+ end
36
+
37
+
38
+ def assign_associated_attachments
39
+ record.public_send("#{name}_attachments=", attachments)
40
+ end
41
+
42
+ def reset_associated_blobs
43
+ record.public_send("#{name}_blobs").reset
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch"
4
+ require "action_dispatch/http/upload"
5
+
6
+ module ActiveStorage
7
+ class Attached::Changes::CreateOne #:nodoc:
8
+ attr_reader :name, :record, :attachable
9
+
10
+ def initialize(name, record, attachable)
11
+ @name, @record, @attachable = name, record, attachable
12
+ end
13
+
14
+ def attachment
15
+ @attachment ||= find_or_build_attachment
16
+ end
17
+
18
+ def blob
19
+ @blob ||= find_or_build_blob
20
+ end
21
+
22
+ def upload
23
+ case attachable
24
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
25
+ blob.upload_without_unfurling(attachable.open)
26
+ when Hash
27
+ blob.upload_without_unfurling(attachable.fetch(:io))
28
+ end
29
+ end
30
+
31
+ def save
32
+ record.public_send("#{name}_attachment=", attachment)
33
+ end
34
+
35
+ private
36
+ def find_or_build_attachment
37
+ find_attachment || build_attachment
38
+ end
39
+
40
+ def find_attachment
41
+ if record.public_send("#{name}_blob") == blob
42
+ record.public_send("#{name}_attachment")
43
+ end
44
+ end
45
+
46
+ def build_attachment
47
+ ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
48
+ end
49
+
50
+ def find_or_build_blob
51
+ case attachable
52
+ when ActiveStorage::Blob
53
+ attachable
54
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
55
+ ActiveStorage::Blob.build_after_unfurling \
56
+ io: attachable.open,
57
+ filename: attachable.original_filename,
58
+ content_type: attachable.content_type
59
+ when Hash
60
+ ActiveStorage::Blob.build_after_unfurling(attachable)
61
+ when String
62
+ ActiveStorage::Blob.find_signed(attachable)
63
+ else
64
+ raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne #:nodoc:
5
+ private
6
+ def find_attachment
7
+ record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::DeleteMany #:nodoc:
5
+ attr_reader :name, :record
6
+
7
+ def initialize(name, record)
8
+ @name, @record = name, record
9
+ end
10
+
11
+ def attachments
12
+ ActiveStorage::Attachment.none
13
+ end
14
+
15
+ def blobs
16
+ ActiveStorage::Blob.none
17
+ end
18
+
19
+ def save
20
+ record.public_send("#{name}_attachments=", [])
21
+ end
22
+ end
23
+ end