activestorage 6.1.4.1 → 7.0.0.rc2

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.

Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +144 -204
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +25 -11
  5. data/app/assets/javascripts/activestorage.esm.js +856 -0
  6. data/app/assets/javascripts/activestorage.js +270 -377
  7. data/app/controllers/active_storage/base_controller.rb +1 -10
  8. data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
  9. data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
  10. data/app/controllers/active_storage/direct_uploads_controller.rb +7 -1
  11. data/app/controllers/active_storage/disk_controller.rb +1 -0
  12. data/app/controllers/active_storage/representations/base_controller.rb +5 -1
  13. data/app/controllers/active_storage/representations/proxy_controller.rb +6 -4
  14. data/app/controllers/active_storage/representations/redirect_controller.rb +6 -4
  15. data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
  16. data/app/controllers/concerns/active_storage/set_current.rb +3 -3
  17. data/app/controllers/concerns/active_storage/streaming.rb +65 -0
  18. data/app/javascript/activestorage/blob_record.js +10 -3
  19. data/app/javascript/activestorage/direct_upload.js +4 -2
  20. data/app/javascript/activestorage/direct_upload_controller.js +9 -1
  21. data/app/javascript/activestorage/ujs.js +1 -1
  22. data/app/models/active_storage/attachment.rb +36 -3
  23. data/app/models/active_storage/blob/representable.rb +7 -5
  24. data/app/models/active_storage/blob.rb +92 -36
  25. data/app/models/active_storage/current.rb +12 -2
  26. data/app/models/active_storage/preview.rb +6 -4
  27. data/app/models/active_storage/record.rb +1 -1
  28. data/app/models/active_storage/variant.rb +3 -6
  29. data/app/models/active_storage/variant_record.rb +2 -0
  30. data/app/models/active_storage/variant_with_record.rb +9 -5
  31. data/app/models/active_storage/variation.rb +2 -2
  32. data/config/routes.rb +10 -10
  33. data/db/migrate/20170806125915_create_active_storage_tables.rb +32 -11
  34. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
  35. data/db/update_migrate/20211119233751_remove_not_null_on_active_storage_blobs_checksum.rb +5 -0
  36. data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
  37. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
  38. data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
  39. data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
  40. data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
  41. data/lib/active_storage/analyzer.rb +8 -4
  42. data/lib/active_storage/attached/changes/create_many.rb +7 -3
  43. data/lib/active_storage/attached/changes/create_one.rb +1 -1
  44. data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
  45. data/lib/active_storage/attached/changes/delete_many.rb +1 -1
  46. data/lib/active_storage/attached/changes/delete_one.rb +1 -1
  47. data/lib/active_storage/attached/changes/detach_many.rb +18 -0
  48. data/lib/active_storage/attached/changes/detach_one.rb +24 -0
  49. data/lib/active_storage/attached/changes/purge_many.rb +27 -0
  50. data/lib/active_storage/attached/changes/purge_one.rb +27 -0
  51. data/lib/active_storage/attached/changes.rb +7 -1
  52. data/lib/active_storage/attached/many.rb +27 -15
  53. data/lib/active_storage/attached/model.rb +31 -5
  54. data/lib/active_storage/attached/one.rb +32 -27
  55. data/lib/active_storage/direct_upload_token.rb +59 -0
  56. data/lib/active_storage/downloader.rb +4 -4
  57. data/lib/active_storage/engine.rb +30 -1
  58. data/lib/active_storage/errors.rb +3 -0
  59. data/lib/active_storage/fixture_set.rb +76 -0
  60. data/lib/active_storage/gem_version.rb +4 -4
  61. data/lib/active_storage/previewer.rb +4 -4
  62. data/lib/active_storage/reflection.rb +12 -2
  63. data/lib/active_storage/service/azure_storage_service.rb +28 -6
  64. data/lib/active_storage/service/configurator.rb +1 -1
  65. data/lib/active_storage/service/disk_service.rb +24 -19
  66. data/lib/active_storage/service/gcs_service.rb +109 -11
  67. data/lib/active_storage/service/mirror_service.rb +2 -2
  68. data/lib/active_storage/service/registry.rb +1 -1
  69. data/lib/active_storage/service/s3_service.rb +37 -15
  70. data/lib/active_storage/service.rb +13 -5
  71. data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
  72. data/lib/active_storage/transformers/transformer.rb +1 -1
  73. data/lib/active_storage.rb +6 -1
  74. metadata +31 -19
  75. data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -2,18 +2,9 @@
2
2
 
3
3
  # The base class for all Active Storage controllers.
4
4
  class ActiveStorage::BaseController < ActionController::Base
5
- include ActiveStorage::SetCurrent
5
+ include ActiveStorage::SetCurrent, ActiveStorage::Streaming
6
6
 
7
7
  protect_from_forgery with: :exception
8
8
 
9
9
  self.etag_with_template_digest = false
10
-
11
- private
12
- def stream(blob)
13
- blob.download do |chunk|
14
- response.stream.write chunk
15
- end
16
- ensure
17
- response.stream.close
18
- end
19
10
  end
@@ -1,14 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Proxy files through application. This avoids having a redirect and makes files easier to cache.
4
+ #
5
+ # WARNING: All Active Storage controllers are publicly accessible by default. The
6
+ # generated URLs are hard to guess, but permanent by design. If your files
7
+ # require a higher level of protection consider implementing
8
+ # {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
4
9
  class ActiveStorage::Blobs::ProxyController < ActiveStorage::BaseController
5
10
  include ActiveStorage::SetBlob
6
- include ActiveStorage::SetHeaders
7
11
 
8
12
  def show
9
- http_cache_forever public: true do
10
- set_content_headers_from @blob
11
- stream @blob
13
+ if request.headers["Range"].present?
14
+ send_blob_byte_range_data @blob, request.headers["Range"]
15
+ else
16
+ http_cache_forever public: true do
17
+ response.headers["Accept-Ranges"] = "bytes"
18
+ response.headers["Content-Length"] = @blob.byte_size.to_s
19
+
20
+ send_blob_stream @blob
21
+ end
12
22
  end
13
23
  end
14
24
  end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Take a signed permanent reference for a blob and turn it into an expiring service URL for download.
4
- # Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
5
- # security-through-obscurity factor of the signed blob references, you'll need to implement your own
6
- # authenticated redirection controller.
4
+ #
5
+ # WARNING: All Active Storage controllers are publicly accessible by default. The
6
+ # generated URLs are hard to guess, but permanent by design. If your files
7
+ # require a higher level of protection consider implementing
8
+ # {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
7
9
  class ActiveStorage::Blobs::RedirectController < ActiveStorage::BaseController
8
10
  include ActiveStorage::SetBlob
9
11
 
10
12
  def show
11
13
  expires_in ActiveStorage.service_urls_expire_in
12
- redirect_to @blob.url(disposition: params[:disposition])
14
+ redirect_to @blob.url(disposition: params[:disposition]), allow_other_host: true
13
15
  end
14
16
  end
@@ -4,8 +4,10 @@
4
4
  # When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference
5
5
  # the blob that was created up front.
6
6
  class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
7
+ include ActiveStorage::DirectUploadToken
8
+
7
9
  def create
8
- blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
10
+ blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args.merge(service_name: verified_service_name))
9
11
  render json: direct_upload_json(blob)
10
12
  end
11
13
 
@@ -14,6 +16,10 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
14
16
  params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
15
17
  end
16
18
 
19
+ def verified_service_name
20
+ ActiveStorage::DirectUploadToken.verify_direct_upload_token(params[:direct_upload_token], params[:attachment_name], session)
21
+ end
22
+
17
23
  def direct_upload_json(blob)
18
24
  blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
19
25
  url: blob.service_url_for_direct_upload,
@@ -23,6 +23,7 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
23
23
  if token = decode_verified_token
24
24
  if acceptable_content?(token)
25
25
  named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
26
+ head :no_content
26
27
  else
27
28
  head :unprocessable_entity
28
29
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ActiveStorage::Representations::BaseController < ActiveStorage::BaseController #:nodoc:
3
+ class ActiveStorage::Representations::BaseController < ActiveStorage::BaseController # :nodoc:
4
4
  include ActiveStorage::SetBlob
5
5
 
6
6
  before_action :set_representation
7
7
 
8
8
  private
9
+ def blob_scope
10
+ ActiveStorage::Blob.scope_for_strict_loading
11
+ end
12
+
9
13
  def set_representation
10
14
  @representation = @blob.representation(params[:variation_key]).processed
11
15
  rescue ActiveSupport::MessageVerifier::InvalidSignature
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Proxy files through application. This avoids having a redirect and makes files easier to cache.
4
+ #
5
+ # WARNING: All Active Storage controllers are publicly accessible by default. The
6
+ # generated URLs are hard to guess, but permanent by design. If your files
7
+ # require a higher level of protection consider implementing
8
+ # {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
4
9
  class ActiveStorage::Representations::ProxyController < ActiveStorage::Representations::BaseController
5
- include ActiveStorage::SetHeaders
6
-
7
10
  def show
8
11
  http_cache_forever public: true do
9
- set_content_headers_from @representation.image
10
- stream @representation
12
+ send_blob_stream @representation.image
11
13
  end
12
14
  end
13
15
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Take a signed permanent reference for a blob representation and turn it into an expiring service URL for download.
4
- # Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
5
- # security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own
6
- # authenticated redirection controller.
4
+ #
5
+ # WARNING: All Active Storage controllers are publicly accessible by default. The
6
+ # generated URLs are hard to guess, but permanent by design. If your files
7
+ # require a higher level of protection consider implementing
8
+ # {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
7
9
  class ActiveStorage::Representations::RedirectController < ActiveStorage::Representations::BaseController
8
10
  def show
9
11
  expires_in ActiveStorage.service_urls_expire_in
10
- redirect_to @representation.url(disposition: params[:disposition])
12
+ redirect_to @representation.url(disposition: params[:disposition]), allow_other_host: true
11
13
  end
12
14
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveStorage::SetBlob #:nodoc:
3
+ module ActiveStorage::SetBlob # :nodoc:
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
@@ -9,8 +9,12 @@ 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 = blob_scope.find_signed!(params[:signed_blob_id] || params[:signed_id])
13
13
  rescue ActiveSupport::MessageVerifier::InvalidSignature
14
14
  head :not_found
15
15
  end
16
+
17
+ def blob_scope
18
+ ActiveStorage::Blob
19
+ end
16
20
  end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Sets the <tt>ActiveStorage::Current.host</tt> attribute, which the disk service uses to generate URLs.
3
+ # Sets the <tt>ActiveStorage::Current.url_options</tt> attribute, which the disk service uses to generate URLs.
4
4
  # Include this concern in custom controllers that call ActiveStorage::Blob#url,
5
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.
6
+ # generate URLs using the same host, protocol, and port as the current request.
7
7
  module ActiveStorage::SetCurrent
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  included do
11
11
  before_action do
12
- ActiveStorage::Current.host = request.base_url
12
+ ActiveStorage::Current.url_options = { protocol: request.protocol, host: request.host, port: request.port }
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module ActiveStorage::Streaming
6
+ DEFAULT_BLOB_STREAMING_DISPOSITION = "inline"
7
+
8
+ include ActionController::DataStreaming
9
+ include ActionController::Live
10
+
11
+ private
12
+ # Stream the blob in byte ranges specified through the header
13
+ def send_blob_byte_range_data(blob, range_header, disposition: nil)
14
+ ranges = Rack::Utils.get_byte_ranges(range_header, blob.byte_size)
15
+
16
+ return head(:range_not_satisfiable) if ranges.blank? || ranges.all?(&:blank?)
17
+
18
+ if ranges.length == 1
19
+ range = ranges.first
20
+ content_type = blob.content_type_for_serving
21
+ data = blob.download_chunk(range)
22
+
23
+ response.headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{blob.byte_size}"
24
+ else
25
+ boundary = SecureRandom.hex
26
+ content_type = "multipart/byteranges; boundary=#{boundary}"
27
+ data = +""
28
+
29
+ ranges.compact.each do |range|
30
+ chunk = blob.download_chunk(range)
31
+
32
+ data << "\r\n--#{boundary}\r\n"
33
+ data << "Content-Type: #{blob.content_type_for_serving}\r\n"
34
+ data << "Content-Range: bytes #{range.begin}-#{range.end}/#{blob.byte_size}\r\n\r\n"
35
+ data << chunk
36
+ end
37
+
38
+ data << "\r\n--#{boundary}--\r\n"
39
+ end
40
+
41
+ response.headers["Accept-Ranges"] = "bytes"
42
+ response.headers["Content-Length"] = data.length.to_s
43
+
44
+ send_data(
45
+ data,
46
+ disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
47
+ filename: blob.filename.sanitized,
48
+ status: :partial_content,
49
+ type: content_type
50
+ )
51
+ end
52
+
53
+ # Stream the blob from storage directly to the response. The disposition can be controlled by setting +disposition+.
54
+ # The content type and filename is set directly from the +blob+.
55
+ def send_blob_stream(blob, disposition: nil) # :doc:
56
+ send_stream(
57
+ filename: blob.filename.sanitized,
58
+ disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
59
+ type: blob.content_type_for_serving) do |stream|
60
+ blob.download do |chunk|
61
+ stream.write chunk
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,16 +1,19 @@
1
1
  import { getMetaValue } from "./helpers"
2
2
 
3
3
  export class BlobRecord {
4
- constructor(file, checksum, url) {
4
+ constructor(file, checksum, url, directUploadToken, attachmentName) {
5
5
  this.file = file
6
6
 
7
7
  this.attributes = {
8
8
  filename: file.name,
9
9
  content_type: file.type || "application/octet-stream",
10
10
  byte_size: file.size,
11
- checksum: checksum
11
+ checksum: checksum,
12
12
  }
13
13
 
14
+ this.directUploadToken = directUploadToken
15
+ this.attachmentName = attachmentName
16
+
14
17
  this.xhr = new XMLHttpRequest
15
18
  this.xhr.open("POST", url, true)
16
19
  this.xhr.responseType = "json"
@@ -43,7 +46,11 @@ export class BlobRecord {
43
46
 
44
47
  create(callback) {
45
48
  this.callback = callback
46
- this.xhr.send(JSON.stringify({ blob: this.attributes }))
49
+ this.xhr.send(JSON.stringify({
50
+ blob: this.attributes,
51
+ direct_upload_token: this.directUploadToken,
52
+ attachment_name: this.attachmentName
53
+ }))
47
54
  }
48
55
 
49
56
  requestDidLoad(event) {
@@ -5,10 +5,12 @@ import { BlobUpload } from "./blob_upload"
5
5
  let id = 0
6
6
 
7
7
  export class DirectUpload {
8
- constructor(file, url, delegate) {
8
+ constructor(file, url, serviceName, attachmentName, delegate) {
9
9
  this.id = ++id
10
10
  this.file = file
11
11
  this.url = url
12
+ this.serviceName = serviceName
13
+ this.attachmentName = attachmentName
12
14
  this.delegate = delegate
13
15
  }
14
16
 
@@ -19,7 +21,7 @@ export class DirectUpload {
19
21
  return
20
22
  }
21
23
 
22
- const blob = new BlobRecord(this.file, checksum, this.url)
24
+ const blob = new BlobRecord(this.file, checksum, this.url, this.serviceName, this.attachmentName)
23
25
  notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
24
26
 
25
27
  blob.create(error => {
@@ -5,7 +5,7 @@ export class DirectUploadController {
5
5
  constructor(input, file) {
6
6
  this.input = input
7
7
  this.file = file
8
- this.directUpload = new DirectUpload(this.file, this.url, this)
8
+ this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this)
9
9
  this.dispatch("initialize")
10
10
  }
11
11
 
@@ -41,6 +41,14 @@ export class DirectUploadController {
41
41
  return this.input.getAttribute("data-direct-upload-url")
42
42
  }
43
43
 
44
+ get directUploadToken() {
45
+ return this.input.getAttribute("data-direct-upload-token")
46
+ }
47
+
48
+ get attachmentName() {
49
+ return this.input.getAttribute("data-direct-upload-attachment-name")
50
+ }
51
+
44
52
  dispatch(name, detail = {}) {
45
53
  detail.file = this.file
46
54
  detail.id = this.directUpload.id
@@ -9,7 +9,7 @@ export function start() {
9
9
  if (!started) {
10
10
  started = true
11
11
  document.addEventListener("click", didClick, true)
12
- document.addEventListener("submit", didSubmitForm)
12
+ document.addEventListener("submit", didSubmitForm, true)
13
13
  document.addEventListener("ajax:before", didSubmitRemoteElement)
14
14
  }
15
15
  }
@@ -7,6 +7,14 @@ require "active_support/core_ext/module/delegation"
7
7
  # on the attachments table prevents blobs from being purged if they’re still attached to any records.
8
8
  #
9
9
  # Attachments also have access to all methods from {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob].
10
+ #
11
+ # If you wish to preload attachments or blobs, you can use these scopes:
12
+ #
13
+ # # preloads attachments, their corresponding blobs, and variant records (if using `ActiveStorage.track_variants`)
14
+ # User.all.with_attached_avatars
15
+ #
16
+ # # preloads blobs and variant records (if using `ActiveStorage.track_variants`)
17
+ # User.first.avatars.with_all_variant_records
10
18
  class ActiveStorage::Attachment < ActiveStorage::Record
11
19
  self.table_name = "active_storage_attachments"
12
20
 
@@ -19,11 +27,13 @@ class ActiveStorage::Attachment < ActiveStorage::Record
19
27
  after_create_commit :mirror_blob_later, :analyze_blob_later
20
28
  after_destroy_commit :purge_dependent_blob_later
21
29
 
30
+ scope :with_all_variant_records, -> { includes(blob: :variant_records) }
31
+
22
32
  # Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
23
33
  def purge
24
34
  transaction do
25
35
  delete
26
- record&.touch
36
+ record.touch if record&.persisted?
27
37
  end
28
38
  blob&.purge
29
39
  end
@@ -32,11 +42,30 @@ class ActiveStorage::Attachment < ActiveStorage::Record
32
42
  def purge_later
33
43
  transaction do
34
44
  delete
35
- record&.touch
45
+ record.touch if record&.persisted?
36
46
  end
37
47
  blob&.purge_later
38
48
  end
39
49
 
50
+ # Returns an ActiveStorage::Variant or ActiveStorage::VariantWithRecord
51
+ # instance for the attachment with the set of +transformations+ provided.
52
+ # See ActiveStorage::Blob::Representable#variant for more information.
53
+ #
54
+ # Raises an +ArgumentError+ if +transformations+ is a +Symbol+ which is an
55
+ # unknown pre-defined variant of the attachment.
56
+ def variant(transformations)
57
+ case transformations
58
+ when Symbol
59
+ variant_name = transformations
60
+ transformations = variants.fetch(variant_name) do
61
+ record_model_name = record.to_model.model_name.name
62
+ raise ArgumentError, "Cannot find variant :#{variant_name} for #{record_model_name}##{name}"
63
+ end
64
+ end
65
+
66
+ blob.variant(transformations)
67
+ end
68
+
40
69
  private
41
70
  def analyze_blob_later
42
71
  blob.analyze_later unless blob.analyzed?
@@ -51,7 +80,11 @@ class ActiveStorage::Attachment < ActiveStorage::Record
51
80
  end
52
81
 
53
82
  def dependent
54
- record.attachment_reflections[name]&.options[:dependent]
83
+ record.attachment_reflections[name]&.options&.fetch(:dependent, nil)
84
+ end
85
+
86
+ def variants
87
+ record.attachment_reflections[name]&.variants
55
88
  end
56
89
  end
57
90
 
@@ -12,8 +12,8 @@ module ActiveStorage::Blob::Representable
12
12
  has_one_attached :preview_image
13
13
  end
14
14
 
15
- # Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
16
- # files, and it allows any image to be transformed for size, colors, and the like. Example:
15
+ # Returns an ActiveStorage::Variant or ActiveStorage::VariantWithRecord instance with the set of +transformations+ provided.
16
+ # This is only relevant for image files, and it allows any image to be transformed for size, colors, and the like. Example:
17
17
  #
18
18
  # avatar.variant(resize_to_limit: [100, 100]).processed.url
19
19
  #
@@ -28,8 +28,9 @@ module ActiveStorage::Blob::Representable
28
28
  # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
29
29
  # can then produce on-demand.
30
30
  #
31
- # Raises ActiveStorage::InvariableError if ImageMagick cannot transform the blob. To determine whether a blob is
32
- # variable, call ActiveStorage::Blob#variable?.
31
+ # Raises ActiveStorage::InvariableError if the variant processor cannot
32
+ # transform the blob. To determine whether a blob is variable, call
33
+ # ActiveStorage::Blob#variable?.
33
34
  def variant(transformations)
34
35
  if variable?
35
36
  variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
@@ -38,7 +39,8 @@ module ActiveStorage::Blob::Representable
38
39
  end
39
40
  end
40
41
 
41
- # Returns true if ImageMagick can transform the blob (its content type is in +ActiveStorage.variable_content_types+).
42
+ # Returns true if the variant processor can transform the blob (its content
43
+ # type is in +ActiveStorage.variable_content_types+).
42
44
  def variable?
43
45
  ActiveStorage.variable_content_types.include?(content_type)
44
46
  end