activestorage 7.0.10 → 7.1.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -381
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +5 -5
  5. data/app/assets/javascripts/activestorage.esm.js +11 -7
  6. data/app/assets/javascripts/activestorage.js +12 -6
  7. data/app/controllers/active_storage/disk_controller.rb +4 -2
  8. data/app/controllers/active_storage/representations/proxy_controller.rb +1 -1
  9. data/app/controllers/concerns/active_storage/file_server.rb +4 -1
  10. data/app/javascript/activestorage/blob_record.js +4 -1
  11. data/app/javascript/activestorage/direct_upload.js +3 -2
  12. data/app/javascript/activestorage/index.js +3 -1
  13. data/app/javascript/activestorage/ujs.js +3 -3
  14. data/app/jobs/active_storage/transform_job.rb +12 -0
  15. data/app/models/active_storage/attachment.rb +87 -13
  16. data/app/models/active_storage/blob/analyzable.rb +4 -3
  17. data/app/models/active_storage/blob/identifiable.rb +1 -0
  18. data/app/models/active_storage/blob/representable.rb +7 -3
  19. data/app/models/active_storage/blob.rb +44 -50
  20. data/app/models/active_storage/current.rb +0 -10
  21. data/app/models/active_storage/filename.rb +2 -0
  22. data/app/models/active_storage/named_variant.rb +21 -0
  23. data/app/models/active_storage/preview.rb +6 -8
  24. data/app/models/active_storage/variant.rb +8 -3
  25. data/app/models/active_storage/variant_with_record.rb +16 -11
  26. data/app/models/active_storage/variation.rb +5 -3
  27. data/db/migrate/20170806125915_create_active_storage_tables.rb +1 -1
  28. data/lib/active_storage/analyzer/audio_analyzer.rb +16 -4
  29. data/lib/active_storage/analyzer/image_analyzer.rb +2 -0
  30. data/lib/active_storage/analyzer/video_analyzer.rb +3 -1
  31. data/lib/active_storage/analyzer.rb +2 -0
  32. data/lib/active_storage/attached/changes/create_many.rb +8 -3
  33. data/lib/active_storage/attached/changes/create_one.rb +14 -2
  34. data/lib/active_storage/attached/many.rb +5 -4
  35. data/lib/active_storage/attached/model.rb +59 -42
  36. data/lib/active_storage/attached/one.rb +5 -4
  37. data/lib/active_storage/attached.rb +2 -0
  38. data/lib/active_storage/deprecator.rb +7 -0
  39. data/lib/active_storage/engine.rb +11 -7
  40. data/lib/active_storage/fixture_set.rb +2 -4
  41. data/lib/active_storage/gem_version.rb +4 -4
  42. data/lib/active_storage/log_subscriber.rb +12 -0
  43. data/lib/active_storage/previewer.rb +8 -1
  44. data/lib/active_storage/reflection.rb +3 -3
  45. data/lib/active_storage/service/azure_storage_service.rb +2 -0
  46. data/lib/active_storage/service/disk_service.rb +2 -0
  47. data/lib/active_storage/service/gcs_service.rb +11 -20
  48. data/lib/active_storage/service/mirror_service.rb +10 -5
  49. data/lib/active_storage/service/s3_service.rb +2 -0
  50. data/lib/active_storage/service.rb +4 -2
  51. data/lib/active_storage/transformers/transformer.rb +2 -0
  52. data/lib/active_storage/version.rb +1 -1
  53. data/lib/active_storage.rb +19 -3
  54. metadata +22 -31
  55. data/app/models/active_storage/blob/servable.rb +0 -22
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveStorage::NamedVariant # :nodoc:
4
+ attr_reader :transformations, :preprocessed
5
+
6
+ def initialize(transformations)
7
+ @preprocessed = transformations[:preprocessed]
8
+ @transformations = transformations.except(:preprocessed)
9
+ end
10
+
11
+ def preprocessed?(record)
12
+ case preprocessed
13
+ when Symbol
14
+ record.send(preprocessed)
15
+ when Proc
16
+ preprocessed.call(record)
17
+ else
18
+ preprocessed
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # = Active Storage \Preview
4
+ #
3
5
  # Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by
4
6
  # extracting its first frame, and a PDF blob can be previewed by extracting its first page.
5
7
  #
@@ -10,7 +12,7 @@
10
12
  # documentation for more details on what's required of previewers.
11
13
  #
12
14
  # To choose the previewer for a blob, Active Storage calls +accept?+ on each registered previewer in order. It uses the
13
- # first previewer for which +accept?+ returns true when given the blob. In a Rails application, add or remove previewers
15
+ # first previewer for which +accept?+ returns true when given the blob. In a \Rails application, add or remove previewers
14
16
  # by manipulating +Rails.application.config.active_storage.previewers+ in an initializer:
15
17
  #
16
18
  # Rails.application.config.active_storage.previewers
@@ -20,28 +22,24 @@
20
22
  # Rails.application.config.active_storage.previewers << DOCXPreviewer
21
23
  # # => [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ]
22
24
  #
23
- # Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
25
+ # Outside of a \Rails application, modify +ActiveStorage.previewers+ instead.
24
26
  #
25
27
  # The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
26
28
  # {FFmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
27
29
  # and the other requires {muPDF}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or muPDF.
28
30
  #
29
- # These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
31
+ # These libraries are not provided by \Rails. You must install them yourself to use the built-in previewers. Before you
30
32
  # install and use third-party software, make sure you understand the licensing implications of doing so.
31
33
  class ActiveStorage::Preview
32
- include ActiveStorage::Blob::Servable
33
-
34
34
  class UnprocessedError < StandardError; end
35
35
 
36
- delegate :filename, :content_type, to: :variant
37
-
38
36
  attr_reader :blob, :variation
39
37
 
40
38
  def initialize(blob, variation_or_variation_key)
41
39
  @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
42
40
  end
43
41
 
44
- # Processes the preview if it has not been processed yet. Returns the receiving +ActiveStorage::Preview+ instance for convenience:
42
+ # Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
45
43
  #
46
44
  # blob.preview(resize_to_limit: [100, 100]).processed.url
47
45
  #
@@ -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.
@@ -51,8 +53,6 @@
51
53
  # * {ImageProcessing::Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md#methods]
52
54
  # * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
53
55
  class ActiveStorage::Variant
54
- include ActiveStorage::Blob::Servable
55
-
56
56
  attr_reader :blob, :variation
57
57
  delegate :service, to: :blob
58
58
  delegate :content_type, to: :variation
@@ -74,7 +74,7 @@ class ActiveStorage::Variant
74
74
 
75
75
  # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
76
76
  #
77
- # 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
78
78
  # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
79
79
  # for its redirection.
80
80
  def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
@@ -96,6 +96,11 @@ class ActiveStorage::Variant
96
96
  self
97
97
  end
98
98
 
99
+ # Deletes variant file from service.
100
+ def destroy
101
+ service.delete(key)
102
+ end
103
+
99
104
  private
100
105
  def processed?
101
106
  service.exist?(key)
@@ -1,10 +1,10 @@
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
- include ActiveStorage::Blob::Servable
7
-
8
8
  attr_reader :blob, :variation
9
9
  delegate :service, to: :blob
10
10
  delegate :content_type, to: :variation
@@ -14,18 +14,10 @@ class ActiveStorage::VariantWithRecord
14
14
  end
15
15
 
16
16
  def processed
17
- process
17
+ process unless processed?
18
18
  self
19
19
  end
20
20
 
21
- def process
22
- transform_blob { |image| create_or_find_record(image: image) } unless processed?
23
- end
24
-
25
- def processed?
26
- record.present?
27
- end
28
-
29
21
  def image
30
22
  record&.image
31
23
  end
@@ -34,9 +26,22 @@ class ActiveStorage::VariantWithRecord
34
26
  ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
35
27
  end
36
28
 
29
+ # Destroys record and deletes file from service.
30
+ def destroy
31
+ record&.destroy
32
+ end
33
+
37
34
  delegate :key, :url, :download, to: :image, allow_nil: true
38
35
 
39
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
+
40
45
  def transform_blob
41
46
  blob.open do |input|
42
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.
@@ -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,8 +22,12 @@ 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))
29
33
  end
@@ -53,7 +57,7 @@ module ActiveStorage
53
57
  case attachable
54
58
  when ActiveStorage::Blob
55
59
  attachable
56
- when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
60
+ when ActionDispatch::Http::UploadedFile
57
61
  ActiveStorage::Blob.build_after_unfurling(
58
62
  io: attachable.open,
59
63
  filename: attachable.original_filename,
@@ -61,6 +65,14 @@ module ActiveStorage
61
65
  record: record,
62
66
  service_name: attachment_service_name
63
67
  )
68
+ when Rack::Test::UploadedFile
69
+ ActiveStorage::Blob.build_after_unfurling(
70
+ io: attachable.respond_to?(:open) ? attachable.open : attachable,
71
+ filename: attachable.original_filename,
72
+ content_type: attachable.content_type,
73
+ record: record,
74
+ service_name: attachment_service_name
75
+ )
64
76
  when Hash
65
77
  ActiveStorage::Blob.build_after_unfurling(
66
78
  **attachable.reverse_merge(
@@ -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)
@@ -144,57 +188,22 @@ module ActiveStorage
144
188
 
145
189
  def #{name}=(attachables)
146
190
  attachables = Array(attachables).compact_blank
191
+ pending_uploads = attachment_changes["#{name}"].try(:pending_uploads)
147
192
 
148
- if ActiveStorage.replace_on_assign_to_many
149
- attachment_changes["#{name}"] =
150
- if attachables.none?
151
- ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
152
- else
153
- ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
154
- end
193
+ attachment_changes["#{name}"] = if attachables.none?
194
+ ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
155
195
  else
156
- ActiveSupport::Deprecation.warn \
157
- "config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. " \
158
- "Make sure that your code works well with config.active_storage.replace_on_assign_to_many set to true before upgrading. " \
159
- "To append new attachables to the Active Storage association, prefer using `attach`. " \
160
- "Using association setter would result in purging the existing attached attachments and replacing them with new ones."
161
-
162
- if attachables.any?
163
- attachment_changes["#{name}"] =
164
- ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, #{name}.blobs + attachables)
165
- end
196
+ ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables, pending_uploads: pending_uploads)
166
197
  end
167
198
  end
168
199
  CODE
169
200
 
170
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading do
171
- def purge
172
- deprecate(:purge)
173
- each(&:purge)
174
- reset
175
- end
176
-
177
- def purge_later
178
- deprecate(:purge_later)
179
- each(&:purge_later)
180
- reset
181
- end
182
-
183
- private
184
- def deprecate(action)
185
- reflection_name = proxy_association.reflection.name
186
- attached_name = reflection_name.to_s.partition("_").first
187
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
188
- Calling `#{action}` from `#{reflection_name}` is deprecated and will be removed in Rails 7.1.
189
- To migrate to Rails 7.1's behavior call `#{action}` from `#{attached_name}` instead: `#{attached_name}.#{action}`.
190
- MSG
191
- end
192
- end
201
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
193
202
  has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading
194
203
 
195
204
  scope :"with_attached_#{name}", -> {
196
205
  if ActiveStorage.track_variants
197
- includes("#{name}_attachments": { blob: :variant_records })
206
+ includes("#{name}_attachments": { blob: { variant_records: { image_attachment: :blob } } })
198
207
  else
199
208
  includes("#{name}_attachments": :blob)
200
209
  end
@@ -221,6 +230,14 @@ module ActiveStorage
221
230
  ActiveStorage::Blob.services.fetch(service) do
222
231
  raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
223
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"
224
241
  end
225
242
  end
226
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
  #
@@ -24,13 +26,9 @@ module ActiveStorage
24
26
  # has_one_attached :thumbnail
25
27
  # end
26
28
  #
27
- # <code></code>
28
- #
29
29
  # # fixtures/active_storage/blobs.yml
30
30
  # first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
31
31
  #
32
- # <code></code>
33
- #
34
32
  # # fixtures/active_storage/attachments.yml
35
33
  # first_thumbnail_attachment:
36
34
  # name: thumbnail
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- # Returns the currently loaded version of Active Storage as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of Active Storage as a +Gem::Version+.
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 0
12
- TINY = 10
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end