activestorage 5.2.7.1 → 6.1.4.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +225 -93
- data/MIT-LICENSE +1 -1
- data/README.md +43 -8
- data/app/assets/javascripts/activestorage.js +5 -2
- data/app/controllers/active_storage/base_controller.rb +13 -4
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +3 -3
- data/app/controllers/active_storage/direct_uploads_controller.rb +2 -2
- data/app/controllers/active_storage/disk_controller.rb +13 -22
- data/app/controllers/active_storage/representations/base_controller.rb +14 -0
- data/app/controllers/active_storage/representations/proxy_controller.rb +13 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +3 -5
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/javascript/activestorage/blob_record.js +7 -2
- data/app/jobs/active_storage/analyze_job.rb +5 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +35 -16
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +36 -6
- data/app/models/active_storage/blob.rb +186 -68
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +37 -12
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +53 -67
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +30 -94
- data/config/routes.rb +66 -15
- data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +14 -4
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +17 -8
- data/lib/active_storage/analyzer.rb +15 -4
- data/lib/active_storage/attached/changes/create_many.rb +47 -0
- data/lib/active_storage/attached/changes/create_one.rb +82 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +27 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/many.rb +19 -12
- data/lib/active_storage/attached/model.rb +212 -0
- data/lib/active_storage/attached/one.rb +19 -21
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/engine.rb +60 -38
- data/lib/active_storage/errors.rb +25 -3
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/video_previewer.rb +17 -10
- data/lib/active_storage/previewer.rb +34 -14
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service/azure_storage_service.rb +65 -44
- data/lib/active_storage/service/configurator.rb +6 -2
- data/lib/active_storage/service/disk_service.rb +57 -44
- data/lib/active_storage/service/gcs_service.rb +68 -64
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +56 -24
- data/lib/active_storage/service.rb +44 -12
- data/lib/active_storage/transformers/image_processing_transformer.rb +45 -0
- data/lib/active_storage/transformers/transformer.rb +39 -0
- data/lib/active_storage.rb +31 -296
- data/lib/tasks/activestorage.rake +11 -0
- metadata +82 -16
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
- data/lib/active_storage/downloading.rb +0 -39
@@ -3,56 +3,47 @@
|
|
3
3
|
# Serves files stored with the disk service in the same way that the cloud services do.
|
4
4
|
# This means using expiring, signed URLs that are meant for immediate access, not permanent linking.
|
5
5
|
# Always go through the BlobsController, or your own authenticated controller, rather than directly
|
6
|
-
# to the service
|
6
|
+
# to the service URL.
|
7
7
|
class ActiveStorage::DiskController < ActiveStorage::BaseController
|
8
|
+
include ActiveStorage::FileServer
|
9
|
+
|
8
10
|
skip_forgery_protection
|
9
11
|
|
10
12
|
def show
|
11
13
|
if key = decode_verified_key
|
12
|
-
serve_file
|
14
|
+
serve_file named_disk_service(key[:service_name]).path_for(key[:key]), content_type: key[:content_type], disposition: key[:disposition]
|
13
15
|
else
|
14
16
|
head :not_found
|
15
17
|
end
|
18
|
+
rescue Errno::ENOENT
|
19
|
+
head :not_found
|
16
20
|
end
|
17
21
|
|
18
22
|
def update
|
19
23
|
if token = decode_verified_token
|
20
24
|
if acceptable_content?(token)
|
21
|
-
|
22
|
-
head :no_content
|
25
|
+
named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
|
23
26
|
else
|
24
27
|
head :unprocessable_entity
|
25
28
|
end
|
29
|
+
else
|
30
|
+
head :not_found
|
26
31
|
end
|
27
32
|
rescue ActiveStorage::IntegrityError
|
28
33
|
head :unprocessable_entity
|
29
34
|
end
|
30
35
|
|
31
36
|
private
|
32
|
-
def
|
33
|
-
ActiveStorage::Blob.
|
37
|
+
def named_disk_service(name)
|
38
|
+
ActiveStorage::Blob.services.fetch(name) do
|
39
|
+
ActiveStorage::Blob.service
|
40
|
+
end
|
34
41
|
end
|
35
42
|
|
36
|
-
|
37
43
|
def decode_verified_key
|
38
44
|
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
39
45
|
end
|
40
46
|
|
41
|
-
def serve_file(path, content_type:, disposition:)
|
42
|
-
Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
|
43
|
-
self.status = status
|
44
|
-
self.response_body = body
|
45
|
-
|
46
|
-
headers.each do |name, value|
|
47
|
-
response.headers[name] = value
|
48
|
-
end
|
49
|
-
|
50
|
-
response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
|
51
|
-
response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
47
|
def decode_verified_token
|
57
48
|
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
58
49
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveStorage::Representations::BaseController < ActiveStorage::BaseController #:nodoc:
|
4
|
+
include ActiveStorage::SetBlob
|
5
|
+
|
6
|
+
before_action :set_representation
|
7
|
+
|
8
|
+
private
|
9
|
+
def set_representation
|
10
|
+
@representation = @blob.representation(params[:variation_key]).processed
|
11
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
12
|
+
head :not_found
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Proxy files through application. This avoids having a redirect and makes files easier to cache.
|
4
|
+
class ActiveStorage::Representations::ProxyController < ActiveStorage::Representations::BaseController
|
5
|
+
include ActiveStorage::SetHeaders
|
6
|
+
|
7
|
+
def show
|
8
|
+
http_cache_forever public: true do
|
9
|
+
set_content_headers_from @representation.image
|
10
|
+
stream @representation
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -4,11 +4,9 @@
|
|
4
4
|
# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
|
5
5
|
# security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own
|
6
6
|
# authenticated redirection controller.
|
7
|
-
class ActiveStorage::
|
8
|
-
include ActiveStorage::SetBlob
|
9
|
-
|
7
|
+
class ActiveStorage::Representations::RedirectController < ActiveStorage::Representations::BaseController
|
10
8
|
def show
|
11
|
-
expires_in ActiveStorage
|
12
|
-
redirect_to @
|
9
|
+
expires_in ActiveStorage.service_urls_expire_in
|
10
|
+
redirect_to @representation.url(disposition: params[:disposition])
|
13
11
|
end
|
14
12
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage::FileServer # :nodoc:
|
4
|
+
private
|
5
|
+
def serve_file(path, content_type:, disposition:)
|
6
|
+
Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
|
7
|
+
self.status = status
|
8
|
+
self.response_body = body
|
9
|
+
|
10
|
+
headers.each do |name, value|
|
11
|
+
response.headers[name] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
|
15
|
+
response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -9,7 +9,7 @@ module ActiveStorage::SetBlob #:nodoc:
|
|
9
9
|
|
10
10
|
private
|
11
11
|
def set_blob
|
12
|
-
@blob = ActiveStorage::Blob.find_signed(params[:signed_blob_id] || params[:signed_id])
|
12
|
+
@blob = ActiveStorage::Blob.find_signed!(params[:signed_blob_id] || params[:signed_id])
|
13
13
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
14
14
|
head :not_found
|
15
15
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Sets the <tt>ActiveStorage::Current.host</tt> attribute, which the disk service uses to generate URLs.
|
4
|
+
# Include this concern in custom controllers that call ActiveStorage::Blob#url,
|
5
|
+
# ActiveStorage::Variant#url, or ActiveStorage::Preview#url so the disk service can
|
6
|
+
# generate URLs using the same host, protocol, and base path as the current request.
|
7
|
+
module ActiveStorage::SetCurrent
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
before_action do
|
12
|
+
ActiveStorage::Current.host = request.base_url
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage::SetHeaders #:nodoc:
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
private
|
7
|
+
def set_content_headers_from(blob)
|
8
|
+
response.headers["Content-Type"] = blob.content_type_for_serving
|
9
|
+
response.headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format \
|
10
|
+
disposition: blob.forced_disposition_for_serving || params[:disposition] || "inline", filename: blob.filename.sanitized
|
11
|
+
end
|
12
|
+
end
|
@@ -6,7 +6,7 @@ export class BlobRecord {
|
|
6
6
|
|
7
7
|
this.attributes = {
|
8
8
|
filename: file.name,
|
9
|
-
content_type: file.type,
|
9
|
+
content_type: file.type || "application/octet-stream",
|
10
10
|
byte_size: file.size,
|
11
11
|
checksum: checksum
|
12
12
|
}
|
@@ -17,7 +17,12 @@ export class BlobRecord {
|
|
17
17
|
this.xhr.setRequestHeader("Content-Type", "application/json")
|
18
18
|
this.xhr.setRequestHeader("Accept", "application/json")
|
19
19
|
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
20
|
-
|
20
|
+
|
21
|
+
const csrfToken = getMetaValue("csrf-token")
|
22
|
+
if (csrfToken != undefined) {
|
23
|
+
this.xhr.setRequestHeader("X-CSRF-Token", csrfToken)
|
24
|
+
}
|
25
|
+
|
21
26
|
this.xhr.addEventListener("load", event => this.requestDidLoad(event))
|
22
27
|
this.xhr.addEventListener("error", event => this.requestDidError(event))
|
23
28
|
}
|
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
# Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later.
|
4
4
|
class ActiveStorage::AnalyzeJob < ActiveStorage::BaseJob
|
5
|
+
queue_as { ActiveStorage.queues[:analysis] }
|
6
|
+
|
7
|
+
discard_on ActiveRecord::RecordNotFound
|
8
|
+
retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer
|
9
|
+
|
5
10
|
def perform(blob)
|
6
11
|
blob.analyze
|
7
12
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/try"
|
4
|
+
|
5
|
+
# Provides asynchronous mirroring of directly-uploaded blobs.
|
6
|
+
class ActiveStorage::MirrorJob < ActiveStorage::BaseJob
|
7
|
+
queue_as { ActiveStorage.queues[:mirror] }
|
8
|
+
|
9
|
+
discard_on ActiveStorage::FileNotFoundError
|
10
|
+
retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer
|
11
|
+
|
12
|
+
def perform(key, checksum:)
|
13
|
+
ActiveStorage::Blob.service.try(:mirror, key, checksum: checksum)
|
14
|
+
end
|
15
|
+
end
|
@@ -2,7 +2,10 @@
|
|
2
2
|
|
3
3
|
# Provides asynchronous purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later.
|
4
4
|
class ActiveStorage::PurgeJob < ActiveStorage::BaseJob
|
5
|
+
queue_as { ActiveStorage.queues[:purge] }
|
6
|
+
|
5
7
|
discard_on ActiveRecord::RecordNotFound
|
8
|
+
retry_on ActiveRecord::Deadlocked, attempts: 10, wait: :exponentially_longer
|
6
9
|
|
7
10
|
def perform(blob)
|
8
11
|
blob.purge
|
@@ -3,37 +3,56 @@
|
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
4
|
|
5
5
|
# Attachments associate records with blobs. Usually that's a one record-many blobs relationship,
|
6
|
-
# but it is possible to associate many different records with the same blob.
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
6
|
+
# but it is possible to associate many different records with the same blob. A foreign-key constraint
|
7
|
+
# on the attachments table prevents blobs from being purged if they’re still attached to any records.
|
8
|
+
#
|
9
|
+
# Attachments also have access to all methods from {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob].
|
10
|
+
class ActiveStorage::Attachment < ActiveStorage::Record
|
10
11
|
self.table_name = "active_storage_attachments"
|
11
12
|
|
12
13
|
belongs_to :record, polymorphic: true, touch: true
|
13
|
-
belongs_to :blob, class_name: "ActiveStorage::Blob"
|
14
|
+
belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true
|
14
15
|
|
15
16
|
delegate_missing_to :blob
|
17
|
+
delegate :signed_id, to: :blob
|
16
18
|
|
17
|
-
after_create_commit :
|
19
|
+
after_create_commit :mirror_blob_later, :analyze_blob_later
|
20
|
+
after_destroy_commit :purge_dependent_blob_later
|
18
21
|
|
19
|
-
# Synchronously
|
22
|
+
# Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
|
20
23
|
def purge
|
21
|
-
|
22
|
-
|
24
|
+
transaction do
|
25
|
+
delete
|
26
|
+
record&.touch
|
27
|
+
end
|
28
|
+
blob&.purge
|
23
29
|
end
|
24
30
|
|
25
|
-
#
|
31
|
+
# Deletes the attachment and {enqueues a background job}[rdoc-ref:ActiveStorage::Blob#purge_later] to purge the blob.
|
26
32
|
def purge_later
|
27
|
-
|
28
|
-
|
33
|
+
transaction do
|
34
|
+
delete
|
35
|
+
record&.touch
|
36
|
+
end
|
37
|
+
blob&.purge_later
|
29
38
|
end
|
30
39
|
|
31
40
|
private
|
32
|
-
def identify_blob
|
33
|
-
blob.identify
|
34
|
-
end
|
35
|
-
|
36
41
|
def analyze_blob_later
|
37
42
|
blob.analyze_later unless blob.analyzed?
|
38
43
|
end
|
44
|
+
|
45
|
+
def mirror_blob_later
|
46
|
+
blob.mirror_later
|
47
|
+
end
|
48
|
+
|
49
|
+
def purge_dependent_blob_later
|
50
|
+
blob&.purge_later if dependent == :purge_later
|
51
|
+
end
|
52
|
+
|
53
|
+
def dependent
|
54
|
+
record.attachment_reflections[name]&.options[:dependent]
|
55
|
+
end
|
39
56
|
end
|
57
|
+
|
58
|
+
ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment
|
@@ -29,12 +29,16 @@ module ActiveStorage::Blob::Analyzable
|
|
29
29
|
update! metadata: metadata.merge(extract_metadata_via_analyzer)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Enqueues an ActiveStorage::AnalyzeJob which calls #analyze.
|
32
|
+
# Enqueues an ActiveStorage::AnalyzeJob which calls #analyze, or calls #analyze inline based on analyzer class configuration.
|
33
33
|
#
|
34
34
|
# This method is automatically called for a blob when it's attached for the first time. You can call it to analyze a blob
|
35
35
|
# again (e.g. if you add a new analyzer or modify an existing one).
|
36
36
|
def analyze_later
|
37
|
-
|
37
|
+
if analyzer_class.analyze_later?
|
38
|
+
ActiveStorage::AnalyzeJob.perform_later(self)
|
39
|
+
else
|
40
|
+
analyze
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# Returns true if the blob has been analyzed.
|
@@ -2,9 +2,14 @@
|
|
2
2
|
|
3
3
|
module ActiveStorage::Blob::Identifiable
|
4
4
|
def identify
|
5
|
+
identify_without_saving
|
6
|
+
save!
|
7
|
+
end
|
8
|
+
|
9
|
+
def identify_without_saving
|
5
10
|
unless identified?
|
6
|
-
|
7
|
-
|
11
|
+
self.content_type = identify_content_type
|
12
|
+
self.identified = true
|
8
13
|
end
|
9
14
|
end
|
10
15
|
|
@@ -24,8 +29,4 @@ module ActiveStorage::Blob::Identifiable
|
|
24
29
|
""
|
25
30
|
end
|
26
31
|
end
|
27
|
-
|
28
|
-
def update_service_metadata
|
29
|
-
service.update_metadata key, service_metadata if service_metadata.any?
|
30
|
-
end
|
31
32
|
end
|
@@ -1,16 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "mini_mime"
|
4
|
+
|
3
5
|
module ActiveStorage::Blob::Representable
|
4
6
|
extend ActiveSupport::Concern
|
5
7
|
|
6
8
|
included do
|
9
|
+
has_many :variant_records, class_name: "ActiveStorage::VariantRecord", dependent: false
|
10
|
+
before_destroy { variant_records.destroy_all if ActiveStorage.track_variants }
|
11
|
+
|
7
12
|
has_one_attached :preview_image
|
8
13
|
end
|
9
14
|
|
10
15
|
# Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
|
11
16
|
# files, and it allows any image to be transformed for size, colors, and the like. Example:
|
12
17
|
#
|
13
|
-
# avatar.variant(
|
18
|
+
# avatar.variant(resize_to_limit: [100, 100]).processed.url
|
14
19
|
#
|
15
20
|
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
|
16
21
|
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
|
@@ -18,7 +23,7 @@ module ActiveStorage::Blob::Representable
|
|
18
23
|
# Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
|
19
24
|
# specific variant that can be created by a controller on-demand. Like so:
|
20
25
|
#
|
21
|
-
# <%= image_tag Current.user.avatar.variant(
|
26
|
+
# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
|
22
27
|
#
|
23
28
|
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
|
24
29
|
# can then produce on-demand.
|
@@ -27,7 +32,7 @@ module ActiveStorage::Blob::Representable
|
|
27
32
|
# variable, call ActiveStorage::Blob#variable?.
|
28
33
|
def variant(transformations)
|
29
34
|
if variable?
|
30
|
-
|
35
|
+
variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
|
31
36
|
else
|
32
37
|
raise ActiveStorage::InvariableError
|
33
38
|
end
|
@@ -43,13 +48,13 @@ module ActiveStorage::Blob::Representable
|
|
43
48
|
# from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
|
44
49
|
# extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
|
45
50
|
#
|
46
|
-
# blob.preview(
|
51
|
+
# blob.preview(resize_to_limit: [100, 100]).processed.url
|
47
52
|
#
|
48
53
|
# Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
|
49
54
|
# Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
|
50
55
|
# how to use the built-in version:
|
51
56
|
#
|
52
|
-
# <%= image_tag video.preview(
|
57
|
+
# <%= image_tag video.preview(resize_to_limit: [100, 100]) %>
|
53
58
|
#
|
54
59
|
# This method raises ActiveStorage::UnpreviewableError if no previewer accepts the receiving blob. To determine
|
55
60
|
# whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
|
@@ -69,7 +74,7 @@ module ActiveStorage::Blob::Representable
|
|
69
74
|
|
70
75
|
# Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob.
|
71
76
|
#
|
72
|
-
# blob.representation(
|
77
|
+
# blob.representation(resize_to_limit: [100, 100]).processed.url
|
73
78
|
#
|
74
79
|
# Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable nor previewable. Call
|
75
80
|
# ActiveStorage::Blob#representable? to determine whether a blob is representable.
|
@@ -90,4 +95,29 @@ module ActiveStorage::Blob::Representable
|
|
90
95
|
def representable?
|
91
96
|
variable? || previewable?
|
92
97
|
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def default_variant_transformations
|
101
|
+
{ format: default_variant_format }
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_variant_format
|
105
|
+
if web_image?
|
106
|
+
format || :png
|
107
|
+
else
|
108
|
+
:png
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def format
|
113
|
+
if filename.extension.present? && MiniMime.lookup_by_extension(filename.extension)&.content_type == content_type
|
114
|
+
filename.extension
|
115
|
+
else
|
116
|
+
MiniMime.lookup_by_content_type(content_type)&.extension
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def variant_class
|
121
|
+
ActiveStorage.track_variants ? ActiveStorage::VariantWithRecord : ActiveStorage::Variant
|
122
|
+
end
|
93
123
|
end
|