activestorage 5.2.3 → 6.0.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -59
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +9 -6
  5. data/app/assets/javascripts/activestorage.js +4 -1
  6. data/app/controllers/active_storage/base_controller.rb +3 -5
  7. data/app/controllers/active_storage/blobs_controller.rb +1 -1
  8. data/app/controllers/active_storage/disk_controller.rb +5 -2
  9. data/app/controllers/active_storage/representations_controller.rb +1 -1
  10. data/app/controllers/concerns/active_storage/set_current.rb +15 -0
  11. data/app/javascript/activestorage/blob_record.js +6 -1
  12. data/app/jobs/active_storage/analyze_job.rb +4 -0
  13. data/app/jobs/active_storage/base_job.rb +0 -1
  14. data/app/jobs/active_storage/purge_job.rb +3 -0
  15. data/app/models/active_storage/attachment.rb +20 -9
  16. data/app/models/active_storage/blob.rb +66 -24
  17. data/app/models/active_storage/blob/representable.rb +5 -5
  18. data/app/models/active_storage/filename.rb +0 -6
  19. data/app/models/active_storage/preview.rb +3 -3
  20. data/app/models/active_storage/variant.rb +51 -52
  21. data/app/models/active_storage/variation.rb +24 -33
  22. data/config/routes.rb +13 -12
  23. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +9 -0
  24. data/lib/active_storage.rb +26 -6
  25. data/lib/active_storage/analyzer.rb +9 -4
  26. data/lib/active_storage/analyzer/image_analyzer.rb +11 -4
  27. data/lib/active_storage/analyzer/video_analyzer.rb +3 -5
  28. data/lib/active_storage/attached.rb +7 -22
  29. data/lib/active_storage/attached/changes.rb +16 -0
  30. data/lib/active_storage/attached/changes/create_many.rb +46 -0
  31. data/lib/active_storage/attached/changes/create_one.rb +69 -0
  32. data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
  33. data/lib/active_storage/attached/changes/delete_many.rb +27 -0
  34. data/lib/active_storage/attached/changes/delete_one.rb +19 -0
  35. data/lib/active_storage/attached/many.rb +16 -10
  36. data/lib/active_storage/attached/model.rb +147 -0
  37. data/lib/active_storage/attached/one.rb +16 -19
  38. data/lib/active_storage/downloader.rb +43 -0
  39. data/lib/active_storage/downloading.rb +8 -0
  40. data/lib/active_storage/engine.rb +43 -6
  41. data/lib/active_storage/errors.rb +22 -3
  42. data/lib/active_storage/gem_version.rb +3 -3
  43. data/lib/active_storage/previewer.rb +21 -11
  44. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +2 -2
  45. data/lib/active_storage/previewer/video_previewer.rb +2 -3
  46. data/lib/active_storage/reflection.rb +64 -0
  47. data/lib/active_storage/service.rb +9 -6
  48. data/lib/active_storage/service/azure_storage_service.rb +30 -14
  49. data/lib/active_storage/service/configurator.rb +3 -1
  50. data/lib/active_storage/service/disk_service.rb +24 -12
  51. data/lib/active_storage/service/gcs_service.rb +49 -47
  52. data/lib/active_storage/service/s3_service.rb +10 -6
  53. data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
  54. data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
  55. data/lib/active_storage/transformers/transformer.rb +42 -0
  56. data/lib/tasks/activestorage.rake +7 -0
  57. metadata +38 -12
  58. data/app/models/active_storage/filename/parameters.rb +0 -36
  59. data/lib/active_storage/attached/macros.rb +0 -110
@@ -6,17 +6,9 @@
6
6
  # In case you do need to use this directly, it's instantiated using a hash of transformations where
7
7
  # the key is the command and the value is the arguments. Example:
8
8
  #
9
- # ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
9
+ # ActiveStorage::Variation.new(resize_to_limit: [100, 100], monochrome: true, trim: true, rotate: "-90")
10
10
  #
11
- # You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
12
- #
13
- # ActiveStorage::Variation.new(combine_options: {
14
- # resize: "100x100^",
15
- # gravity: "center",
16
- # crop: "100x100+0+0",
17
- # })
18
- #
19
- # A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
11
+ # The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
20
12
  class ActiveStorage::Variation
21
13
  attr_reader :transformations
22
14
 
@@ -48,24 +40,16 @@ class ActiveStorage::Variation
48
40
  end
49
41
 
50
42
  def initialize(transformations)
51
- @transformations = transformations
43
+ @transformations = transformations.deep_symbolize_keys
52
44
  end
53
45
 
54
- # Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
55
- # and performs the +transformations+ against it. The transformed image instance is then returned.
56
- def transform(image)
46
+ # Accepts a File object, performs the +transformations+ against it, and
47
+ # saves the transformed image into a temporary file. If +format+ is specified
48
+ # it will be the format of the result image, otherwise the result image
49
+ # retains the source format.
50
+ def transform(file, format: nil, &block)
57
51
  ActiveSupport::Notifications.instrument("transform.active_storage") do
58
- transformations.each do |name, argument_or_subtransformations|
59
- image.mogrify do |command|
60
- if name.to_s == "combine_options"
61
- argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
62
- pass_transform_argument(command, subtransformation_name, subtransformation_argument)
63
- end
64
- else
65
- pass_transform_argument(command, name, argument_or_subtransformations)
66
- end
67
- end
68
- end
52
+ transformer.transform(file, format: format, &block)
69
53
  end
70
54
  end
71
55
 
@@ -75,15 +59,22 @@ class ActiveStorage::Variation
75
59
  end
76
60
 
77
61
  private
78
- def pass_transform_argument(command, method, argument)
79
- if eligible_argument?(argument)
80
- command.public_send(method, argument)
62
+ def transformer
63
+ if ActiveStorage.variant_processor
64
+ begin
65
+ require "image_processing"
66
+ rescue LoadError
67
+ ActiveSupport::Deprecation.warn <<~WARNING.squish
68
+ Generating image variants will require the image_processing gem in Rails 6.1.
69
+ Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
70
+ WARNING
71
+
72
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
73
+ else
74
+ ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations)
75
+ end
81
76
  else
82
- command.public_send(method)
77
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
83
78
  end
84
79
  end
85
-
86
- def eligible_argument?(argument)
87
- argument.present? && argument != true
88
- end
89
80
  end
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rails.application.routes.draw do
4
- get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
5
-
6
- direct :rails_blob do |blob, options|
7
- route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
8
- end
9
-
10
- resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
11
- resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
4
+ scope ActiveStorage.routes_prefix do
5
+ get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
12
6
 
7
+ get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
13
8
 
14
- get "/rails/active_storage/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
9
+ get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
10
+ put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
11
+ post "/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
12
+ end
15
13
 
16
14
  direct :rails_representation do |representation, options|
17
15
  signed_blob_id = representation.blob.signed_id
@@ -25,7 +23,10 @@ Rails.application.routes.draw do
25
23
  resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
26
24
 
27
25
 
28
- get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
29
- put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
30
- post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
26
+ direct :rails_blob do |blob, options|
27
+ route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
28
+ end
29
+
30
+ resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
31
+ resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
31
32
  end
@@ -0,0 +1,9 @@
1
+ class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
2
+ def up
3
+ return if foreign_key_exists?(:active_storage_attachments, column: :blob_id)
4
+
5
+ if table_exists?(:active_storage_blobs)
6
+ add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp
4
+ # Copyright (c) 2017-2019 David Heinemeier Hansson, Basecamp
5
5
  #
6
6
  # Permission is hereby granted, free of charge, to any person obtaining
7
7
  # a copy of this software and associated documentation files (the
@@ -26,6 +26,7 @@
26
26
  require "active_record"
27
27
  require "active_support"
28
28
  require "active_support/rails"
29
+ require "active_support/core_ext/numeric/time"
29
30
 
30
31
  require "active_storage/version"
31
32
  require "active_storage/errors"
@@ -42,12 +43,31 @@ module ActiveStorage
42
43
 
43
44
  mattr_accessor :logger
44
45
  mattr_accessor :verifier
45
- mattr_accessor :queue
46
+ mattr_accessor :variant_processor, default: :mini_magick
47
+
48
+ mattr_accessor :queues, default: {}
49
+
46
50
  mattr_accessor :previewers, default: []
47
- mattr_accessor :analyzers, default: []
51
+ mattr_accessor :analyzers, default: []
52
+
48
53
  mattr_accessor :paths, default: {}
49
- mattr_accessor :variable_content_types, default: []
54
+
55
+ mattr_accessor :variable_content_types, default: []
56
+ mattr_accessor :binary_content_type, default: "application/octet-stream"
50
57
  mattr_accessor :content_types_to_serve_as_binary, default: []
51
- mattr_accessor :content_types_allowed_inline, default: []
52
- mattr_accessor :binary_content_type, default: "application/octet-stream"
58
+ mattr_accessor :content_types_allowed_inline, default: []
59
+
60
+ mattr_accessor :service_urls_expire_in, default: 5.minutes
61
+
62
+ mattr_accessor :routes_prefix, default: "/rails/active_storage"
63
+
64
+ mattr_accessor :replace_on_assign_to_many, default: false
65
+
66
+ module Transformers
67
+ extend ActiveSupport::Autoload
68
+
69
+ autoload :Transformer
70
+ autoload :ImageProcessingTransformer
71
+ autoload :MiniMagickTransformer
72
+ end
53
73
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_storage/downloading"
4
-
5
3
  module ActiveStorage
6
4
  # This is an abstract base class for analyzers, which extract metadata from blobs. See
7
5
  # ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
8
6
  class Analyzer
9
- include Downloading
10
-
11
7
  attr_reader :blob
12
8
 
13
9
  # Implement this method in a concrete subclass. Have it return true when given a blob from which
@@ -26,8 +22,17 @@ module ActiveStorage
26
22
  end
27
23
 
28
24
  private
25
+ # Downloads the blob to a tempfile on disk. Yields the tempfile.
26
+ def download_blob_to_tempfile(&block) #:doc:
27
+ blob.open tmpdir: tmpdir, &block
28
+ end
29
+
29
30
  def logger #:doc:
30
31
  ActiveStorage.logger
31
32
  end
33
+
34
+ def tmpdir #:doc:
35
+ Dir.tmpdir
36
+ end
32
37
  end
33
38
  end
@@ -25,17 +25,24 @@ module ActiveStorage
25
25
  { width: image.width, height: image.height }
26
26
  end
27
27
  end
28
- rescue LoadError
29
- logger.info "Skipping image analysis because the mini_magick gem isn't installed"
30
- {}
31
28
  end
32
29
 
33
30
  private
34
31
  def read_image
35
32
  download_blob_to_tempfile do |file|
36
33
  require "mini_magick"
37
- yield MiniMagick::Image.new(file.path)
34
+ image = MiniMagick::Image.new(file.path)
35
+
36
+ if image.valid?
37
+ yield image
38
+ else
39
+ logger.info "Skipping image analysis because ImageMagick doesn't support the file"
40
+ {}
41
+ end
38
42
  end
43
+ rescue LoadError
44
+ logger.info "Skipping image analysis because the mini_magick gem isn't installed"
45
+ {}
39
46
  end
40
47
 
41
48
  def rotated_image?(image)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/compact"
4
-
5
3
  module ActiveStorage
6
4
  # Extracts the following from a video blob:
7
5
  #
@@ -13,12 +11,12 @@ module ActiveStorage
13
11
  #
14
12
  # Example:
15
13
  #
16
- # ActiveStorage::VideoAnalyzer.new(blob).metadata
14
+ # ActiveStorage::Analyzer::VideoAnalyzer.new(blob).metadata
17
15
  # # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3] }
18
16
  #
19
17
  # When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
20
18
  #
21
- # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
19
+ # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
22
20
  class Analyzer::VideoAnalyzer < Analyzer
23
21
  def self.accept?(blob)
24
22
  blob.video?
@@ -109,7 +107,7 @@ module ActiveStorage
109
107
  JSON.parse(output.read)
110
108
  end
111
109
  rescue Errno::ENOENT
112
- logger.info "Skipping video analysis because ffmpeg isn't installed"
110
+ logger.info "Skipping video analysis because FFmpeg isn't installed"
113
111
  {}
114
112
  end
115
113
 
@@ -1,40 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch"
4
- require "action_dispatch/http/upload"
5
3
  require "active_support/core_ext/module/delegation"
6
4
 
7
5
  module ActiveStorage
8
6
  # Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
9
7
  # classes that both provide proxy access to the blob association for a record.
10
8
  class Attached
11
- attr_reader :name, :record, :dependent
9
+ attr_reader :name, :record
12
10
 
13
- def initialize(name, record, dependent:)
14
- @name, @record, @dependent = name, record, dependent
11
+ def initialize(name, record)
12
+ @name, @record = name, record
15
13
  end
16
14
 
17
15
  private
18
- def create_blob_from(attachable)
19
- case attachable
20
- when ActiveStorage::Blob
21
- attachable
22
- when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
23
- ActiveStorage::Blob.create_after_upload! \
24
- io: attachable.open,
25
- filename: attachable.original_filename,
26
- content_type: attachable.content_type
27
- when Hash
28
- ActiveStorage::Blob.create_after_upload!(attachable)
29
- when String
30
- ActiveStorage::Blob.find_signed(attachable)
31
- else
32
- nil
33
- end
16
+ def change
17
+ record.attachment_changes[name]
34
18
  end
35
19
  end
36
20
  end
37
21
 
22
+ require "active_storage/attached/model"
38
23
  require "active_storage/attached/one"
39
24
  require "active_storage/attached/many"
40
- require "active_storage/attached/macros"
25
+ require "active_storage/attached/changes"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module Attached::Changes #:nodoc:
5
+ extend ActiveSupport::Autoload
6
+
7
+ eager_autoload do
8
+ autoload :CreateOne
9
+ autoload :CreateMany
10
+ autoload :CreateOneOfMany
11
+
12
+ autoload :DeleteOne
13
+ autoload :DeleteMany
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::CreateMany #:nodoc:
5
+ attr_reader :name, :record, :attachables
6
+
7
+ def initialize(name, record, attachables)
8
+ @name, @record, @attachables = name, record, Array(attachables)
9
+ end
10
+
11
+ def attachments
12
+ @attachments ||= subchanges.collect(&:attachment)
13
+ end
14
+
15
+ def blobs
16
+ @blobs ||= subchanges.collect(&:blob)
17
+ end
18
+
19
+ def upload
20
+ subchanges.each(&:upload)
21
+ end
22
+
23
+ def save
24
+ assign_associated_attachments
25
+ reset_associated_blobs
26
+ end
27
+
28
+ private
29
+ def subchanges
30
+ @subchanges ||= attachables.collect { |attachable| build_subchange_from(attachable) }
31
+ end
32
+
33
+ def build_subchange_from(attachable)
34
+ ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
35
+ end
36
+
37
+
38
+ def assign_associated_attachments
39
+ record.public_send("#{name}_attachments=", attachments)
40
+ end
41
+
42
+ def reset_associated_blobs
43
+ record.public_send("#{name}_blobs").reset
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch"
4
+ require "action_dispatch/http/upload"
5
+
6
+ module ActiveStorage
7
+ class Attached::Changes::CreateOne #:nodoc:
8
+ attr_reader :name, :record, :attachable
9
+
10
+ def initialize(name, record, attachable)
11
+ @name, @record, @attachable = name, record, attachable
12
+ end
13
+
14
+ def attachment
15
+ @attachment ||= find_or_build_attachment
16
+ end
17
+
18
+ def blob
19
+ @blob ||= find_or_build_blob
20
+ end
21
+
22
+ def upload
23
+ case attachable
24
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
25
+ blob.upload_without_unfurling(attachable.open)
26
+ when Hash
27
+ blob.upload_without_unfurling(attachable.fetch(:io))
28
+ end
29
+ end
30
+
31
+ def save
32
+ record.public_send("#{name}_attachment=", attachment)
33
+ record.public_send("#{name}_blob=", blob)
34
+ end
35
+
36
+ private
37
+ def find_or_build_attachment
38
+ find_attachment || build_attachment
39
+ end
40
+
41
+ def find_attachment
42
+ if record.public_send("#{name}_blob") == blob
43
+ record.public_send("#{name}_attachment")
44
+ end
45
+ end
46
+
47
+ def build_attachment
48
+ ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
49
+ end
50
+
51
+ def find_or_build_blob
52
+ case attachable
53
+ when ActiveStorage::Blob
54
+ attachable
55
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
56
+ ActiveStorage::Blob.build_after_unfurling \
57
+ io: attachable.open,
58
+ filename: attachable.original_filename,
59
+ content_type: attachable.content_type
60
+ when Hash
61
+ ActiveStorage::Blob.build_after_unfurling(attachable)
62
+ when String
63
+ ActiveStorage::Blob.find_signed(attachable)
64
+ else
65
+ raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
66
+ end
67
+ end
68
+ end
69
+ end