activestorage 7.0.8 → 7.1.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -307
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +6 -6
  5. data/app/assets/javascripts/activestorage.esm.js +8 -4
  6. data/app/assets/javascripts/activestorage.js +9 -3
  7. data/app/controllers/active_storage/blobs/proxy_controller.rb +1 -0
  8. data/app/controllers/active_storage/disk_controller.rb +4 -2
  9. data/app/controllers/active_storage/representations/proxy_controller.rb +1 -0
  10. data/app/controllers/concerns/active_storage/disable_session.rb +12 -0
  11. data/app/controllers/concerns/active_storage/file_server.rb +4 -1
  12. data/app/javascript/activestorage/blob_record.js +4 -1
  13. data/app/javascript/activestorage/direct_upload.js +3 -2
  14. data/app/javascript/activestorage/index.js +3 -1
  15. data/app/jobs/active_storage/analyze_job.rb +1 -1
  16. data/app/jobs/active_storage/mirror_job.rb +1 -1
  17. data/app/jobs/active_storage/purge_job.rb +1 -1
  18. data/app/jobs/active_storage/transform_job.rb +12 -0
  19. data/app/models/active_storage/attachment.rb +87 -13
  20. data/app/models/active_storage/blob/analyzable.rb +4 -3
  21. data/app/models/active_storage/blob/identifiable.rb +1 -0
  22. data/app/models/active_storage/blob/representable.rb +7 -3
  23. data/app/models/active_storage/blob.rb +26 -46
  24. data/app/models/active_storage/current.rb +0 -10
  25. data/app/models/active_storage/filename.rb +2 -0
  26. data/app/models/active_storage/named_variant.rb +21 -0
  27. data/app/models/active_storage/preview.rb +5 -3
  28. data/app/models/active_storage/variant.rb +8 -7
  29. data/app/models/active_storage/variant_with_record.rb +19 -7
  30. data/app/models/active_storage/variation.rb +5 -3
  31. data/config/routes.rb +6 -4
  32. data/db/migrate/20170806125915_create_active_storage_tables.rb +1 -1
  33. data/lib/active_storage/analyzer/audio_analyzer.rb +16 -4
  34. data/lib/active_storage/analyzer/image_analyzer.rb +2 -0
  35. data/lib/active_storage/analyzer/video_analyzer.rb +3 -1
  36. data/lib/active_storage/analyzer.rb +2 -0
  37. data/lib/active_storage/attached/changes/create_many.rb +8 -3
  38. data/lib/active_storage/attached/changes/create_one.rb +45 -3
  39. data/lib/active_storage/attached/many.rb +5 -4
  40. data/lib/active_storage/attached/model.rb +66 -43
  41. data/lib/active_storage/attached/one.rb +5 -4
  42. data/lib/active_storage/attached.rb +2 -0
  43. data/lib/active_storage/deprecator.rb +7 -0
  44. data/lib/active_storage/engine.rb +11 -7
  45. data/lib/active_storage/fixture_set.rb +2 -0
  46. data/lib/active_storage/gem_version.rb +3 -3
  47. data/lib/active_storage/log_subscriber.rb +12 -0
  48. data/lib/active_storage/previewer.rb +8 -1
  49. data/lib/active_storage/reflection.rb +3 -3
  50. data/lib/active_storage/service/azure_storage_service.rb +2 -0
  51. data/lib/active_storage/service/disk_service.rb +2 -0
  52. data/lib/active_storage/service/gcs_service.rb +11 -20
  53. data/lib/active_storage/service/mirror_service.rb +10 -5
  54. data/lib/active_storage/service/s3_service.rb +2 -0
  55. data/lib/active_storage/service.rb +4 -2
  56. data/lib/active_storage/transformers/transformer.rb +2 -0
  57. data/lib/active_storage/version.rb +1 -1
  58. data/lib/active_storage.rb +19 -3
  59. metadata +17 -27
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # = Active Storage \Variant
4
+ #
3
5
  # Image blobs can have variants that are the result of a set of transformations applied to the original.
4
6
  # These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
5
7
  # original.
@@ -72,7 +74,7 @@ class ActiveStorage::Variant
72
74
 
73
75
  # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
74
76
  #
75
- # Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL
77
+ # Use <tt>url_for(variant)</tt> (or the implied form, like <tt>link_to variant</tt> or <tt>redirect_to variant</tt>) to get the stable URL
76
78
  # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
77
79
  # for its redirection.
78
80
  def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
@@ -89,17 +91,16 @@ class ActiveStorage::Variant
89
91
  ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
90
92
  end
91
93
 
92
- alias_method :content_type_for_serving, :content_type
93
-
94
- def forced_disposition_for_serving # :nodoc:
95
- nil
96
- end
97
-
98
94
  # Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
99
95
  def image
100
96
  self
101
97
  end
102
98
 
99
+ # Deletes variant file from service.
100
+ def destroy
101
+ service.delete(key)
102
+ end
103
+
103
104
  private
104
105
  def processed?
105
106
  service.exist?(key)
@@ -1,35 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # = Active Storage \Variant With Record
4
+ #
3
5
  # Like an ActiveStorage::Variant, but keeps detail about the variant in the database as an
4
6
  # ActiveStorage::VariantRecord. This is only used if +ActiveStorage.track_variants+ is enabled.
5
7
  class ActiveStorage::VariantWithRecord
6
8
  attr_reader :blob, :variation
7
9
  delegate :service, to: :blob
10
+ delegate :content_type, to: :variation
8
11
 
9
12
  def initialize(blob, variation)
10
13
  @blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
11
14
  end
12
15
 
13
16
  def processed
14
- process
17
+ process unless processed?
15
18
  self
16
19
  end
17
20
 
18
- def process
19
- transform_blob { |image| create_or_find_record(image: image) } unless processed?
21
+ def image
22
+ record&.image
20
23
  end
21
24
 
22
- def processed?
23
- record.present?
25
+ def filename
26
+ ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
24
27
  end
25
28
 
26
- def image
27
- record&.image
29
+ # Destroys record and deletes file from service.
30
+ def destroy
31
+ record&.destroy
28
32
  end
29
33
 
30
34
  delegate :key, :url, :download, to: :image, allow_nil: true
31
35
 
32
36
  private
37
+ def processed?
38
+ record.present?
39
+ end
40
+
41
+ def process
42
+ transform_blob { |image| create_or_find_record(image: image) }
43
+ end
44
+
33
45
  def transform_blob
34
46
  blob.open do |input|
35
47
  variation.transform(input) do |output|
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mini_mime"
3
+ require "marcel"
4
4
 
5
+ # = Active Storage \Variation
6
+ #
5
7
  # A set of transformations that can be applied to a blob to create a variant. This class is exposed via
6
8
  # the ActiveStorage::Blob#variant method and should rarely be used directly.
7
9
  #
@@ -59,14 +61,14 @@ class ActiveStorage::Variation
59
61
 
60
62
  def format
61
63
  transformations.fetch(:format, :png).tap do |format|
62
- if MiniMime.lookup_by_extension(format.to_s).nil?
64
+ if Marcel::Magic.by_extension(format.to_s).nil?
63
65
  raise ArgumentError, "Invalid variant format (#{format.inspect})"
64
66
  end
65
67
  end
66
68
  end
67
69
 
68
70
  def content_type
69
- MiniMime.lookup_by_extension(format.to_s).content_type
71
+ Marcel::MimeType.for(extension: format.to_s)
70
72
  end
71
73
 
72
74
  # Returns a signed key for all the +transformations+ that this variation was instantiated with.
data/config/routes.rb CHANGED
@@ -32,16 +32,17 @@ Rails.application.routes.draw do
32
32
 
33
33
  direct :rails_storage_proxy do |model, options|
34
34
  expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
35
+ expires_at = options.delete(:expires_at)
35
36
 
36
37
  if model.respond_to?(:signed_id)
37
38
  route_for(
38
39
  :rails_service_blob_proxy,
39
- model.signed_id(expires_in: expires_in),
40
+ model.signed_id(expires_in: expires_in, expires_at: expires_at),
40
41
  model.filename,
41
42
  options
42
43
  )
43
44
  else
44
- signed_blob_id = model.blob.signed_id(expires_in: expires_in)
45
+ signed_blob_id = model.blob.signed_id(expires_in: expires_in, expires_at: expires_at)
45
46
  variation_key = model.variation.key
46
47
  filename = model.blob.filename
47
48
 
@@ -57,16 +58,17 @@ Rails.application.routes.draw do
57
58
 
58
59
  direct :rails_storage_redirect do |model, options|
59
60
  expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
61
+ expires_at = options.delete(:expires_at)
60
62
 
61
63
  if model.respond_to?(:signed_id)
62
64
  route_for(
63
65
  :rails_service_blob,
64
- model.signed_id(expires_in: expires_in),
66
+ model.signed_id(expires_in: expires_in, expires_at: expires_at),
65
67
  model.filename,
66
68
  options
67
69
  )
68
70
  else
69
- signed_blob_id = model.blob.signed_id(expires_in: expires_in)
71
+ signed_blob_id = model.blob.signed_id(expires_in: expires_in, expires_at: expires_at)
70
72
  variation_key = model.variation.key
71
73
  filename = model.blob.filename
72
74
 
@@ -1,4 +1,4 @@
1
- class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
1
+ class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  # Use Active Record's configured type for primary and foreign keys
4
4
  primary_key_type, foreign_key_type = primary_and_foreign_key_types
@@ -1,21 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- # Extracts duration (seconds) and bit_rate (bits/s) from an audio blob.
4
+ # = Active Storage Audio \Analyzer
5
+ #
6
+ # Extracts duration (seconds), bit_rate (bits/s), sample_rate (hertz) and tags (internal metadata) from an audio blob.
5
7
  #
6
8
  # Example:
7
9
  #
8
10
  # ActiveStorage::Analyzer::AudioAnalyzer.new(blob).metadata
9
- # # => { duration: 5.0, bit_rate: 320340 }
11
+ # # => { duration: 5.0, bit_rate: 320340, sample_rate: 44100, tags: { encoder: "Lavc57.64", ... } }
10
12
  #
11
- # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
13
+ # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by \Rails.
12
14
  class Analyzer::AudioAnalyzer < Analyzer
13
15
  def self.accept?(blob)
14
16
  blob.audio?
15
17
  end
16
18
 
17
19
  def metadata
18
- { duration: duration, bit_rate: bit_rate }.compact
20
+ { duration: duration, bit_rate: bit_rate, sample_rate: sample_rate, tags: tags }.compact
19
21
  end
20
22
 
21
23
  private
@@ -29,6 +31,16 @@ module ActiveStorage
29
31
  Integer(bit_rate) if bit_rate
30
32
  end
31
33
 
34
+ def sample_rate
35
+ sample_rate = audio_stream["sample_rate"]
36
+ Integer(sample_rate) if sample_rate
37
+ end
38
+
39
+ def tags
40
+ tags = audio_stream["tags"]
41
+ Hash(tags) if tags
42
+ end
43
+
32
44
  def audio_stream
33
45
  @audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
34
46
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
+ # = Active Storage Image \Analyzer
5
+ #
4
6
  # This is an abstract base class for image analyzers, which extract width and height from an image blob.
5
7
  #
6
8
  # If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
+ # = Active Storage Video \Analyzer
5
+ #
4
6
  # Extracts the following from a video blob:
5
7
  #
6
8
  # * Width (pixels)
@@ -18,7 +20,7 @@ module ActiveStorage
18
20
  #
19
21
  # When a video's angle is 90, -90, 270 or -270 degrees, its width and height are automatically swapped for convenience.
20
22
  #
21
- # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
23
+ # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by \Rails.
22
24
  class Analyzer::VideoAnalyzer < Analyzer
23
25
  def self.accept?(blob)
24
26
  blob.video?
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
+ # = Active Storage \Analyzer
5
+ #
4
6
  # This is an abstract base class for analyzers, which extract metadata from blobs. See
5
7
  # ActiveStorage::Analyzer::VideoAnalyzer for an example of a concrete subclass.
6
8
  class Analyzer
@@ -2,11 +2,12 @@
2
2
 
3
3
  module ActiveStorage
4
4
  class Attached::Changes::CreateMany # :nodoc:
5
- attr_reader :name, :record, :attachables
5
+ attr_reader :name, :record, :attachables, :pending_uploads
6
6
 
7
- def initialize(name, record, attachables)
7
+ def initialize(name, record, attachables, pending_uploads: [])
8
8
  @name, @record, @attachables = name, record, Array(attachables)
9
9
  blobs.each(&:identify_without_saving)
10
+ @pending_uploads = Array(pending_uploads) + subchanges_without_blobs
10
11
  attachments
11
12
  end
12
13
 
@@ -19,7 +20,7 @@ module ActiveStorage
19
20
  end
20
21
 
21
22
  def upload
22
- subchanges.each(&:upload)
23
+ pending_uploads.each(&:upload)
23
24
  end
24
25
 
25
26
  def save
@@ -36,6 +37,10 @@ module ActiveStorage
36
37
  ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
37
38
  end
38
39
 
40
+ def subchanges_without_blobs
41
+ subchanges.reject { |subchange| subchange.attachable.is_a?(ActiveStorage::Blob) }
42
+ end
43
+
39
44
  def assign_associated_attachments
40
45
  record.public_send("#{name}_attachments=", persisted_or_new_attachments)
41
46
  end
@@ -22,10 +22,26 @@ module ActiveStorage
22
22
 
23
23
  def upload
24
24
  case attachable
25
- when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
25
+ when ActionDispatch::Http::UploadedFile
26
26
  blob.upload_without_unfurling(attachable.open)
27
+ when Rack::Test::UploadedFile
28
+ blob.upload_without_unfurling(
29
+ attachable.respond_to?(:open) ? attachable.open : attachable
30
+ )
27
31
  when Hash
28
32
  blob.upload_without_unfurling(attachable.fetch(:io))
33
+ when File
34
+ blob.upload_without_unfurling(attachable)
35
+ when Pathname
36
+ blob.upload_without_unfurling(attachable.open)
37
+ when ActiveStorage::Blob
38
+ when String
39
+ else
40
+ raise(
41
+ ArgumentError,
42
+ "Could not upload: expected attachable, " \
43
+ "got #{attachable.inspect}"
44
+ )
29
45
  end
30
46
  end
31
47
 
@@ -53,7 +69,7 @@ module ActiveStorage
53
69
  case attachable
54
70
  when ActiveStorage::Blob
55
71
  attachable
56
- when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
72
+ when ActionDispatch::Http::UploadedFile
57
73
  ActiveStorage::Blob.build_after_unfurling(
58
74
  io: attachable.open,
59
75
  filename: attachable.original_filename,
@@ -61,6 +77,14 @@ module ActiveStorage
61
77
  record: record,
62
78
  service_name: attachment_service_name
63
79
  )
80
+ when Rack::Test::UploadedFile
81
+ ActiveStorage::Blob.build_after_unfurling(
82
+ io: attachable.respond_to?(:open) ? attachable.open : attachable,
83
+ filename: attachable.original_filename,
84
+ content_type: attachable.content_type,
85
+ record: record,
86
+ service_name: attachment_service_name
87
+ )
64
88
  when Hash
65
89
  ActiveStorage::Blob.build_after_unfurling(
66
90
  **attachable.reverse_merge(
@@ -70,8 +94,26 @@ module ActiveStorage
70
94
  )
71
95
  when String
72
96
  ActiveStorage::Blob.find_signed!(attachable, record: record)
97
+ when File
98
+ ActiveStorage::Blob.build_after_unfurling(
99
+ io: attachable,
100
+ filename: File.basename(attachable),
101
+ record: record,
102
+ service_name: attachment_service_name
103
+ )
104
+ when Pathname
105
+ ActiveStorage::Blob.build_after_unfurling(
106
+ io: attachable.open,
107
+ filename: File.basename(attachable),
108
+ record: record,
109
+ service_name: attachment_service_name
110
+ )
73
111
  else
74
- raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
112
+ raise(
113
+ ArgumentError,
114
+ "Could not find or build blob: expected attachable, " \
115
+ "got #{attachable.inspect}"
116
+ )
75
117
  end
76
118
  end
77
119
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
+ # = Active Storage \Attached \Many
5
+ #
4
6
  # Decorated proxy object representing of multiple attachments to a model.
5
7
  class Attached::Many < Attached
6
8
  ##
@@ -47,12 +49,11 @@ module ActiveStorage
47
49
  # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpeg")
48
50
  # document.images.attach([ first_blob, second_blob ])
49
51
  def attach(*attachables)
52
+ record.public_send("#{name}=", blobs + attachables.flatten)
50
53
  if record.persisted? && !record.changed?
51
- record.public_send("#{name}=", blobs + attachables.flatten)
52
- record.save
53
- else
54
- record.public_send("#{name}=", (change&.attachables || blobs) + attachables.flatten)
54
+ return if !record.save
55
55
  end
56
+ record.public_send("#{name}")
56
57
  end
57
58
 
58
59
  # Returns true if any attachments have been made.
@@ -3,10 +3,54 @@
3
3
  require "active_support/core_ext/object/try"
4
4
 
5
5
  module ActiveStorage
6
+ # = Active Storage \Attached \Model
7
+ #
6
8
  # Provides the class-level DSL for declaring an Active Record model's attachments.
7
9
  module Attached::Model
8
10
  extend ActiveSupport::Concern
9
11
 
12
+ ##
13
+ # :method: *_attachment
14
+ #
15
+ # Returns the attachment for the +has_one_attached+.
16
+ #
17
+ # User.last.avatar_attachment
18
+
19
+ ##
20
+ # :method: *_attachments
21
+ #
22
+ # Returns the attachments for the +has_many_attached+.
23
+ #
24
+ # Gallery.last.photos_attachments
25
+
26
+ ##
27
+ # :method: *_blob
28
+ #
29
+ # Returns the blob for the +has_one_attached+ attachment.
30
+ #
31
+ # User.last.avatar_blob
32
+
33
+ ##
34
+ # :method: *_blobs
35
+ #
36
+ # Returns the blobs for the +has_many_attached+ attachments.
37
+ #
38
+ # Gallery.last.photos_blobs
39
+
40
+ ##
41
+ # :method: with_attached_*
42
+ #
43
+ # Includes the attached blobs in your query to avoid N+1 queries.
44
+ #
45
+ # If +ActiveStorage.track_variants+ is enabled, it will also include the
46
+ # variants record and their attached blobs.
47
+ #
48
+ # User.with_attached_avatar
49
+ #
50
+ # Use the plural form for +has_many_attached+:
51
+ #
52
+ # Gallery.with_attached_photos
53
+
10
54
  class_methods do
11
55
  # Specifies the relation between a single attachment and the model.
12
56
  #
@@ -59,7 +103,7 @@ module ActiveStorage
59
103
 
60
104
  def #{name}=(attachable)
61
105
  attachment_changes["#{name}"] =
62
- if attachable.nil?
106
+ if attachable.nil? || attachable == ""
63
107
  ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
64
108
  else
65
109
  ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
@@ -70,7 +114,13 @@ module ActiveStorage
70
114
  has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
71
115
  has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading
72
116
 
73
- scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
117
+ scope :"with_attached_#{name}", -> {
118
+ if ActiveStorage.track_variants
119
+ includes("#{name}_attachment": { blob: { variant_records: { image_attachment: :blob } } })
120
+ else
121
+ includes("#{name}_attachment": :blob)
122
+ end
123
+ }
74
124
 
75
125
  after_save { attachment_changes[name.to_s]&.save }
76
126
 
@@ -138,57 +188,22 @@ module ActiveStorage
138
188
 
139
189
  def #{name}=(attachables)
140
190
  attachables = Array(attachables).compact_blank
191
+ pending_uploads = attachment_changes["#{name}"].try(:pending_uploads)
141
192
 
142
- if ActiveStorage.replace_on_assign_to_many
143
- attachment_changes["#{name}"] =
144
- if attachables.none?
145
- ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
146
- else
147
- ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
148
- end
193
+ attachment_changes["#{name}"] = if attachables.none?
194
+ ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
149
195
  else
150
- ActiveSupport::Deprecation.warn \
151
- "config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. " \
152
- "Make sure that your code works well with config.active_storage.replace_on_assign_to_many set to true before upgrading. " \
153
- "To append new attachables to the Active Storage association, prefer using `attach`. " \
154
- "Using association setter would result in purging the existing attached attachments and replacing them with new ones."
155
-
156
- if attachables.any?
157
- attachment_changes["#{name}"] =
158
- ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, #{name}.blobs + attachables)
159
- end
196
+ ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables, pending_uploads: pending_uploads)
160
197
  end
161
198
  end
162
199
  CODE
163
200
 
164
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading do
165
- def purge
166
- deprecate(:purge)
167
- each(&:purge)
168
- reset
169
- end
170
-
171
- def purge_later
172
- deprecate(:purge_later)
173
- each(&:purge_later)
174
- reset
175
- end
176
-
177
- private
178
- def deprecate(action)
179
- reflection_name = proxy_association.reflection.name
180
- attached_name = reflection_name.to_s.partition("_").first
181
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
182
- Calling `#{action}` from `#{reflection_name}` is deprecated and will be removed in Rails 7.1.
183
- To migrate to Rails 7.1's behavior call `#{action}` from `#{attached_name}` instead: `#{attached_name}.#{action}`.
184
- MSG
185
- end
186
- end
201
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
187
202
  has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading
188
203
 
189
204
  scope :"with_attached_#{name}", -> {
190
205
  if ActiveStorage.track_variants
191
- includes("#{name}_attachments": { blob: :variant_records })
206
+ includes("#{name}_attachments": { blob: { variant_records: { image_attachment: :blob } } })
192
207
  else
193
208
  includes("#{name}_attachments": :blob)
194
209
  end
@@ -215,6 +230,14 @@ module ActiveStorage
215
230
  ActiveStorage::Blob.services.fetch(service) do
216
231
  raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
217
232
  end
233
+ else
234
+ validate_global_service_configuration
235
+ end
236
+ end
237
+
238
+ def validate_global_service_configuration
239
+ if connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
240
+ raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
218
241
  end
219
242
  end
220
243
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
+ # = Active Storage \Attached \One
5
+ #
4
6
  # Representation of a single attachment to a model.
5
7
  class Attached::One < Attached
6
8
  ##
@@ -54,12 +56,11 @@ module ActiveStorage
54
56
  # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpeg")
55
57
  # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
56
58
  def attach(attachable)
59
+ record.public_send("#{name}=", attachable)
57
60
  if record.persisted? && !record.changed?
58
- record.public_send("#{name}=", attachable)
59
- record.save
60
- else
61
- record.public_send("#{name}=", attachable)
61
+ return if !record.save
62
62
  end
63
+ record.public_send("#{name}")
63
64
  end
64
65
 
65
66
  # Returns +true+ if an attachment has been made.
@@ -3,6 +3,8 @@
3
3
  require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module ActiveStorage
6
+ # = Active Storage \Attached
7
+ #
6
8
  # Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
7
9
  # classes that both provide proxy access to the blob association for a record.
8
10
  class Attached
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -35,9 +35,7 @@ module ActiveStorage
35
35
  config.active_storage.variable_content_types = %w(
36
36
  image/png
37
37
  image/gif
38
- image/jpg
39
38
  image/jpeg
40
- image/pjpeg
41
39
  image/tiff
42
40
  image/bmp
43
41
  image/vnd.adobe.photoshop
@@ -51,13 +49,11 @@ module ActiveStorage
51
49
  config.active_storage.web_image_content_types = %w(
52
50
  image/png
53
51
  image/jpeg
54
- image/jpg
55
52
  image/gif
56
53
  )
57
54
 
58
55
  config.active_storage.content_types_to_serve_as_binary = %w(
59
56
  text/html
60
- text/javascript
61
57
  image/svg+xml
62
58
  application/postscript
63
59
  application/x-shockwave-flash
@@ -71,7 +67,6 @@ module ActiveStorage
71
67
  config.active_storage.content_types_allowed_inline = %w(
72
68
  image/png
73
69
  image/gif
74
- image/jpg
75
70
  image/jpeg
76
71
  image/tiff
77
72
  image/bmp
@@ -82,6 +77,10 @@ module ActiveStorage
82
77
 
83
78
  config.eager_load_namespaces << ActiveStorage
84
79
 
80
+ initializer "active_storage.deprecator", before: :load_environment_config do |app|
81
+ app.deprecators[:active_storage] = ActiveStorage.deprecator
82
+ end
83
+
85
84
  initializer "active_storage.configs" do
86
85
  config.after_initialize do |app|
87
86
  ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
@@ -117,9 +116,14 @@ module ActiveStorage
117
116
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
118
117
  ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
119
118
 
120
- ActiveStorage.silence_invalid_content_types_warning = app.config.active_storage.silence_invalid_content_types_warning || false
119
+ unless app.config.active_storage.silence_invalid_content_types_warning.nil?
120
+ ActiveStorage.silence_invalid_content_types_warning = app.config.active_storage.silence_invalid_content_types_warning
121
+ end
122
+
123
+ unless app.config.active_storage.replace_on_assign_to_many.nil?
124
+ ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many
125
+ end
121
126
 
122
- ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
123
127
  ActiveStorage.track_variants = app.config.active_storage.track_variants || false
124
128
  end
125
129
  end
@@ -4,6 +4,8 @@ require "active_support/testing/file_fixtures"
4
4
  require "active_record/secure_token"
5
5
 
6
6
  module ActiveStorage
7
+ # = Active Storage \FixtureSet
8
+ #
7
9
  # Fixtures are a way of organizing data that you want to test against; in
8
10
  # short, sample data.
9
11
  #