activestorage 6.0.6.1 → 6.1.7.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.

Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +230 -167
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +35 -3
  5. data/app/controllers/active_storage/base_controller.rb +11 -0
  6. data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
  7. data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +2 -2
  8. data/app/controllers/active_storage/direct_uploads_controller.rb +1 -1
  9. data/app/controllers/active_storage/disk_controller.rb +8 -20
  10. data/app/controllers/active_storage/representations/base_controller.rb +14 -0
  11. data/app/controllers/active_storage/representations/proxy_controller.rb +13 -0
  12. data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +2 -4
  13. data/app/controllers/concerns/active_storage/file_server.rb +18 -0
  14. data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
  15. data/app/controllers/concerns/active_storage/set_current.rb +2 -2
  16. data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
  17. data/app/jobs/active_storage/mirror_job.rb +15 -0
  18. data/app/models/active_storage/attachment.rb +19 -11
  19. data/app/models/active_storage/blob/analyzable.rb +6 -2
  20. data/app/models/active_storage/blob/identifiable.rb +7 -6
  21. data/app/models/active_storage/blob/representable.rb +34 -4
  22. data/app/models/active_storage/blob.rb +122 -57
  23. data/app/models/active_storage/preview.rb +31 -10
  24. data/app/models/active_storage/record.rb +7 -0
  25. data/app/models/active_storage/variant.rb +31 -44
  26. data/app/models/active_storage/variant_record.rb +8 -0
  27. data/app/models/active_storage/variant_with_record.rb +54 -0
  28. data/app/models/active_storage/variation.rb +26 -21
  29. data/config/routes.rb +58 -8
  30. data/db/migrate/20170806125915_create_active_storage_tables.rb +30 -9
  31. data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +21 -0
  32. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +26 -0
  33. data/lib/active_storage/analyzer/image_analyzer.rb +3 -0
  34. data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
  35. data/lib/active_storage/analyzer/video_analyzer.rb +14 -3
  36. data/lib/active_storage/analyzer.rb +6 -0
  37. data/lib/active_storage/attached/changes/create_many.rb +1 -0
  38. data/lib/active_storage/attached/changes/create_one.rb +17 -4
  39. data/lib/active_storage/attached/many.rb +4 -3
  40. data/lib/active_storage/attached/model.rb +67 -14
  41. data/lib/active_storage/attached/one.rb +4 -3
  42. data/lib/active_storage/engine.rb +41 -43
  43. data/lib/active_storage/errors.rb +3 -0
  44. data/lib/active_storage/gem_version.rb +3 -3
  45. data/lib/active_storage/log_subscriber.rb +6 -0
  46. data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
  47. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +2 -2
  48. data/lib/active_storage/previewer/video_previewer.rb +5 -3
  49. data/lib/active_storage/previewer.rb +13 -3
  50. data/lib/active_storage/service/azure_storage_service.rb +40 -35
  51. data/lib/active_storage/service/configurator.rb +3 -1
  52. data/lib/active_storage/service/disk_service.rb +36 -31
  53. data/lib/active_storage/service/gcs_service.rb +18 -16
  54. data/lib/active_storage/service/mirror_service.rb +31 -7
  55. data/lib/active_storage/service/registry.rb +32 -0
  56. data/lib/active_storage/service/s3_service.rb +51 -23
  57. data/lib/active_storage/service.rb +35 -7
  58. data/lib/active_storage/transformers/image_processing_transformer.rb +21 -308
  59. data/lib/active_storage/transformers/transformer.rb +0 -3
  60. data/lib/active_storage.rb +301 -7
  61. data/lib/tasks/activestorage.rake +5 -1
  62. metadata +54 -17
  63. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +0 -9
  64. data/lib/active_storage/downloading.rb +0 -47
  65. data/lib/active_storage/transformers/mini_magick_transformer.rb +0 -38
@@ -5,4 +5,15 @@ class ActiveStorage::BaseController < ActionController::Base
5
5
  include ActiveStorage::SetCurrent
6
6
 
7
7
  protect_from_forgery with: :exception
8
+
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
8
19
  end
@@ -0,0 +1,14 @@
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::Blobs::ProxyController < ActiveStorage::BaseController
5
+ include ActiveStorage::SetBlob
6
+ include ActiveStorage::SetHeaders
7
+
8
+ def show
9
+ http_cache_forever public: true do
10
+ set_content_headers_from @blob
11
+ stream @blob
12
+ end
13
+ end
14
+ end
@@ -4,11 +4,11 @@
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 references, you'll need to implement your own
6
6
  # authenticated redirection controller.
7
- class ActiveStorage::BlobsController < ActiveStorage::BaseController
7
+ class ActiveStorage::Blobs::RedirectController < ActiveStorage::BaseController
8
8
  include ActiveStorage::SetBlob
9
9
 
10
10
  def show
11
11
  expires_in ActiveStorage.service_urls_expire_in
12
- redirect_to @blob.service_url(disposition: params[:disposition])
12
+ redirect_to @blob.url(disposition: params[:disposition])
13
13
  end
14
14
  end
@@ -11,7 +11,7 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
11
11
 
12
12
  private
13
13
  def blob_args
14
- params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys
14
+ params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
15
15
  end
16
16
 
17
17
  def direct_upload_json(blob)
@@ -5,11 +5,13 @@
5
5
  # Always go through the BlobsController, or your own authenticated controller, rather than directly
6
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 disk_service.path_for(key[:key]), content_type: key[:content_type], disposition: key[:disposition]
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
@@ -20,7 +22,7 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
20
22
  def update
21
23
  if token = decode_verified_token
22
24
  if acceptable_content?(token)
23
- disk_service.upload token[:key], request.body, checksum: token[:checksum]
25
+ named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
24
26
  else
25
27
  head :unprocessable_entity
26
28
  end
@@ -32,30 +34,16 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
32
34
  end
33
35
 
34
36
  private
35
- def disk_service
36
- ActiveStorage::Blob.service
37
+ def named_disk_service(name)
38
+ ActiveStorage::Blob.services.fetch(name) do
39
+ ActiveStorage::Blob.service
40
+ end
37
41
  end
38
42
 
39
-
40
43
  def decode_verified_key
41
44
  ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
42
45
  end
43
46
 
44
- def serve_file(path, content_type:, disposition:)
45
- Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
46
- self.status = status
47
- self.response_body = body
48
-
49
- headers.each do |name, value|
50
- response.headers[name] = value
51
- end
52
-
53
- response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
54
- response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
55
- end
56
- end
57
-
58
-
59
47
  def decode_verified_token
60
48
  ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
61
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::RepresentationsController < ActiveStorage::BaseController
8
- include ActiveStorage::SetBlob
9
-
7
+ class ActiveStorage::Representations::RedirectController < ActiveStorage::Representations::BaseController
10
8
  def show
11
9
  expires_in ActiveStorage.service_urls_expire_in
12
- redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
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
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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#service_url,
5
- # ActiveStorage::Variant#service_url, or ActiveStorage::Preview#service_url so the disk service can
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
6
  # generate URLs using the same host, protocol, and base path as the current request.
7
7
  module ActiveStorage::SetCurrent
8
8
  extend ActiveSupport::Concern
@@ -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
@@ -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
@@ -5,45 +5,53 @@ require "active_support/core_ext/module/delegation"
5
5
  # Attachments associate records with blobs. Usually that's a one record-many blobs relationship,
6
6
  # but it is possible to associate many different records with the same blob. A foreign-key constraint
7
7
  # on the attachments table prevents blobs from being purged if they’re still attached to any records.
8
- class ActiveStorage::Attachment < ActiveRecord::Base
8
+ #
9
+ # Attachments also have access to all methods from {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob].
10
+ class ActiveStorage::Attachment < ActiveStorage::Record
9
11
  self.table_name = "active_storage_attachments"
10
12
 
11
13
  belongs_to :record, polymorphic: true, touch: true
12
- belongs_to :blob, class_name: "ActiveStorage::Blob"
14
+ belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true
13
15
 
14
16
  delegate_missing_to :blob
17
+ delegate :signed_id, to: :blob
15
18
 
16
- after_create_commit :analyze_blob_later, :identify_blob
19
+ after_create_commit :mirror_blob_later, :analyze_blob_later
17
20
  after_destroy_commit :purge_dependent_blob_later
18
21
 
19
22
  # Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
20
23
  def purge
21
- delete
24
+ transaction do
25
+ delete
26
+ record&.touch
27
+ end
22
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
- delete
33
+ transaction do
34
+ delete
35
+ record&.touch
36
+ end
28
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
39
44
 
45
+ def mirror_blob_later
46
+ blob.mirror_later
47
+ end
48
+
40
49
  def purge_dependent_blob_later
41
50
  blob&.purge_later if dependent == :purge_later
42
51
  end
43
52
 
44
-
45
53
  def dependent
46
- record.attachment_reflections[name]&.options[:dependent]
54
+ record.attachment_reflections[name]&.options&.fetch(:dependent, nil)
47
55
  end
48
56
  end
49
57
 
@@ -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
- ActiveStorage::AnalyzeJob.perform_later(self)
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
- update! content_type: identify_content_type, identified: true
7
- update_service_metadata
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(resize_to_limit: [100, 100]).processed.service_url
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.
@@ -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
- ActiveStorage::Variant.new(self, transformations)
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,7 +48,7 @@ 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(resize_to_limit: [100, 100]).processed.service_url
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
@@ -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(resize_to_limit: [100, 100]).processed.service_url
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