activestorage 7.0.8.7 → 7.2.2.1
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 +49 -390
- data/MIT-LICENSE +1 -1
- data/README.md +6 -6
- data/app/assets/javascripts/activestorage.esm.js +11 -7
- data/app/assets/javascripts/activestorage.js +12 -6
- data/app/controllers/active_storage/disk_controller.rb +4 -2
- data/app/controllers/active_storage/representations/proxy_controller.rb +1 -1
- 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/javascript/activestorage/ujs.js +3 -3
- 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/preview_image_job.rb +16 -0
- 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 +101 -16
- 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 +15 -3
- data/app/models/active_storage/blob/servable.rb +22 -0
- data/app/models/active_storage/blob.rb +59 -72
- data/app/models/active_storage/current.rb +0 -10
- data/app/models/active_storage/filename.rb +2 -4
- data/app/models/active_storage/named_variant.rb +21 -0
- data/app/models/active_storage/preview.rb +23 -8
- data/app/models/active_storage/variant.rb +10 -7
- data/app/models/active_storage/variant_record.rb +0 -2
- data/app/models/active_storage/variant_with_record.rb +21 -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 +2 -2
- data/lib/active_storage/analyzer/audio_analyzer.rb +16 -4
- data/lib/active_storage/analyzer/image_analyzer/vips.rb +5 -9
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +9 -3
- 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 +51 -4
- data/lib/active_storage/attached/changes/create_one_of_many.rb +5 -1
- data/lib/active_storage/attached/many.rb +5 -4
- data/lib/active_storage/attached/model.rb +96 -60
- 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 +7 -9
- data/lib/active_storage/fixture_set.rb +7 -1
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +12 -0
- data/lib/active_storage/previewer/mupdf_previewer.rb +6 -2
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +6 -2
- data/lib/active_storage/previewer/video_previewer.rb +1 -1
- 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/image_processing_transformer.rb +1 -1
- data/lib/active_storage/transformers/transformer.rb +2 -0
- data/lib/active_storage/version.rb +1 -1
- data/lib/active_storage.rb +5 -4
- metadata +18 -27
@@ -2,14 +2,4 @@
|
|
2
2
|
|
3
3
|
class ActiveStorage::Current < ActiveSupport::CurrentAttributes # :nodoc:
|
4
4
|
attribute :url_options
|
5
|
-
|
6
|
-
def host=(host)
|
7
|
-
ActiveSupport::Deprecation.warn("ActiveStorage::Current.host= is deprecated, instead use ActiveStorage::Current.url_options=")
|
8
|
-
self.url_options = { host: host }
|
9
|
-
end
|
10
|
-
|
11
|
-
def host
|
12
|
-
ActiveSupport::Deprecation.warn("ActiveStorage::Current.host is deprecated, instead use ActiveStorage::Current.url_options")
|
13
|
-
self.url_options&.dig(:host)
|
14
|
-
end
|
15
5
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# = Active Storage \Filename
|
4
|
+
#
|
3
5
|
# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
|
4
6
|
# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
|
5
7
|
class ActiveStorage::Filename
|
@@ -67,10 +69,6 @@ class ActiveStorage::Filename
|
|
67
69
|
to_s
|
68
70
|
end
|
69
71
|
|
70
|
-
def to_json
|
71
|
-
to_s
|
72
|
-
end
|
73
|
-
|
74
72
|
def <=>(other)
|
75
73
|
to_s.downcase <=> other.to_s.downcase
|
76
74
|
end
|
@@ -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,24 +22,28 @@
|
|
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
|
34
|
+
include ActiveStorage::Blob::Servable
|
35
|
+
|
32
36
|
class UnprocessedError < StandardError; end
|
33
37
|
|
38
|
+
delegate :filename, :content_type, to: :presentation
|
39
|
+
|
34
40
|
attr_reader :blob, :variation
|
35
41
|
|
36
42
|
def initialize(blob, variation_or_variation_key)
|
37
43
|
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
|
38
44
|
end
|
39
45
|
|
40
|
-
# Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
|
46
|
+
# Processes the preview if it has not been processed yet. Returns the receiving +ActiveStorage::Preview+ instance for convenience:
|
41
47
|
#
|
42
48
|
# blob.preview(resize_to_limit: [100, 100]).processed.url
|
43
49
|
#
|
@@ -45,6 +51,7 @@ class ActiveStorage::Preview
|
|
45
51
|
# image is stored with the blob, it is only generated once.
|
46
52
|
def processed
|
47
53
|
process unless processed?
|
54
|
+
variant.processed if variant?
|
48
55
|
self
|
49
56
|
end
|
50
57
|
|
@@ -60,7 +67,7 @@ class ActiveStorage::Preview
|
|
60
67
|
# a stable URL that redirects to the URL returned by this method.
|
61
68
|
def url(**options)
|
62
69
|
if processed?
|
63
|
-
|
70
|
+
presentation.url(**options)
|
64
71
|
else
|
65
72
|
raise UnprocessedError
|
66
73
|
end
|
@@ -69,7 +76,7 @@ class ActiveStorage::Preview
|
|
69
76
|
# Returns a combination key of the blob and the variation that together identifies a specific variant.
|
70
77
|
def key
|
71
78
|
if processed?
|
72
|
-
|
79
|
+
presentation.key
|
73
80
|
else
|
74
81
|
raise UnprocessedError
|
75
82
|
end
|
@@ -82,7 +89,7 @@ class ActiveStorage::Preview
|
|
82
89
|
# if the preview has not been processed yet.
|
83
90
|
def download(&block)
|
84
91
|
if processed?
|
85
|
-
|
92
|
+
presentation.download(&block)
|
86
93
|
else
|
87
94
|
raise UnprocessedError
|
88
95
|
end
|
@@ -102,7 +109,15 @@ class ActiveStorage::Preview
|
|
102
109
|
end
|
103
110
|
|
104
111
|
def variant
|
105
|
-
image.variant(variation)
|
112
|
+
image.variant(variation)
|
113
|
+
end
|
114
|
+
|
115
|
+
def variant?
|
116
|
+
variation.transformations.present?
|
117
|
+
end
|
118
|
+
|
119
|
+
def presentation
|
120
|
+
variant? ? variant.processed : image
|
106
121
|
end
|
107
122
|
|
108
123
|
|
@@ -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,6 +53,8 @@
|
|
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
|
56
|
+
include ActiveStorage::Blob::Servable
|
57
|
+
|
54
58
|
attr_reader :blob, :variation
|
55
59
|
delegate :service, to: :blob
|
56
60
|
delegate :content_type, to: :variation
|
@@ -72,7 +76,7 @@ class ActiveStorage::Variant
|
|
72
76
|
|
73
77
|
# Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
|
74
78
|
#
|
75
|
-
# Use <tt>url_for(variant)</tt> (or the implied form, like
|
79
|
+
# 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
80
|
# for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
|
77
81
|
# for its redirection.
|
78
82
|
def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
|
@@ -89,17 +93,16 @@ class ActiveStorage::Variant
|
|
89
93
|
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
|
90
94
|
end
|
91
95
|
|
92
|
-
alias_method :content_type_for_serving, :content_type
|
93
|
-
|
94
|
-
def forced_disposition_for_serving # :nodoc:
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
|
98
96
|
# Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
|
99
97
|
def image
|
100
98
|
self
|
101
99
|
end
|
102
100
|
|
101
|
+
# Deletes variant file from service.
|
102
|
+
def destroy
|
103
|
+
service.delete(key)
|
104
|
+
end
|
105
|
+
|
103
106
|
private
|
104
107
|
def processed?
|
105
108
|
service.exist?(key)
|
@@ -1,35 +1,49 @@
|
|
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
|
8
|
+
include ActiveStorage::Blob::Servable
|
9
|
+
|
6
10
|
attr_reader :blob, :variation
|
7
11
|
delegate :service, to: :blob
|
12
|
+
delegate :content_type, to: :variation
|
8
13
|
|
9
14
|
def initialize(blob, variation)
|
10
15
|
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
|
11
16
|
end
|
12
17
|
|
13
18
|
def processed
|
14
|
-
process
|
19
|
+
process unless processed?
|
15
20
|
self
|
16
21
|
end
|
17
22
|
|
18
|
-
def
|
19
|
-
|
23
|
+
def image
|
24
|
+
record&.image
|
20
25
|
end
|
21
26
|
|
22
|
-
def
|
23
|
-
|
27
|
+
def filename
|
28
|
+
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
|
24
29
|
end
|
25
30
|
|
26
|
-
|
27
|
-
|
31
|
+
# Destroys record and deletes file from service.
|
32
|
+
def destroy
|
33
|
+
record&.destroy
|
28
34
|
end
|
29
35
|
|
30
36
|
delegate :key, :url, :download, to: :image, allow_nil: true
|
31
37
|
|
32
38
|
private
|
39
|
+
def processed?
|
40
|
+
record.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def process
|
44
|
+
transform_blob { |image| create_or_find_record(image: image) }
|
45
|
+
end
|
46
|
+
|
33
47
|
def transform_blob
|
34
48
|
blob.open do |input|
|
35
49
|
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
|
@@ -51,6 +51,6 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
|
51
51
|
setting = config.options[config.orm][:primary_key_type]
|
52
52
|
primary_key_type = setting || :primary_key
|
53
53
|
foreign_key_type = setting || :bigint
|
54
|
-
[primary_key_type, foreign_key_type]
|
54
|
+
[ primary_key_type, foreign_key_type ]
|
55
55
|
end
|
56
56
|
end
|
@@ -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
|
@@ -19,13 +19,16 @@ module ActiveStorage
|
|
19
19
|
|
20
20
|
download_blob_to_tempfile do |file|
|
21
21
|
image = instrument("vips") do
|
22
|
+
# ruby-vips will raise Vips::Error if it can't find an appropriate loader for the file
|
22
23
|
::Vips::Image.new_from_file(file.path, access: :sequential)
|
24
|
+
rescue ::Vips::Error
|
25
|
+
logger.info "Skipping image analysis because Vips doesn't support the file"
|
26
|
+
nil
|
23
27
|
end
|
24
28
|
|
25
|
-
if
|
29
|
+
if image
|
26
30
|
yield image
|
27
31
|
else
|
28
|
-
logger.info "Skipping image analysis because Vips doesn't support the file"
|
29
32
|
{}
|
30
33
|
end
|
31
34
|
rescue ::Vips::Error => error
|
@@ -40,12 +43,5 @@ module ActiveStorage
|
|
40
43
|
rescue ::Vips::Error
|
41
44
|
false
|
42
45
|
end
|
43
|
-
|
44
|
-
def valid_image?(image)
|
45
|
-
image.avg
|
46
|
-
true
|
47
|
-
rescue ::Vips::Error
|
48
|
-
false
|
49
|
-
end
|
50
46
|
end
|
51
47
|
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?
|
@@ -53,11 +55,15 @@ module ActiveStorage
|
|
53
55
|
def angle
|
54
56
|
if tags["rotate"]
|
55
57
|
Integer(tags["rotate"])
|
56
|
-
elsif
|
57
|
-
Integer(
|
58
|
+
elsif display_matrix && display_matrix["rotation"]
|
59
|
+
Integer(display_matrix["rotation"])
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
63
|
+
def display_matrix
|
64
|
+
side_data.detect { |data| data["side_data_type"] == "Display Matrix" }
|
65
|
+
end
|
66
|
+
|
61
67
|
def display_aspect_ratio
|
62
68
|
if descriptor = video_stream["display_aspect_ratio"]
|
63
69
|
if terms = descriptor.split(":", 2)
|
@@ -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,13 +94,36 @@ 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
|
|
78
120
|
def attachment_service_name
|
79
|
-
record.attachment_reflections[name].options[:service_name]
|
121
|
+
service_name = record.attachment_reflections[name].options[:service_name]
|
122
|
+
if service_name.is_a?(Proc)
|
123
|
+
service_name = service_name.call(record)
|
124
|
+
ActiveStorage::Blob.validate_service_configuration(service_name, record.class, name)
|
125
|
+
end
|
126
|
+
service_name
|
80
127
|
end
|
81
128
|
end
|
82
129
|
end
|
@@ -4,7 +4,11 @@ module ActiveStorage
|
|
4
4
|
class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne # :nodoc:
|
5
5
|
private
|
6
6
|
def find_attachment
|
7
|
-
|
7
|
+
if blob.persisted?
|
8
|
+
record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
|
9
|
+
else
|
10
|
+
blob.attachments.find { |attachment| attachment.record == record }
|
11
|
+
end
|
8
12
|
end
|
9
13
|
end
|
10
14
|
end
|
@@ -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.
|