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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +155 -307
- data/MIT-LICENSE +1 -1
- data/README.md +6 -6
- data/app/assets/javascripts/activestorage.esm.js +8 -4
- data/app/assets/javascripts/activestorage.js +9 -3
- data/app/controllers/active_storage/blobs/proxy_controller.rb +1 -0
- data/app/controllers/active_storage/disk_controller.rb +4 -2
- data/app/controllers/active_storage/representations/proxy_controller.rb +1 -0
- data/app/controllers/concerns/active_storage/disable_session.rb +12 -0
- data/app/controllers/concerns/active_storage/file_server.rb +4 -1
- data/app/javascript/activestorage/blob_record.js +4 -1
- data/app/javascript/activestorage/direct_upload.js +3 -2
- data/app/javascript/activestorage/index.js +3 -1
- data/app/jobs/active_storage/analyze_job.rb +1 -1
- data/app/jobs/active_storage/mirror_job.rb +1 -1
- data/app/jobs/active_storage/purge_job.rb +1 -1
- data/app/jobs/active_storage/transform_job.rb +12 -0
- data/app/models/active_storage/attachment.rb +87 -13
- data/app/models/active_storage/blob/analyzable.rb +4 -3
- data/app/models/active_storage/blob/identifiable.rb +1 -0
- data/app/models/active_storage/blob/representable.rb +7 -3
- data/app/models/active_storage/blob.rb +26 -46
- data/app/models/active_storage/current.rb +0 -10
- data/app/models/active_storage/filename.rb +2 -0
- data/app/models/active_storage/named_variant.rb +21 -0
- data/app/models/active_storage/preview.rb +5 -3
- data/app/models/active_storage/variant.rb +8 -7
- data/app/models/active_storage/variant_with_record.rb +19 -7
- data/app/models/active_storage/variation.rb +5 -3
- data/config/routes.rb +6 -4
- data/db/migrate/20170806125915_create_active_storage_tables.rb +1 -1
- data/lib/active_storage/analyzer/audio_analyzer.rb +16 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +3 -1
- data/lib/active_storage/analyzer.rb +2 -0
- data/lib/active_storage/attached/changes/create_many.rb +8 -3
- data/lib/active_storage/attached/changes/create_one.rb +45 -3
- data/lib/active_storage/attached/many.rb +5 -4
- data/lib/active_storage/attached/model.rb +66 -43
- data/lib/active_storage/attached/one.rb +5 -4
- data/lib/active_storage/attached.rb +2 -0
- data/lib/active_storage/deprecator.rb +7 -0
- data/lib/active_storage/engine.rb +11 -7
- data/lib/active_storage/fixture_set.rb +2 -0
- data/lib/active_storage/gem_version.rb +3 -3
- data/lib/active_storage/log_subscriber.rb +12 -0
- data/lib/active_storage/previewer.rb +8 -1
- data/lib/active_storage/reflection.rb +3 -3
- data/lib/active_storage/service/azure_storage_service.rb +2 -0
- data/lib/active_storage/service/disk_service.rb +2 -0
- data/lib/active_storage/service/gcs_service.rb +11 -20
- data/lib/active_storage/service/mirror_service.rb +10 -5
- data/lib/active_storage/service/s3_service.rb +2 -0
- data/lib/active_storage/service.rb +4 -2
- data/lib/active_storage/transformers/transformer.rb +2 -0
- data/lib/active_storage/version.rb +1 -1
- data/lib/active_storage.rb +19 -3
- 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
|
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
|
19
|
-
|
21
|
+
def image
|
22
|
+
record&.image
|
20
23
|
end
|
21
24
|
|
22
|
-
def
|
23
|
-
|
25
|
+
def filename
|
26
|
+
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
|
24
27
|
end
|
25
28
|
|
26
|
-
|
27
|
-
|
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 "
|
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
|
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
|
-
|
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[
|
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
|
-
#
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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}", -> {
|
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
|
143
|
-
|
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
|
-
|
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
|
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: :
|
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.
|
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
|
@@ -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
|
-
|
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
|