activestorage 6.1.3.1 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -181
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +25 -11
  5. data/app/assets/javascripts/activestorage.esm.js +844 -0
  6. data/app/assets/javascripts/activestorage.js +257 -376
  7. data/app/controllers/active_storage/base_controller.rb +1 -10
  8. data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
  9. data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
  10. data/app/controllers/active_storage/representations/base_controller.rb +18 -0
  11. data/app/controllers/active_storage/representations/proxy_controller.rb +7 -11
  12. data/app/controllers/active_storage/representations/redirect_controller.rb +7 -7
  13. data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
  14. data/app/controllers/concerns/active_storage/set_current.rb +3 -3
  15. data/app/controllers/concerns/active_storage/streaming.rb +65 -0
  16. data/app/javascript/activestorage/ujs.js +1 -1
  17. data/app/models/active_storage/attachment.rb +36 -3
  18. data/app/models/active_storage/blob/representable.rb +8 -6
  19. data/app/models/active_storage/blob.rb +27 -28
  20. data/app/models/active_storage/current.rb +12 -2
  21. data/app/models/active_storage/preview.rb +6 -4
  22. data/app/models/active_storage/record.rb +1 -1
  23. data/app/models/active_storage/variant.rb +4 -7
  24. data/app/models/active_storage/variant_record.rb +2 -0
  25. data/app/models/active_storage/variant_with_record.rb +10 -6
  26. data/app/models/active_storage/variation.rb +2 -2
  27. data/config/routes.rb +10 -10
  28. data/db/migrate/20170806125915_create_active_storage_tables.rb +29 -8
  29. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
  30. data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
  31. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
  32. data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
  33. data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
  34. data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
  35. data/lib/active_storage/analyzer.rb +8 -4
  36. data/lib/active_storage/attached/changes/create_many.rb +7 -3
  37. data/lib/active_storage/attached/changes/create_one.rb +1 -1
  38. data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
  39. data/lib/active_storage/attached/changes/delete_many.rb +1 -1
  40. data/lib/active_storage/attached/changes/delete_one.rb +1 -1
  41. data/lib/active_storage/attached/changes/detach_many.rb +18 -0
  42. data/lib/active_storage/attached/changes/detach_one.rb +24 -0
  43. data/lib/active_storage/attached/changes/purge_many.rb +27 -0
  44. data/lib/active_storage/attached/changes/purge_one.rb +27 -0
  45. data/lib/active_storage/attached/changes.rb +7 -1
  46. data/lib/active_storage/attached/many.rb +27 -15
  47. data/lib/active_storage/attached/model.rb +31 -5
  48. data/lib/active_storage/attached/one.rb +32 -27
  49. data/lib/active_storage/downloader.rb +2 -2
  50. data/lib/active_storage/engine.rb +29 -1
  51. data/lib/active_storage/errors.rb +3 -0
  52. data/lib/active_storage/fixture_set.rb +76 -0
  53. data/lib/active_storage/gem_version.rb +4 -4
  54. data/lib/active_storage/previewer/video_previewer.rb +1 -1
  55. data/lib/active_storage/previewer.rb +14 -5
  56. data/lib/active_storage/reflection.rb +12 -2
  57. data/lib/active_storage/service/azure_storage_service.rb +1 -1
  58. data/lib/active_storage/service/configurator.rb +1 -1
  59. data/lib/active_storage/service/disk_service.rb +13 -18
  60. data/lib/active_storage/service/gcs_service.rb +91 -7
  61. data/lib/active_storage/service/mirror_service.rb +1 -1
  62. data/lib/active_storage/service/registry.rb +1 -1
  63. data/lib/active_storage/service/s3_service.rb +4 -4
  64. data/lib/active_storage/service.rb +3 -3
  65. data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
  66. data/lib/active_storage/transformers/transformer.rb +1 -1
  67. data/lib/active_storage.rb +5 -1
  68. data/lib/tasks/activestorage.rake +5 -1
  69. metadata +32 -22
  70. data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -1,17 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- # Extracts width and height in pixels from an image blob.
4
+ # This is an abstract base class for image analyzers, which extract width and height from an image blob.
5
5
  #
6
6
  # If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience.
7
7
  #
8
8
  # Example:
9
9
  #
10
- # ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
10
+ # ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick.new(blob).metadata
11
11
  # # => { width: 4104, height: 2736 }
12
- #
13
- # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
14
- # the {ImageMagick}[http://www.imagemagick.org] system library.
15
12
  class Analyzer::ImageAnalyzer < Analyzer
16
13
  def self.accept?(blob)
17
14
  blob.image?
@@ -26,30 +23,5 @@ module ActiveStorage
26
23
  end
27
24
  end
28
25
  end
29
-
30
- private
31
- def read_image
32
- download_blob_to_tempfile do |file|
33
- require "mini_magick"
34
- image = MiniMagick::Image.new(file.path)
35
-
36
- if image.valid?
37
- yield image
38
- else
39
- logger.info "Skipping image analysis because ImageMagick doesn't support the file"
40
- {}
41
- end
42
- end
43
- rescue LoadError
44
- logger.info "Skipping image analysis because the mini_magick gem isn't installed"
45
- {}
46
- rescue MiniMagick::Error => error
47
- logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
48
- {}
49
- end
50
-
51
- def rotated_image?(image)
52
- %w[ RightTop LeftBottom ].include?(image["%[orientation]"])
53
- end
54
26
  end
55
27
  end
@@ -8,11 +8,13 @@ module ActiveStorage
8
8
  # * Duration (seconds)
9
9
  # * Angle (degrees)
10
10
  # * Display aspect ratio
11
+ # * Audio (true if file has an audio channel, false if not)
12
+ # * Video (true if file has an video channel, false if not)
11
13
  #
12
14
  # Example:
13
15
  #
14
16
  # ActiveStorage::Analyzer::VideoAnalyzer.new(blob).metadata
15
- # # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3] }
17
+ # # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3], audio: true, video: true }
16
18
  #
17
19
  # When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
18
20
  #
@@ -23,7 +25,7 @@ module ActiveStorage
23
25
  end
24
26
 
25
27
  def metadata
26
- { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio }.compact
28
+ { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio, audio: audio?, video: video? }.compact
27
29
  end
28
30
 
29
31
  private
@@ -63,11 +65,18 @@ module ActiveStorage
63
65
  end
64
66
  end
65
67
 
66
-
67
68
  def rotated?
68
69
  angle == 90 || angle == 270
69
70
  end
70
71
 
72
+ def audio?
73
+ audio_stream.present?
74
+ end
75
+
76
+ def video?
77
+ video_stream.present?
78
+ end
79
+
71
80
  def computed_height
72
81
  if encoded_width && display_height_scale
73
82
  encoded_width * display_height_scale
@@ -95,6 +104,10 @@ module ActiveStorage
95
104
  @video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
96
105
  end
97
106
 
107
+ def audio_stream
108
+ @audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
109
+ end
110
+
98
111
  def streams
99
112
  probe["streams"] || []
100
113
  end
@@ -108,14 +121,16 @@ module ActiveStorage
108
121
  end
109
122
 
110
123
  def probe_from(file)
111
- IO.popen([ ffprobe_path,
112
- "-print_format", "json",
113
- "-show_streams",
114
- "-show_format",
115
- "-v", "error",
116
- file.path
117
- ]) do |output|
118
- JSON.parse(output.read)
124
+ instrument(File.basename(ffprobe_path)) do
125
+ IO.popen([ ffprobe_path,
126
+ "-print_format", "json",
127
+ "-show_streams",
128
+ "-show_format",
129
+ "-v", "error",
130
+ file.path
131
+ ]) do |output|
132
+ JSON.parse(output.read)
133
+ end
119
134
  end
120
135
  rescue Errno::ENOENT
121
136
  logger.info "Skipping video analysis because FFmpeg isn't installed"
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveStorage
4
4
  # This is an abstract base class for analyzers, which extract metadata from blobs. See
5
- # ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
5
+ # ActiveStorage::Analyzer::VideoAnalyzer for an example of a concrete subclass.
6
6
  class Analyzer
7
7
  attr_reader :blob
8
8
 
@@ -29,16 +29,20 @@ module ActiveStorage
29
29
 
30
30
  private
31
31
  # Downloads the blob to a tempfile on disk. Yields the tempfile.
32
- def download_blob_to_tempfile(&block) #:doc:
32
+ def download_blob_to_tempfile(&block) # :doc:
33
33
  blob.open tmpdir: tmpdir, &block
34
34
  end
35
35
 
36
- def logger #:doc:
36
+ def logger # :doc:
37
37
  ActiveStorage.logger
38
38
  end
39
39
 
40
- def tmpdir #:doc:
40
+ def tmpdir # :doc:
41
41
  Dir.tmpdir
42
42
  end
43
+
44
+ def instrument(analyzer, &block) # :doc:
45
+ ActiveSupport::Notifications.instrument("analyze.active_storage", analyzer: analyzer, &block)
46
+ end
43
47
  end
44
48
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Attached::Changes::CreateMany #:nodoc:
4
+ class Attached::Changes::CreateMany # :nodoc:
5
5
  attr_reader :name, :record, :attachables
6
6
 
7
7
  def initialize(name, record, attachables)
8
8
  @name, @record, @attachables = name, record, Array(attachables)
9
9
  blobs.each(&:identify_without_saving)
10
+ attachments
10
11
  end
11
12
 
12
13
  def attachments
@@ -35,13 +36,16 @@ module ActiveStorage
35
36
  ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
36
37
  end
37
38
 
38
-
39
39
  def assign_associated_attachments
40
- record.public_send("#{name}_attachments=", attachments)
40
+ record.public_send("#{name}_attachments=", persisted_or_new_attachments)
41
41
  end
42
42
 
43
43
  def reset_associated_blobs
44
44
  record.public_send("#{name}_blobs").reset
45
45
  end
46
+
47
+ def persisted_or_new_attachments
48
+ attachments.select { |attachment| attachment.persisted? || attachment.new_record? }
49
+ end
46
50
  end
47
51
  end
@@ -4,7 +4,7 @@ require "action_dispatch"
4
4
  require "action_dispatch/http/upload"
5
5
 
6
6
  module ActiveStorage
7
- class Attached::Changes::CreateOne #:nodoc:
7
+ class Attached::Changes::CreateOne # :nodoc:
8
8
  attr_reader :name, :record, :attachable
9
9
 
10
10
  def initialize(name, record, attachable)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne #:nodoc:
4
+ class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne # :nodoc:
5
5
  private
6
6
  def find_attachment
7
7
  record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Attached::Changes::DeleteMany #:nodoc:
4
+ class Attached::Changes::DeleteMany # :nodoc:
5
5
  attr_reader :name, :record
6
6
 
7
7
  def initialize(name, record)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Attached::Changes::DeleteOne #:nodoc:
4
+ class Attached::Changes::DeleteOne # :nodoc:
5
5
  attr_reader :name, :record
6
6
 
7
7
  def initialize(name, record)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::DetachMany # :nodoc:
5
+ attr_reader :name, :record, :attachments
6
+
7
+ def initialize(name, record, attachments)
8
+ @name, @record, @attachments = name, record, attachments
9
+ end
10
+
11
+ def detach
12
+ if attachments.any?
13
+ attachments.delete_all if attachments.respond_to?(:delete_all)
14
+ record.attachment_changes.delete(name)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::DetachOne # :nodoc:
5
+ attr_reader :name, :record, :attachment
6
+
7
+ def initialize(name, record, attachment)
8
+ @name, @record, @attachment = name, record, attachment
9
+ end
10
+
11
+ def detach
12
+ if attachment.present?
13
+ attachment.delete
14
+ reset
15
+ end
16
+ end
17
+
18
+ private
19
+ def reset
20
+ record.attachment_changes.delete(name)
21
+ record.public_send("#{name}_attachment=", nil)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::PurgeMany # :nodoc:
5
+ attr_reader :name, :record, :attachments
6
+
7
+ def initialize(name, record, attachments)
8
+ @name, @record, @attachments = name, record, attachments
9
+ end
10
+
11
+ def purge
12
+ attachments.each(&:purge)
13
+ reset
14
+ end
15
+
16
+ def purge_later
17
+ attachments.each(&:purge_later)
18
+ reset
19
+ end
20
+
21
+ private
22
+ def reset
23
+ record.attachment_changes.delete(name)
24
+ record.public_send("#{name}_attachments").reset
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::PurgeOne # :nodoc:
5
+ attr_reader :name, :record, :attachment
6
+
7
+ def initialize(name, record, attachment)
8
+ @name, @record, @attachment = name, record, attachment
9
+ end
10
+
11
+ def purge
12
+ attachment&.purge
13
+ reset
14
+ end
15
+
16
+ def purge_later
17
+ attachment&.purge_later
18
+ reset
19
+ end
20
+
21
+ private
22
+ def reset
23
+ record.attachment_changes.delete(name)
24
+ record.public_send("#{name}_attachment=", nil)
25
+ end
26
+ end
27
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- module Attached::Changes #:nodoc:
4
+ module Attached::Changes # :nodoc:
5
5
  extend ActiveSupport::Autoload
6
6
 
7
7
  eager_autoload do
@@ -11,6 +11,12 @@ module ActiveStorage
11
11
 
12
12
  autoload :DeleteOne
13
13
  autoload :DeleteMany
14
+
15
+ autoload :DetachOne
16
+ autoload :DetachMany
17
+
18
+ autoload :PurgeOne
19
+ autoload :PurgeMany
14
20
  end
15
21
  end
16
22
  end
@@ -3,6 +3,25 @@
3
3
  module ActiveStorage
4
4
  # Decorated proxy object representing of multiple attachments to a model.
5
5
  class Attached::Many < Attached
6
+ ##
7
+ # :method: purge
8
+ #
9
+ # Directly purges each associated attachment (i.e. destroys the blobs and
10
+ # attachments and deletes the files on the service).
11
+ delegate :purge, to: :purge_many
12
+
13
+ ##
14
+ # :method: purge_later
15
+ #
16
+ # Purges each associated attachment through the queuing system.
17
+ delegate :purge_later, to: :purge_many
18
+
19
+ ##
20
+ # :method: detach
21
+ #
22
+ # Deletes associated attachments without purging them, leaving their respective blobs in place.
23
+ delegate :detach, to: :detach_many
24
+
6
25
  delegate_missing_to :attachments
7
26
 
8
27
  # Returns all the associated attachment records.
@@ -25,7 +44,7 @@ module ActiveStorage
25
44
  #
26
45
  # document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
27
46
  # document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
28
- # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
47
+ # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpeg")
29
48
  # document.images.attach([ first_blob, second_blob ])
30
49
  def attach(*attachables)
31
50
  if record.persisted? && !record.changed?
@@ -47,20 +66,13 @@ module ActiveStorage
47
66
  attachments.any?
48
67
  end
49
68
 
50
- # Deletes associated attachments without purging them, leaving their respective blobs in place.
51
- def detach
52
- attachments.delete_all if attached?
53
- end
54
-
55
- ##
56
- # :method: purge
57
- #
58
- # Directly purges each associated attachment (i.e. destroys the blobs and
59
- # attachments and deletes the files on the service).
69
+ private
70
+ def purge_many
71
+ Attached::Changes::PurgeMany.new(name, record, attachments)
72
+ end
60
73
 
61
- ##
62
- # :method: purge_later
63
- #
64
- # Purges each associated attachment through the queuing system.
74
+ def detach_many
75
+ Attached::Changes::DetachMany.new(name, record, attachments)
76
+ end
65
77
  end
66
78
  end
@@ -83,6 +83,7 @@ module ActiveStorage
83
83
  { dependent: dependent, service_name: service },
84
84
  self
85
85
  )
86
+ yield reflection if block_given?
86
87
  ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
87
88
  end
88
89
 
@@ -144,6 +145,12 @@ module ActiveStorage
144
145
  ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
145
146
  end
146
147
  else
148
+ ActiveSupport::Deprecation.warn \
149
+ "config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. " \
150
+ "Make sure that your code works well with config.active_storage.replace_on_assign_to_many set to true before upgrading. " \
151
+ "To append new attachables to the Active Storage association, prefer using `attach`. " \
152
+ "Using association setter would result in purging the existing attached attachments and replacing them with new ones."
153
+
147
154
  if Array(attachables).any?
148
155
  attachment_changes["#{name}"] =
149
156
  ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, #{name}.blobs + attachables)
@@ -154,18 +161,36 @@ module ActiveStorage
154
161
 
155
162
  has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading do
156
163
  def purge
164
+ deprecate(:purge)
157
165
  each(&:purge)
158
166
  reset
159
167
  end
160
168
 
161
169
  def purge_later
170
+ deprecate(:purge_later)
162
171
  each(&:purge_later)
163
172
  reset
164
173
  end
174
+
175
+ private
176
+ def deprecate(action)
177
+ reflection_name = proxy_association.reflection.name
178
+ attached_name = reflection_name.to_s.partition("_").first
179
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
180
+ Calling `#{action}` from `#{reflection_name}` is deprecated and will be removed in Rails 7.1.
181
+ To migrate to Rails 7.1's behavior call `#{action}` from `#{attached_name}` instead: `#{attached_name}.#{action}`.
182
+ MSG
183
+ end
165
184
  end
166
185
  has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading
167
186
 
168
- scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
187
+ scope :"with_attached_#{name}", -> {
188
+ if ActiveStorage.track_variants
189
+ includes("#{name}_attachments": { blob: :variant_records })
190
+ else
191
+ includes("#{name}_attachments": :blob)
192
+ end
193
+ }
169
194
 
170
195
  after_save { attachment_changes[name.to_s]&.save }
171
196
 
@@ -178,6 +203,7 @@ module ActiveStorage
178
203
  { dependent: dependent, service_name: service },
179
204
  self
180
205
  )
206
+ yield reflection if block_given?
181
207
  ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
182
208
  end
183
209
 
@@ -191,21 +217,21 @@ module ActiveStorage
191
217
  end
192
218
  end
193
219
 
194
- def attachment_changes #:nodoc:
220
+ def attachment_changes # :nodoc:
195
221
  @attachment_changes ||= {}
196
222
  end
197
223
 
198
- def changed_for_autosave? #:nodoc:
224
+ def changed_for_autosave? # :nodoc:
199
225
  super || attachment_changes.any?
200
226
  end
201
227
 
202
- def initialize_dup(*) #:nodoc:
228
+ def initialize_dup(*) # :nodoc:
203
229
  super
204
230
  @active_storage_attached = nil
205
231
  @attachment_changes = nil
206
232
  end
207
233
 
208
- def reload(*) #:nodoc:
234
+ def reload(*) # :nodoc:
209
235
  super.tap { @attachment_changes = nil }
210
236
  end
211
237
  end