activestorage 6.1.3.1 → 7.0.0.alpha1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -181
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +25 -11
  5. data/app/assets/javascripts/activestorage.esm.js +844 -0
  6. data/app/assets/javascripts/activestorage.js +257 -376
  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/representations/base_controller.rb +18 -0
  11. data/app/controllers/active_storage/representations/proxy_controller.rb +7 -11
  12. data/app/controllers/active_storage/representations/redirect_controller.rb +7 -7
  13. data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
  14. data/app/controllers/concerns/active_storage/set_current.rb +3 -3
  15. data/app/controllers/concerns/active_storage/streaming.rb +65 -0
  16. data/app/javascript/activestorage/ujs.js +1 -1
  17. data/app/models/active_storage/attachment.rb +36 -3
  18. data/app/models/active_storage/blob/representable.rb +8 -6
  19. data/app/models/active_storage/blob.rb +27 -28
  20. data/app/models/active_storage/current.rb +12 -2
  21. data/app/models/active_storage/preview.rb +6 -4
  22. data/app/models/active_storage/record.rb +1 -1
  23. data/app/models/active_storage/variant.rb +4 -7
  24. data/app/models/active_storage/variant_record.rb +2 -0
  25. data/app/models/active_storage/variant_with_record.rb +10 -6
  26. data/app/models/active_storage/variation.rb +2 -2
  27. data/config/routes.rb +10 -10
  28. data/db/migrate/20170806125915_create_active_storage_tables.rb +29 -8
  29. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
  30. data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
  31. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
  32. data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
  33. data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
  34. data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
  35. data/lib/active_storage/analyzer.rb +8 -4
  36. data/lib/active_storage/attached/changes/create_many.rb +7 -3
  37. data/lib/active_storage/attached/changes/create_one.rb +1 -1
  38. data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
  39. data/lib/active_storage/attached/changes/delete_many.rb +1 -1
  40. data/lib/active_storage/attached/changes/delete_one.rb +1 -1
  41. data/lib/active_storage/attached/changes/detach_many.rb +18 -0
  42. data/lib/active_storage/attached/changes/detach_one.rb +24 -0
  43. data/lib/active_storage/attached/changes/purge_many.rb +27 -0
  44. data/lib/active_storage/attached/changes/purge_one.rb +27 -0
  45. data/lib/active_storage/attached/changes.rb +7 -1
  46. data/lib/active_storage/attached/many.rb +27 -15
  47. data/lib/active_storage/attached/model.rb +31 -5
  48. data/lib/active_storage/attached/one.rb +32 -27
  49. data/lib/active_storage/downloader.rb +2 -2
  50. data/lib/active_storage/engine.rb +29 -1
  51. data/lib/active_storage/errors.rb +3 -0
  52. data/lib/active_storage/fixture_set.rb +76 -0
  53. data/lib/active_storage/gem_version.rb +4 -4
  54. data/lib/active_storage/previewer/video_previewer.rb +1 -1
  55. data/lib/active_storage/previewer.rb +14 -5
  56. data/lib/active_storage/reflection.rb +12 -2
  57. data/lib/active_storage/service/azure_storage_service.rb +1 -1
  58. data/lib/active_storage/service/configurator.rb +1 -1
  59. data/lib/active_storage/service/disk_service.rb +13 -18
  60. data/lib/active_storage/service/gcs_service.rb +91 -7
  61. data/lib/active_storage/service/mirror_service.rb +1 -1
  62. data/lib/active_storage/service/registry.rb +1 -1
  63. data/lib/active_storage/service/s3_service.rb +4 -4
  64. data/lib/active_storage/service.rb +3 -3
  65. data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
  66. data/lib/active_storage/transformers/transformer.rb +1 -1
  67. data/lib/active_storage.rb +5 -1
  68. data/lib/tasks/activestorage.rake +5 -1
  69. metadata +32 -22
  70. data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -3,6 +3,25 @@
3
3
  module ActiveStorage
4
4
  # Representation of a single attachment to a model.
5
5
  class Attached::One < Attached
6
+ ##
7
+ # :method: purge
8
+ #
9
+ # Directly purges the attachment (i.e. destroys the blob and
10
+ # attachment and deletes the file on the service).
11
+ delegate :purge, to: :purge_one
12
+
13
+ ##
14
+ # :method: purge_later
15
+ #
16
+ # Purges the attachment through the queuing system.
17
+ delegate :purge_later, to: :purge_one
18
+
19
+ ##
20
+ # :method: detach
21
+ #
22
+ # Deletes the attachment without purging it, leaving its blob in place.
23
+ delegate :detach, to: :detach_one
24
+
6
25
  delegate_missing_to :attachment, allow_nil: true
7
26
 
8
27
  # Returns the associated attachment record.
@@ -13,6 +32,13 @@ module ActiveStorage
13
32
  change.present? ? change.attachment : record.public_send("#{name}_attachment")
14
33
  end
15
34
 
35
+ # Returns +true+ if an attachment is not attached.
36
+ #
37
+ # class User < ApplicationRecord
38
+ # has_one_attached :avatar
39
+ # end
40
+ #
41
+ # User.new.avatar.blank? # => true
16
42
  def blank?
17
43
  !attached?
18
44
  end
@@ -25,7 +51,7 @@ module ActiveStorage
25
51
  #
26
52
  # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
27
53
  # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
28
- # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
54
+ # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpeg")
29
55
  # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
30
56
  def attach(attachable)
31
57
  if record.persisted? && !record.changed?
@@ -47,34 +73,13 @@ module ActiveStorage
47
73
  attachment.present?
48
74
  end
49
75
 
50
- # Deletes the attachment without purging it, leaving its blob in place.
51
- def detach
52
- if attached?
53
- attachment.delete
54
- write_attachment nil
55
- end
56
- end
57
-
58
- # Directly purges the attachment (i.e. destroys the blob and
59
- # attachment and deletes the file on the service).
60
- def purge
61
- if attached?
62
- attachment.purge
63
- write_attachment nil
64
- end
65
- end
66
-
67
- # Purges the attachment through the queuing system.
68
- def purge_later
69
- if attached?
70
- attachment.purge_later
71
- write_attachment nil
76
+ private
77
+ def purge_one
78
+ Attached::Changes::PurgeOne.new(name, record, attachment)
72
79
  end
73
- end
74
80
 
75
- private
76
- def write_attachment(attachment)
77
- record.public_send("#{name}_attachment=", attachment)
81
+ def detach_one
82
+ Attached::Changes::DetachOne.new(name, record, attachment)
78
83
  end
79
84
  end
80
85
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Downloader #:nodoc:
4
+ class Downloader # :nodoc:
5
5
  attr_reader :service
6
6
 
7
7
  def initialize(service)
@@ -35,7 +35,7 @@ module ActiveStorage
35
35
  end
36
36
 
37
37
  def verify_integrity_of(file, checksum:)
38
- unless Digest::MD5.file(file).base64digest == checksum
38
+ unless OpenSSL::Digest::MD5.file(file).base64digest == checksum
39
39
  raise ActiveStorage::IntegrityError
40
40
  end
41
41
  end
@@ -12,7 +12,10 @@ require "active_storage/previewer/mupdf_previewer"
12
12
  require "active_storage/previewer/video_previewer"
13
13
 
14
14
  require "active_storage/analyzer/image_analyzer"
15
+ require "active_storage/analyzer/image_analyzer/image_magick"
16
+ require "active_storage/analyzer/image_analyzer/vips"
15
17
  require "active_storage/analyzer/video_analyzer"
18
+ require "active_storage/analyzer/audio_analyzer"
16
19
 
17
20
  require "active_storage/service/registry"
18
21
 
@@ -24,7 +27,7 @@ module ActiveStorage
24
27
 
25
28
  config.active_storage = ActiveSupport::OrderedOptions.new
26
29
  config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
27
- config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
30
+ config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer::Vips, ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick, ActiveStorage::Analyzer::VideoAnalyzer, ActiveStorage::Analyzer::AudioAnalyzer ]
28
31
  config.active_storage.paths = ActiveSupport::OrderedOptions.new
29
32
  config.active_storage.queues = ActiveSupport::InheritableOptions.new
30
33
 
@@ -39,6 +42,9 @@ module ActiveStorage
39
42
  image/vnd.adobe.photoshop
40
43
  image/vnd.microsoft.icon
41
44
  image/webp
45
+ image/avif
46
+ image/heic
47
+ image/heif
42
48
  )
43
49
 
44
50
  config.active_storage.web_image_content_types = %w(
@@ -90,8 +96,10 @@ module ActiveStorage
90
96
  ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
91
97
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
92
98
  ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
99
+ ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
93
100
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
94
101
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
102
+ ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
95
103
 
96
104
  ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
97
105
  ActiveStorage.track_variants = app.config.active_storage.track_variants || false
@@ -143,5 +151,25 @@ module ActiveStorage
143
151
  ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
144
152
  end
145
153
  end
154
+
155
+ initializer "active_storage.asset" do
156
+ if Rails.application.config.respond_to?(:assets)
157
+ Rails.application.config.assets.precompile += %w( activestorage activestorage.esm )
158
+ end
159
+ end
160
+
161
+ initializer "active_storage.fixture_set" do
162
+ ActiveSupport.on_load(:active_record_fixture_set) do
163
+ ActiveStorage::FixtureSet.file_fixture_path ||= Rails.root.join(*[
164
+ ENV.fetch("FIXTURES_PATH") { File.join("test", "fixtures") },
165
+ ENV["FIXTURES_DIR"],
166
+ "files"
167
+ ].compact_blank)
168
+ end
169
+
170
+ ActiveSupport.on_load(:active_support_test_case) do
171
+ ActiveStorage::FixtureSet.file_fixture_path = ActiveSupport::TestCase.file_fixture_path
172
+ end
173
+ end
146
174
  end
147
175
  end
@@ -23,4 +23,7 @@ module ActiveStorage
23
23
  # Raised when ActiveStorage::Blob#download is called on a blob where the
24
24
  # backing file is no longer present in its service.
25
25
  class FileNotFoundError < Error; end
26
+
27
+ # Raised when a Previewer is unable to generate a preview image.
28
+ class PreviewError < Error; end
26
29
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/testing/file_fixtures"
4
+ require "active_record/secure_token"
5
+
6
+ module ActiveStorage
7
+ # Fixtures are a way of organizing data that you want to test against; in
8
+ # short, sample data.
9
+ #
10
+ # To learn more about fixtures, read the
11
+ # {ActiveRecord::FixtureSet}[rdoc-ref:ActiveRecord::FixtureSet] documentation.
12
+ #
13
+ # === YAML
14
+ #
15
+ # Like other Active Record-backed models,
16
+ # {ActiveStorage::Attachment}[rdoc-ref:ActiveStorage::Attachment] and
17
+ # {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob] records inherit from
18
+ # {ActiveRecord::Base}[rdoc-ref:ActiveRecord::Base] instances and therefore
19
+ # can be populated by fixtures.
20
+ #
21
+ # Consider a hypothetical <tt>Article</tt> model class, its related
22
+ # fixture data, as well as fixture data for related ActiveStorage::Attachment
23
+ # and ActiveStorage::Blob records:
24
+ #
25
+ # # app/models/article.rb
26
+ # class Article < ApplicationRecord
27
+ # has_one_attached :thumbnail
28
+ # end
29
+ #
30
+ # # fixtures/active_storage/blobs.yml
31
+ # first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
32
+ #
33
+ # # fixtures/active_storage/attachments.yml
34
+ # first_thumbnail_attachment:
35
+ # name: thumbnail
36
+ # record: first (Article)
37
+ # blob: first_thumbnail_blob
38
+ #
39
+ # When processed, Active Record will insert database records for each fixture
40
+ # entry and will ensure the Active Storage relationship is intact.
41
+ class FixtureSet
42
+ include ActiveSupport::Testing::FileFixtures
43
+ include ActiveRecord::SecureToken
44
+
45
+ # Generate a YAML-encoded representation of an ActiveStorage::Blob
46
+ # instance's attributes, resolve the file relative to the directory mentioned
47
+ # by <tt>ActiveSupport::Testing::FileFixtures.file_fixture</tt>, and upload
48
+ # the file to the Service
49
+ #
50
+ # === Examples
51
+ #
52
+ # # tests/fixtures/action_text/blobs.yml
53
+ # second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
54
+ # filename: "second.svg",
55
+ # ) %>
56
+ #
57
+ # third_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
58
+ # filename: "third.svg",
59
+ # content_type: "image/svg+xml",
60
+ # service_name: "public"
61
+ # ) %>
62
+ #
63
+ def self.blob(filename:, **attributes)
64
+ new.prepare Blob.new(filename: filename, key: generate_unique_secure_token), **attributes
65
+ end
66
+
67
+ def prepare(instance, **attributes)
68
+ io = file_fixture(instance.filename.to_s).open
69
+ instance.unfurl(io)
70
+ instance.assign_attributes(attributes)
71
+ instance.upload_without_unfurling(io)
72
+
73
+ instance.attributes.transform_values { |value| value.is_a?(Hash) ? value.to_json : value }.compact.to_json
74
+ end
75
+ end
76
+ end
@@ -7,10 +7,10 @@ module ActiveStorage
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 6
11
- MINOR = 1
12
- TINY = 3
13
- PRE = "1"
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "alpha1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -28,7 +28,7 @@ module ActiveStorage
28
28
 
29
29
  private
30
30
  def draw_relevant_frame_from(file, &block)
31
- draw self.class.ffmpeg_path, "-i", file.path, "-y", "-vframes", "1", "-f", "image2", "-", &block
31
+ draw self.class.ffmpeg_path, "-i", file.path, *Shellwords.split(ActiveStorage.video_preview_arguments), "-", &block
32
32
  end
33
33
  end
34
34
  end
@@ -26,7 +26,7 @@ module ActiveStorage
26
26
 
27
27
  private
28
28
  # Downloads the blob to a tempfile on disk. Yields the tempfile.
29
- def download_blob_to_tempfile(&block) #:doc:
29
+ def download_blob_to_tempfile(&block) # :doc:
30
30
  blob.open tmpdir: tmpdir, &block
31
31
  end
32
32
 
@@ -44,7 +44,7 @@ module ActiveStorage
44
44
  # end
45
45
  #
46
46
  # The output tempfile is opened in the directory returned by #tmpdir.
47
- def draw(*argv) #:doc:
47
+ def draw(*argv) # :doc:
48
48
  open_tempfile do |file|
49
49
  instrument :preview, key: blob.key do
50
50
  capture(*argv, to: file)
@@ -70,15 +70,24 @@ module ActiveStorage
70
70
 
71
71
  def capture(*argv, to:)
72
72
  to.binmode
73
- IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) }
73
+
74
+ open_tempfile do |err|
75
+ IO.popen(argv, err: err) { |out| IO.copy_stream(out, to) }
76
+ err.rewind
77
+
78
+ unless $?.success?
79
+ raise PreviewError, "#{argv.first} failed (status #{$?.exitstatus}): #{err.read.to_s.chomp}"
80
+ end
81
+ end
82
+
74
83
  to.rewind
75
84
  end
76
85
 
77
- def logger #:doc:
86
+ def logger # :doc:
78
87
  ActiveStorage.logger
79
88
  end
80
89
 
81
- def tmpdir #:doc:
90
+ def tmpdir # :doc:
82
91
  Dir.tmpdir
83
92
  end
84
93
  end
@@ -2,9 +2,19 @@
2
2
 
3
3
  module ActiveStorage
4
4
  module Reflection
5
+ class HasAttachedReflection < ActiveRecord::Reflection::MacroReflection # :nodoc:
6
+ def variant(name, transformations)
7
+ variants[name] = transformations
8
+ end
9
+
10
+ def variants
11
+ @variants ||= {}
12
+ end
13
+ end
14
+
5
15
  # Holds all the metadata about a has_one_attached attachment as it was
6
16
  # specified in the Active Record class.
7
- class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
17
+ class HasOneAttachedReflection < HasAttachedReflection # :nodoc:
8
18
  def macro
9
19
  :has_one_attached
10
20
  end
@@ -12,7 +22,7 @@ module ActiveStorage
12
22
 
13
23
  # Holds all the metadata about a has_many_attached attachment as it was
14
24
  # specified in the Active Record class.
15
- class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
25
+ class HasManyAttachedReflection < HasAttachedReflection # :nodoc:
16
26
  def macro
17
27
  :has_many_attached
18
28
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- gem "azure-storage-blob", ">= 1.1"
3
+ gem "azure-storage-blob", ">= 2.0"
4
4
 
5
5
  require "active_support/core_ext/numeric/bytes"
6
6
  require "azure/storage/blob"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Service::Configurator #:nodoc:
4
+ class Service::Configurator # :nodoc:
5
5
  attr_reader :configurations
6
6
 
7
7
  def self.build(service_name, configurations)
@@ -2,14 +2,14 @@
2
2
 
3
3
  require "fileutils"
4
4
  require "pathname"
5
- require "digest/md5"
5
+ require "openssl"
6
6
  require "active_support/core_ext/numeric/bytes"
7
7
 
8
8
  module ActiveStorage
9
9
  # Wraps a local disk path as an Active Storage service. See ActiveStorage::Service for the generic API
10
10
  # documentation that applies to all services.
11
11
  class Service::DiskService < Service
12
- attr_reader :root
12
+ attr_accessor :root
13
13
 
14
14
  def initialize(root:, public: false, **options)
15
15
  @root = root
@@ -86,11 +86,9 @@ module ActiveStorage
86
86
  purpose: :blob_token
87
87
  )
88
88
 
89
- generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host)
90
-
91
- payload[:url] = generated_url
92
-
93
- generated_url
89
+ url_helpers.update_rails_disk_service_url(verified_token_with_expiration, url_options).tap do |generated_url|
90
+ payload[:url] = generated_url
91
+ end
94
92
  end
95
93
  end
96
94
 
@@ -98,7 +96,7 @@ module ActiveStorage
98
96
  { "Content-Type" => content_type }
99
97
  end
100
98
 
101
- def path_for(key) #:nodoc:
99
+ def path_for(key) # :nodoc:
102
100
  File.join root, folder_for(key), key
103
101
  end
104
102
 
@@ -124,14 +122,11 @@ module ActiveStorage
124
122
  purpose: :blob_key
125
123
  )
126
124
 
127
- current_uri = URI.parse(current_host)
125
+ if url_options.blank?
126
+ raise ArgumentError, "Cannot generate URL for #{filename} using Disk service, please set ActiveStorage::Current.url_options."
127
+ end
128
128
 
129
- url_helpers.rails_disk_service_url(verified_key_with_expiration,
130
- protocol: current_uri.scheme,
131
- host: current_uri.host,
132
- port: current_uri.port,
133
- filename: filename
134
- )
129
+ url_helpers.rails_disk_service_url(verified_key_with_expiration, filename: filename, **url_options)
135
130
  end
136
131
 
137
132
 
@@ -154,7 +149,7 @@ module ActiveStorage
154
149
  end
155
150
 
156
151
  def ensure_integrity_of(key, checksum)
157
- unless Digest::MD5.file(path_for(key)).base64digest == checksum
152
+ unless OpenSSL::Digest::MD5.file(path_for(key)).base64digest == checksum
158
153
  delete key
159
154
  raise ActiveStorage::IntegrityError
160
155
  end
@@ -164,8 +159,8 @@ module ActiveStorage
164
159
  @url_helpers ||= Rails.application.routes.url_helpers
165
160
  end
166
161
 
167
- def current_host
168
- ActiveStorage::Current.host
162
+ def url_options
163
+ ActiveStorage::Current.url_options
169
164
  end
170
165
  end
171
166
  end