activestorage 8.0.2.1 → 8.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -54
  3. data/README.md +6 -3
  4. data/app/assets/javascripts/activestorage.esm.js +37 -1
  5. data/app/assets/javascripts/activestorage.js +37 -1
  6. data/app/controllers/active_storage/disk_controller.rb +2 -2
  7. data/app/javascript/activestorage/direct_upload_controller.js +48 -1
  8. data/app/models/active_storage/blob/representable.rb +66 -0
  9. data/app/models/active_storage/blob.rb +1 -1
  10. data/app/models/active_storage/filename.rb +1 -0
  11. data/app/models/active_storage/variant.rb +7 -7
  12. data/app/models/active_storage/variation.rb +1 -1
  13. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +7 -11
  14. data/lib/active_storage/analyzer/image_analyzer/vips.rb +10 -11
  15. data/lib/active_storage/analyzer/image_analyzer.rb +5 -0
  16. data/lib/active_storage/attached/model.rb +4 -4
  17. data/lib/active_storage/attached.rb +0 -1
  18. data/lib/active_storage/downloader.rb +1 -1
  19. data/lib/active_storage/engine.rb +48 -10
  20. data/lib/active_storage/fixture_set.rb +1 -1
  21. data/lib/active_storage/gem_version.rb +3 -3
  22. data/lib/active_storage/service/configurator.rb +6 -0
  23. data/lib/active_storage/service/disk_service.rb +1 -1
  24. data/lib/active_storage/service/gcs_service.rb +10 -2
  25. data/lib/active_storage/service/mirror_service.rb +1 -1
  26. data/lib/active_storage/service/registry.rb +6 -0
  27. data/lib/active_storage/service/s3_service.rb +19 -4
  28. data/lib/active_storage/service.rb +4 -1
  29. data/lib/active_storage/transformers/image_magick.rb +72 -0
  30. data/lib/active_storage/transformers/image_processing_transformer.rb +5 -67
  31. data/lib/active_storage/transformers/vips.rb +11 -0
  32. data/lib/active_storage.rb +13 -0
  33. metadata +14 -13
  34. data/lib/active_storage/service/azure_storage_service.rb +0 -201
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bbd02c5f4a3b3d7b0944ed4846eaf10e7732224767d0ba3bbab219397ea2dee
4
- data.tar.gz: 74d1f142bbbc49fbc83bf956e7ef996d16a56b6ce58e65521296c4f049c88661
3
+ metadata.gz: 8959531cd3a50439e1d30882ac0398b7d361da8a6859049b01c31e1a35e58862
4
+ data.tar.gz: c4027f992ab9f941cecbe88a7a415d52f5a3800b86e69fe8789567f320a9b9ea
5
5
  SHA512:
6
- metadata.gz: c2e0e50977527a52df9e9c8c91a86cef573e79cb8bdcb4decc2fca0f6712e1ac59da4a99b6b1908ecf478f27d072aa4c94969199a192cc751c6cda3b37e6556c
7
- data.tar.gz: 797b1e3a07e006ff8a80ca7af37ea45bcf25f69feb3f4dd03d56c5cc01364dfd4cc318c9370e68cbbb8aa5c46f2ffb3e1e83261dfe99b6f3c2b79d0606f0c4b1
6
+ metadata.gz: 2750ae72f60c4e388b74813b74fa2580a5ebe48887eca53905b1d1173a08bac4ff931f22fadc3fb2ffec0d96417f7661fe22aba44cdaa36e7480c7df4b0f52d0
7
+ data.tar.gz: 3201606c2dce4e6e41649c8aca6432dbd2c0635c966366809bc6552ad07cc074596d3d973054bce87f79f3eca9ffb68d91e116d9b8f81ac5dad7d59b8040cd5b
data/CHANGELOG.md CHANGED
@@ -1,76 +1,62 @@
1
- ## Rails 8.0.2.1 (August 13, 2025) ##
1
+ ## Rails 8.1.0.beta1 (September 04, 2025) ##
2
2
 
3
- Remove dangerous transformations
3
+ * Remove deprecated `:azure` storage service.
4
4
 
5
- [CVE-2025-24293]
5
+ *Rafael Mendonça França*
6
6
 
7
- *Zack Deveau*
7
+ * Remove unnecessary calls to the GCP metadata server.
8
8
 
9
- ## Rails 8.0.2 (March 12, 2025) ##
9
+ Calling Google::Auth.get_application_default triggers an explicit call to
10
+ the metadata server - given it was being called for significant number of
11
+ file operations, it can lead to considerable tail latencies and even metadata
12
+ server overloads. Instead, it's preferable (and significantly more efficient)
13
+ that applications use:
10
14
 
11
- * No changes.
15
+ ```ruby
16
+ Google::Apis::RequestOptions.default.authorization = Google::Auth.get_application_default(...)
17
+ ```
12
18
 
19
+ In the cases applications do not set that, the GCP libraries automatically determine credentials.
13
20
 
14
- ## Rails 8.0.2 (March 12, 2025) ##
21
+ This also enables using credentials other than those of the associated GCP
22
+ service account like when using impersonation.
15
23
 
16
- * A Blob will no longer autosave associated Attachment.
17
-
18
- This fixes an issue where a record with an attachment would have
19
- its dirty attributes reset, preventing your `after commit` callbacks
20
- on that record to behave as expected.
21
-
22
- Note that this change doesn't require any changes on your application
23
- and is supposed to be internal. Active Storage Attachment will continue
24
- to be autosaved (through a different relation).
25
-
26
- *Edouard-chin*
27
-
28
-
29
- ## Rails 8.0.1 (December 13, 2024) ##
30
-
31
- * No changes.
32
-
33
-
34
- ## Rails 8.0.0.1 (December 10, 2024) ##
35
-
36
- * No changes.
24
+ *Alex Coomans*
37
25
 
26
+ * Direct upload progress accounts for server processing time.
38
27
 
39
- ## Rails 8.0.0 (November 07, 2024) ##
28
+ *Jeremy Daer*
40
29
 
41
- * No changes.
30
+ * Delegate `ActiveStorage::Filename#to_str` to `#to_s`
42
31
 
32
+ Supports checking String equality:
43
33
 
44
- ## Rails 8.0.0.rc2 (October 30, 2024) ##
34
+ ```ruby
35
+ filename = ActiveStorage::Filename.new("file.txt")
36
+ filename == "file.txt" # => true
37
+ filename in "file.txt" # => true
38
+ "file.txt" == filename # => true
39
+ ```
45
40
 
46
- * No changes.
41
+ *Sean Doyle*
47
42
 
43
+ * Add support for alternative MD5 implementation through `config.active_storage.checksum_implementation`.
48
44
 
49
- ## Rails 8.0.0.rc1 (October 19, 2024) ##
45
+ Also automatically degrade to using the slower `Digest::MD5` implementation if `OpenSSL::Digest::MD5`
46
+ is found to be disabled because of OpenSSL FIPS mode.
50
47
 
51
- * No changes.
48
+ *Matt Pasquini*, *Jean Boussier*
52
49
 
50
+ * A Blob will no longer autosave associated Attachment.
53
51
 
54
- ## Rails 8.0.0.beta1 (September 26, 2024) ##
55
-
56
- * Deprecate `ActiveStorage::Service::AzureStorageService`.
57
-
58
- *zzak*
59
-
60
- * Improve `ActiveStorage::Filename#sanitized` method to handle special characters more effectively.
61
- Replace the characters `"*?<>` with `-` if they exist in the Filename to match the Filename convention of Win OS.
62
-
63
- *Luong Viet Dung(Martin)*
64
-
65
- * Improve InvariableError, UnpreviewableError and UnrepresentableError message.
66
-
67
- Include Blob ID and content_type in the messages.
68
-
69
- *Petrik de Heus*
70
-
71
- * Mark proxied files as `immutable` in their Cache-Control header
52
+ This fixes an issue where a record with an attachment would have
53
+ its dirty attributes reset, preventing your `after commit` callbacks
54
+ on that record to behave as expected.
72
55
 
73
- *Nate Matykiewicz*
56
+ Note that this change doesn't require any changes on your application
57
+ and is supposed to be internal. Active Storage Attachment will continue
58
+ to be autosaved (through a different relation).
74
59
 
60
+ *Edouard-chin*
75
61
 
76
- Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activestorage/CHANGELOG.md) for previous changes.
62
+ Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/activestorage/CHANGELOG.md) for previous changes.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Active Storage
2
2
 
3
- Active Storage makes it simple to upload and reference files in cloud services like [Amazon S3](https://aws.amazon.com/s3/), [Google Cloud Storage](https://cloud.google.com/storage/docs/), or [Microsoft Azure Storage](https://azure.microsoft.com/en-us/services/storage/), and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.
3
+ Active Storage makes it simple to upload and reference files in cloud services like [Amazon S3](https://aws.amazon.com/s3/), or [Google Cloud Storage](https://cloud.google.com/storage/docs/), and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.
4
4
 
5
5
  Files can be uploaded from the server to the cloud or directly from the client to the cloud.
6
6
 
@@ -173,7 +173,10 @@ Active Storage, with its included JavaScript library, supports uploading directl
173
173
  ```erb
174
174
  <%= form.file_field :attachments, multiple: true, direct_upload: true %>
175
175
  ```
176
- 3. That's it! Uploads begin upon form submission.
176
+
177
+ 3. Configure CORS on third-party storage services to allow direct upload requests.
178
+
179
+ 4. That's it! Uploads begin upon form submission.
177
180
 
178
181
  ### Direct upload JavaScript events
179
182
 
@@ -203,6 +206,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
203
206
 
204
207
  * https://github.com/rails/rails/issues
205
208
 
206
- Feature requests should be discussed on the rails-core mailing list here:
209
+ Feature requests should be discussed on the rubyonrails-core forum here:
207
210
 
208
211
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -672,7 +672,7 @@ class DirectUploadController {
672
672
  }));
673
673
  }
674
674
  uploadRequestDidProgress(event) {
675
- const progress = event.loaded / event.total * 100;
675
+ const progress = event.loaded / event.total * 90;
676
676
  if (progress) {
677
677
  this.dispatch("progress", {
678
678
  progress: progress
@@ -707,6 +707,42 @@ class DirectUploadController {
707
707
  xhr: xhr
708
708
  });
709
709
  xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
710
+ xhr.upload.addEventListener("loadend", (() => {
711
+ this.simulateResponseProgress(xhr);
712
+ }));
713
+ }
714
+ simulateResponseProgress(xhr) {
715
+ let progress = 90;
716
+ const startTime = Date.now();
717
+ const updateProgress = () => {
718
+ const elapsed = Date.now() - startTime;
719
+ const estimatedResponseTime = this.estimateResponseTime();
720
+ const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
721
+ progress = 90 + responseProgress * 9;
722
+ this.dispatch("progress", {
723
+ progress: progress
724
+ });
725
+ if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
726
+ requestAnimationFrame(updateProgress);
727
+ }
728
+ };
729
+ xhr.addEventListener("loadend", (() => {
730
+ this.dispatch("progress", {
731
+ progress: 100
732
+ });
733
+ }));
734
+ requestAnimationFrame(updateProgress);
735
+ }
736
+ estimateResponseTime() {
737
+ const fileSize = this.file.size;
738
+ const MB = 1024 * 1024;
739
+ if (fileSize < MB) {
740
+ return 1e3;
741
+ } else if (fileSize < 10 * MB) {
742
+ return 2e3;
743
+ } else {
744
+ return 3e3 + fileSize / MB * 50;
745
+ }
710
746
  }
711
747
  }
712
748
 
@@ -662,7 +662,7 @@
662
662
  }));
663
663
  }
664
664
  uploadRequestDidProgress(event) {
665
- const progress = event.loaded / event.total * 100;
665
+ const progress = event.loaded / event.total * 90;
666
666
  if (progress) {
667
667
  this.dispatch("progress", {
668
668
  progress: progress
@@ -697,6 +697,42 @@
697
697
  xhr: xhr
698
698
  });
699
699
  xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
700
+ xhr.upload.addEventListener("loadend", (() => {
701
+ this.simulateResponseProgress(xhr);
702
+ }));
703
+ }
704
+ simulateResponseProgress(xhr) {
705
+ let progress = 90;
706
+ const startTime = Date.now();
707
+ const updateProgress = () => {
708
+ const elapsed = Date.now() - startTime;
709
+ const estimatedResponseTime = this.estimateResponseTime();
710
+ const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
711
+ progress = 90 + responseProgress * 9;
712
+ this.dispatch("progress", {
713
+ progress: progress
714
+ });
715
+ if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
716
+ requestAnimationFrame(updateProgress);
717
+ }
718
+ };
719
+ xhr.addEventListener("loadend", (() => {
720
+ this.dispatch("progress", {
721
+ progress: 100
722
+ });
723
+ }));
724
+ requestAnimationFrame(updateProgress);
725
+ }
726
+ estimateResponseTime() {
727
+ const fileSize = this.file.size;
728
+ const MB = 1024 * 1024;
729
+ if (fileSize < MB) {
730
+ return 1e3;
731
+ } else if (fileSize < 10 * MB) {
732
+ return 2e3;
733
+ } else {
734
+ return 3e3 + fileSize / MB * 50;
735
+ }
700
736
  }
701
737
  }
702
738
  const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
@@ -25,13 +25,13 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
25
25
  named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
26
26
  head :no_content
27
27
  else
28
- head :unprocessable_entity
28
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
29
29
  end
30
30
  else
31
31
  head :not_found
32
32
  end
33
33
  rescue ActiveStorage::IntegrityError
34
- head :unprocessable_entity
34
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
35
35
  end
36
36
 
37
37
  private
@@ -31,7 +31,8 @@ export class DirectUploadController {
31
31
  }
32
32
 
33
33
  uploadRequestDidProgress(event) {
34
- const progress = event.loaded / event.total * 100
34
+ // Scale upload progress to 0-90% range
35
+ const progress = (event.loaded / event.total) * 90
35
36
  if (progress) {
36
37
  this.dispatch("progress", { progress })
37
38
  }
@@ -63,5 +64,51 @@ export class DirectUploadController {
63
64
  directUploadWillStoreFileWithXHR(xhr) {
64
65
  this.dispatch("before-storage-request", { xhr })
65
66
  xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event))
67
+
68
+ // Start simulating progress after upload completes
69
+ xhr.upload.addEventListener("loadend", () => {
70
+ this.simulateResponseProgress(xhr)
71
+ })
72
+ }
73
+
74
+ simulateResponseProgress(xhr) {
75
+ let progress = 90
76
+ const startTime = Date.now()
77
+
78
+ const updateProgress = () => {
79
+ // Simulate progress from 90% to 99% over estimated time
80
+ const elapsed = Date.now() - startTime
81
+ const estimatedResponseTime = this.estimateResponseTime()
82
+ const responseProgress = Math.min(elapsed / estimatedResponseTime, 1)
83
+ progress = 90 + (responseProgress * 9) // 90% to 99%
84
+
85
+ this.dispatch("progress", { progress })
86
+
87
+ // Continue until response arrives or we hit 99%
88
+ if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
89
+ requestAnimationFrame(updateProgress)
90
+ }
91
+ }
92
+
93
+ // Stop simulation when response arrives
94
+ xhr.addEventListener("loadend", () => {
95
+ this.dispatch("progress", { progress: 100 })
96
+ })
97
+
98
+ requestAnimationFrame(updateProgress)
99
+ }
100
+
101
+ estimateResponseTime() {
102
+ // Base estimate: 1 second for small files, scaling up for larger files
103
+ const fileSize = this.file.size
104
+ const MB = 1024 * 1024
105
+
106
+ if (fileSize < MB) {
107
+ return 1000 // 1 second for files under 1MB
108
+ } else if (fileSize < 10 * MB) {
109
+ return 2000 // 2 seconds for files 1-10MB
110
+ } else {
111
+ return 3000 + (fileSize / MB * 50) // 3+ seconds for larger files
112
+ }
66
113
  }
67
114
  }
@@ -31,6 +31,72 @@ module ActiveStorage::Blob::Representable
31
31
  # Raises ActiveStorage::InvariableError if the variant processor cannot
32
32
  # transform the blob. To determine whether a blob is variable, call
33
33
  # ActiveStorage::Blob#variable?.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # Options are defined by the {image_processing gem}[https://github.com/janko/image_processing],
38
+ # and depend on which variant processor you are using:
39
+ # {Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md] or
40
+ # {MiniMagick}[https://github.com/janko/image_processing/blob/master/doc/minimagick.md].
41
+ # However, both variant processors support the following options:
42
+ #
43
+ # [+:resize_to_limit+]
44
+ # Downsizes the image to fit within the specified dimensions while retaining
45
+ # the original aspect ratio. Will only resize the image if it's larger than
46
+ # the specified dimensions.
47
+ #
48
+ # user.avatar.variant(resize_to_limit: [100, 100])
49
+ #
50
+ # [+:resize_to_fit+]
51
+ # Resizes the image to fit within the specified dimensions while retaining
52
+ # the original aspect ratio. Will downsize the image if it's larger than the
53
+ # specified dimensions or upsize if it's smaller.
54
+ #
55
+ # user.avatar.variant(resize_to_fit: [100, 100])
56
+ #
57
+ # [+:resize_to_fill+]
58
+ # Resizes the image to fill the specified dimensions while retaining the
59
+ # original aspect ratio. If necessary, will crop the image in the larger
60
+ # dimension.
61
+ #
62
+ # user.avatar.variant(resize_to_fill: [100, 100])
63
+ #
64
+ # [+:resize_and_pad+]
65
+ # Resizes the image to fit within the specified dimensions while retaining
66
+ # the original aspect ratio. If necessary, will pad the remaining area with
67
+ # transparent color if source image has alpha channel, black otherwise.
68
+ #
69
+ # user.avatar.variant(resize_and_pad: [100, 100])
70
+ #
71
+ # [+:crop+]
72
+ # Extracts an area from an image. The first two arguments are the left and
73
+ # top edges of area to extract, while the last two arguments are the width
74
+ # and height of the area to extract.
75
+ #
76
+ # user.avatar.variant(crop: [20, 50, 300, 300])
77
+ #
78
+ # [+:rotate+]
79
+ # Rotates the image by the specified angle.
80
+ #
81
+ # user.avatar.variant(rotate: 90)
82
+ #
83
+ # Some options, including those listed above, can accept additional
84
+ # processor-specific values which can be passed as a trailing hash:
85
+ #
86
+ # <!-- Vips supports configuring `crop` for many of its transformations -->
87
+ # <%= image_tag user.avatar.variant(resize_to_fill: [100, 100, { crop: :centre }]) %>
88
+ #
89
+ # If migrating an existing application between MiniMagick and Vips, you will
90
+ # need to update processor-specific options:
91
+ #
92
+ # <!-- MiniMagick -->
93
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
94
+ # sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
95
+ #
96
+ # <!-- Vips -->
97
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
98
+ # saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
99
+ #
34
100
  def variant(transformations)
35
101
  if variable?
36
102
  variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
@@ -332,7 +332,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
332
332
  def compute_checksum_in_chunks(io)
333
333
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)
334
334
 
335
- OpenSSL::Digest::MD5.new.tap do |checksum|
335
+ ActiveStorage.checksum_implementation.new.tap do |checksum|
336
336
  read_buffer = "".b
337
337
  while io.read(5.megabytes, read_buffer)
338
338
  checksum << read_buffer
@@ -64,6 +64,7 @@ class ActiveStorage::Filename
64
64
  def to_s
65
65
  sanitized.to_s
66
66
  end
67
+ alias_method :to_str, :to_s
67
68
 
68
69
  def as_json(*)
69
70
  to_s
@@ -8,17 +8,17 @@
8
8
  #
9
9
  # Variants rely on {ImageProcessing}[https://github.com/janko/image_processing] gem for the actual transformations
10
10
  # of the file, so you must add <tt>gem "image_processing"</tt> to your Gemfile if you wish to use variants. By
11
- # default, images will be processed with {ImageMagick}[http://imagemagick.org] using the
12
- # {MiniMagick}[https://github.com/minimagick/minimagick] gem, but you can also switch to the
13
- # {libvips}[http://libvips.github.io/libvips/] processor operated by the {ruby-vips}[https://github.com/libvips/ruby-vips]
11
+ # default, images will be processed with {libvips}[http://libvips.github.io/libvips/] using the
12
+ # {ruby-vips}[https://github.com/libvips/ruby-vips] gem, but you can also switch to the
13
+ # {ImageMagick}[http://imagemagick.org] processor operated by the {MiniMagick}[https://github.com/minimagick/minimagick]
14
14
  # gem).
15
15
  #
16
16
  # Rails.application.config.active_storage.variant_processor
17
- # # => :mini_magick
18
- #
19
- # Rails.application.config.active_storage.variant_processor = :vips
20
17
  # # => :vips
21
18
  #
19
+ # Rails.application.config.active_storage.variant_processor = :mini_magick
20
+ # # => :mini_magick
21
+ #
22
22
  # Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
23
23
  # you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
24
24
  # in a template, for example. Delay the processing to an on-demand controller, like the one provided in
@@ -74,7 +74,7 @@ class ActiveStorage::Variant
74
74
  "variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
75
75
  end
76
76
 
77
- # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
77
+ # Returns the URL of the blob variant on the service. See ActiveStorage::Blob#url for details.
78
78
  #
79
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
80
80
  # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
@@ -82,6 +82,6 @@ class ActiveStorage::Variation
82
82
 
83
83
  private
84
84
  def transformer
85
- ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations.except(:format))
85
+ ActiveStorage.variant_transformer.new(transformations.except(:format))
86
86
  end
87
87
  end
@@ -1,22 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ begin
4
+ gem "mini_magick"
5
+ require "mini_magick"
6
+ rescue LoadError => error
7
+ raise error unless error.message.include?("mini_magick")
8
+ end
9
+
3
10
  module ActiveStorage
4
11
  # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
5
12
  # the {ImageMagick}[http://www.imagemagick.org] system library.
6
13
  class Analyzer::ImageAnalyzer::ImageMagick < Analyzer::ImageAnalyzer
7
- def self.accept?(blob)
8
- super && ActiveStorage.variant_processor == :mini_magick
9
- end
10
-
11
14
  private
12
15
  def read_image
13
- begin
14
- require "mini_magick"
15
- rescue LoadError
16
- logger.info "Skipping image analysis because the mini_magick gem isn't installed"
17
- return {}
18
- end
19
-
20
16
  download_blob_to_tempfile do |file|
21
17
  image = instrument("mini_magick") do
22
18
  MiniMagick::Image.new(file.path)
@@ -1,22 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ begin
4
+ gem "ruby-vips"
5
+ require "ruby-vips"
6
+ rescue LoadError => error
7
+ raise error unless error.message.include?("ruby-vips")
8
+ end
9
+
3
10
  module ActiveStorage
4
11
  # This analyzer relies on the third-party {ruby-vips}[https://github.com/libvips/ruby-vips] gem. Ruby-vips requires
5
12
  # the {libvips}[https://libvips.github.io/libvips/] system library.
6
13
  class Analyzer::ImageAnalyzer::Vips < Analyzer::ImageAnalyzer
7
- def self.accept?(blob)
8
- super && ActiveStorage.variant_processor == :vips
9
- end
10
-
11
14
  private
12
15
  def read_image
13
- begin
14
- require "ruby-vips"
15
- rescue LoadError
16
- logger.info "Skipping image analysis because the ruby-vips gem isn't installed"
17
- return {}
18
- end
19
-
20
16
  download_blob_to_tempfile do |file|
21
17
  image = instrument("vips") do
22
18
  # ruby-vips will raise Vips::Error if it can't find an appropriate loader for the file
@@ -35,6 +31,9 @@ module ActiveStorage
35
31
  logger.error "Skipping image analysis due to a Vips error: #{error.message}"
36
32
  {}
37
33
  end
34
+ rescue ::Vips::Error => error
35
+ logger.error "Skipping image analysis due to an Vips error: #{error.message}"
36
+ {}
38
37
  end
39
38
 
40
39
  ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/
@@ -12,6 +12,11 @@ module ActiveStorage
12
12
  # ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick.new(blob).metadata
13
13
  # # => { width: 4104, height: 2736 }
14
14
  class Analyzer::ImageAnalyzer < Analyzer
15
+ extend ActiveSupport::Autoload
16
+
17
+ autoload :Vips
18
+ autoload :ImageMagick
19
+
15
20
  def self.accept?(blob)
16
21
  blob.image?
17
22
  end
@@ -61,8 +61,8 @@ module ActiveStorage
61
61
  # There is no column defined on the model side, Active Storage takes
62
62
  # care of the mapping between your records and the attachment.
63
63
  #
64
- # Under the covers, this relationship is implemented as a +has_one+ association to a
65
- # ActiveStorage::Attachment record and a +has_one-through+ association to a
64
+ # Under the covers, this relationship is implemented as a +has_one+ association to an
65
+ # ActiveStorage::Attachment record and a +has_one-through+ association to an
66
66
  # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
67
67
  # and +avatar_blob+. But you shouldn't need to work with these associations directly in
68
68
  # most circumstances.
@@ -163,8 +163,8 @@ module ActiveStorage
163
163
  # There are no columns defined on the model side, Active Storage takes
164
164
  # care of the mapping between your records and the attachments.
165
165
  #
166
- # Under the covers, this relationship is implemented as a +has_many+ association to a
167
- # ActiveStorage::Attachment record and a +has_many-through+ association to a
166
+ # Under the covers, this relationship is implemented as a +has_many+ association to an
167
+ # ActiveStorage::Attachment record and a +has_many-through+ association to an
168
168
  # ActiveStorage::Blob record. These associations are available as +photos_attachments+
169
169
  # and +photos_blobs+. But you shouldn't need to work with these associations directly in
170
170
  # most circumstances.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveStorage
6
5
  # = Active Storage \Attached
@@ -35,7 +35,7 @@ module ActiveStorage
35
35
  end
36
36
 
37
37
  def verify_integrity_of(file, checksum:)
38
- unless OpenSSL::Digest::MD5.file(file).base64digest == checksum
38
+ unless ActiveStorage.checksum_implementation.file(file).base64digest == checksum
39
39
  raise ActiveStorage::IntegrityError
40
40
  end
41
41
  end