activestorage 5.2.4.4 → 6.0.0.beta1

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 +103 -81
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +6 -5
  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 +4 -1
  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 +18 -9
  16. data/app/models/active_storage/blob.rb +63 -22
  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 +23 -32
  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 +7 -0
  24. data/lib/active_storage.rb +13 -2
  25. data/lib/active_storage/analyzer.rb +9 -4
  26. data/lib/active_storage/analyzer/video_analyzer.rb +2 -4
  27. data/lib/active_storage/attached.rb +7 -22
  28. data/lib/active_storage/attached/changes.rb +16 -0
  29. data/lib/active_storage/attached/changes/create_many.rb +46 -0
  30. data/lib/active_storage/attached/changes/create_one.rb +68 -0
  31. data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
  32. data/lib/active_storage/attached/changes/delete_many.rb +23 -0
  33. data/lib/active_storage/attached/changes/delete_one.rb +19 -0
  34. data/lib/active_storage/attached/many.rb +16 -10
  35. data/lib/active_storage/attached/model.rb +140 -0
  36. data/lib/active_storage/attached/one.rb +16 -19
  37. data/lib/active_storage/downloader.rb +44 -0
  38. data/lib/active_storage/downloading.rb +8 -0
  39. data/lib/active_storage/engine.rb +35 -6
  40. data/lib/active_storage/errors.rb +22 -3
  41. data/lib/active_storage/gem_version.rb +4 -4
  42. data/lib/active_storage/previewer.rb +21 -11
  43. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +1 -1
  44. data/lib/active_storage/previewer/video_previewer.rb +2 -3
  45. data/lib/active_storage/reflection.rb +64 -0
  46. data/lib/active_storage/service.rb +5 -6
  47. data/lib/active_storage/service/azure_storage_service.rb +28 -12
  48. data/lib/active_storage/service/configurator.rb +3 -1
  49. data/lib/active_storage/service/disk_service.rb +20 -16
  50. data/lib/active_storage/service/gcs_service.rb +48 -46
  51. data/lib/active_storage/service/mirror_service.rb +1 -1
  52. data/lib/active_storage/service/s3_service.rb +10 -7
  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 +26 -14
  58. data/app/models/active_storage/filename/parameters.rb +0 -36
  59. data/lib/active_storage/attached/macros.rb +0 -110
@@ -10,26 +10,28 @@ module ActiveStorage
10
10
  # You don't have to call this method to access the attachment's methods as
11
11
  # they are all available at the model level.
12
12
  def attachment
13
- record.public_send("#{name}_attachment")
13
+ change.present? ? change.attachment : record.public_send("#{name}_attachment")
14
14
  end
15
15
 
16
- # Associates a given attachment with the current record, saving it to the database.
16
+ def blank?
17
+ !attached?
18
+ end
19
+
20
+ # Attaches an +attachable+ to the record.
21
+ #
22
+ # If the record is persisted and unchanged, the attachment is saved to
23
+ # the database immediately. Otherwise, it'll be saved to the DB when the
24
+ # record is next saved.
17
25
  #
18
26
  # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
19
27
  # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
20
28
  # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
21
29
  # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
22
30
  def attach(attachable)
23
- blob_was = blob if attached?
24
- blob = create_blob_from(attachable)
25
-
26
- unless blob == blob_was
27
- transaction do
28
- detach
29
- write_attachment build_attachment(blob: blob)
30
- end
31
-
32
- blob_was.purge_later if blob_was && dependent == :purge_later
31
+ if record.persisted? && !record.changed?
32
+ record.update(name => attachable)
33
+ else
34
+ record.public_send("#{name}=", attachable)
33
35
  end
34
36
  end
35
37
 
@@ -47,7 +49,7 @@ module ActiveStorage
47
49
  # Deletes the attachment without purging it, leaving its blob in place.
48
50
  def detach
49
51
  if attached?
50
- attachment.destroy
52
+ attachment.delete
51
53
  write_attachment nil
52
54
  end
53
55
  end
@@ -65,16 +67,11 @@ module ActiveStorage
65
67
  def purge_later
66
68
  if attached?
67
69
  attachment.purge_later
70
+ write_attachment nil
68
71
  end
69
72
  end
70
73
 
71
74
  private
72
- delegate :transaction, to: :record
73
-
74
- def build_attachment(blob:)
75
- ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
76
- end
77
-
78
75
  def write_attachment(attachment)
79
76
  record.public_send("#{name}_attachment=", attachment)
80
77
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Downloader #:nodoc:
5
+ def initialize(blob, tempdir: nil)
6
+ @blob = blob
7
+ @tempdir = tempdir
8
+ end
9
+
10
+ def download_blob_to_tempfile
11
+ open_tempfile do |file|
12
+ download_blob_to file
13
+ verify_integrity_of file
14
+ yield file
15
+ end
16
+ end
17
+
18
+ private
19
+ attr_reader :blob, :tempdir
20
+
21
+ def open_tempfile
22
+ file = Tempfile.open([ "ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter ], tempdir)
23
+
24
+ begin
25
+ yield file
26
+ ensure
27
+ file.close!
28
+ end
29
+ end
30
+
31
+ def download_blob_to(file)
32
+ file.binmode
33
+ blob.download { |chunk| file.write(chunk) }
34
+ file.flush
35
+ file.rewind
36
+ end
37
+
38
+ def verify_integrity_of(file)
39
+ unless Digest::MD5.file(file).base64digest == blob.checksum
40
+ raise ActiveStorage::IntegrityError
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,9 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tmpdir"
4
+ require "active_support/core_ext/string/filters"
4
5
 
5
6
  module ActiveStorage
6
7
  module Downloading
8
+ def self.included(klass)
9
+ ActiveSupport::Deprecation.warn <<~MESSAGE.squish, caller_locations(2)
10
+ ActiveStorage::Downloading is deprecated and will be removed in Active Storage 6.1.
11
+ Use ActiveStorage::Blob#open instead.
12
+ MESSAGE
13
+ end
14
+
7
15
  private
8
16
  # Opens a new tempfile in #tempdir and copies blob data into it. Yields the tempfile.
9
17
  def download_blob_to_tempfile #:doc:
@@ -10,6 +10,8 @@ require "active_storage/previewer/video_previewer"
10
10
  require "active_storage/analyzer/image_analyzer"
11
11
  require "active_storage/analyzer/video_analyzer"
12
12
 
13
+ require "active_storage/reflection"
14
+
13
15
  module ActiveStorage
14
16
  class Engine < Rails::Engine # :nodoc:
15
17
  isolate_namespace ActiveStorage
@@ -18,12 +20,15 @@ module ActiveStorage
18
20
  config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
19
21
  config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
20
22
  config.active_storage.paths = ActiveSupport::OrderedOptions.new
23
+ config.active_storage.queues = ActiveSupport::OrderedOptions.new
21
24
 
22
25
  config.active_storage.variable_content_types = %w(
23
26
  image/png
24
27
  image/gif
25
28
  image/jpg
26
29
  image/jpeg
30
+ image/pjpeg
31
+ image/tiff
27
32
  image/vnd.adobe.photoshop
28
33
  image/vnd.microsoft.icon
29
34
  )
@@ -46,6 +51,7 @@ module ActiveStorage
46
51
  image/gif
47
52
  image/jpg
48
53
  image/jpeg
54
+ image/tiff
49
55
  image/vnd.adobe.photoshop
50
56
  image/vnd.microsoft.icon
51
57
  application/pdf
@@ -55,14 +61,16 @@ module ActiveStorage
55
61
 
56
62
  initializer "active_storage.configs" do
57
63
  config.after_initialize do |app|
58
- ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
59
- ActiveStorage.queue = app.config.active_storage.queue
60
- ActiveStorage.previewers = app.config.active_storage.previewers || []
61
- ActiveStorage.analyzers = app.config.active_storage.analyzers || []
62
- ActiveStorage.paths = app.config.active_storage.paths || {}
64
+ ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
65
+ ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
66
+ ActiveStorage.previewers = app.config.active_storage.previewers || []
67
+ ActiveStorage.analyzers = app.config.active_storage.analyzers || []
68
+ ActiveStorage.paths = app.config.active_storage.paths || {}
69
+ ActiveStorage.routes_prefix = app.config.active_storage.routes_prefix || "/rails/active_storage"
63
70
 
64
71
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
65
72
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
73
+ ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
66
74
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
67
75
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
68
76
  end
@@ -72,7 +80,7 @@ module ActiveStorage
72
80
  require "active_storage/attached"
73
81
 
74
82
  ActiveSupport.on_load(:active_record) do
75
- extend ActiveStorage::Attached::Macros
83
+ include ActiveStorage::Attached::Model
76
84
  end
77
85
  end
78
86
 
@@ -108,5 +116,26 @@ module ActiveStorage
108
116
  end
109
117
  end
110
118
  end
119
+
120
+ initializer "active_storage.queues" do
121
+ config.after_initialize do |app|
122
+ if queue = app.config.active_storage.queue
123
+ ActiveSupport::Deprecation.warn \
124
+ "config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \
125
+ "Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead."
126
+
127
+ ActiveStorage.queues = { purge: queue, analysis: queue }
128
+ else
129
+ ActiveStorage.queues = app.config.active_storage.queues || {}
130
+ end
131
+ end
132
+ end
133
+
134
+ initializer "active_storage.reflection" do
135
+ ActiveSupport.on_load(:active_record) do
136
+ include Reflection::ActiveRecordExtensions
137
+ ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
138
+ end
139
+ end
111
140
  end
112
141
  end
@@ -1,7 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class InvariableError < StandardError; end
5
- class UnpreviewableError < StandardError; end
6
- class UnrepresentableError < StandardError; end
4
+ # Generic base class for all Active Storage exceptions.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when ActiveStorage::Blob#variant is called on a blob that isn't variable.
8
+ # Use ActiveStorage::Blob#variable? to determine whether a blob is variable.
9
+ class InvariableError < Error; end
10
+
11
+ # Raised when ActiveStorage::Blob#preview is called on a blob that isn't previewable.
12
+ # Use ActiveStorage::Blob#previewable? to determine whether a blob is previewable.
13
+ class UnpreviewableError < Error; end
14
+
15
+ # Raised when ActiveStorage::Blob#representation is called on a blob that isn't representable.
16
+ # Use ActiveStorage::Blob#representable? to determine whether a blob is representable.
17
+ class UnrepresentableError < Error; end
18
+
19
+ # Raised when uploaded or downloaded data does not match a precomputed checksum.
20
+ # Indicates that a network error or a software bug caused data corruption.
21
+ class IntegrityError < Error; end
22
+
23
+ # Raised when ActiveStorage::Blob#download is called on a blob where the
24
+ # backing file is no longer present in its service.
25
+ class FileNotFoundError < Error; end
7
26
  end
@@ -7,10 +7,10 @@ module ActiveStorage
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 4
13
- PRE = "4"
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -1,14 +1,10 @@
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 previewers, which generate images from blobs. See
7
5
  # ActiveStorage::Previewer::MuPDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for
8
6
  # examples of concrete subclasses.
9
7
  class Previewer
10
- include Downloading
11
-
12
8
  attr_reader :blob
13
9
 
14
10
  # Implement this method in a concrete subclass. Have it return true when given a blob from which
@@ -28,9 +24,14 @@ module ActiveStorage
28
24
  end
29
25
 
30
26
  private
27
+ # Downloads the blob to a tempfile on disk. Yields the tempfile.
28
+ def download_blob_to_tempfile(&block) #:doc:
29
+ blob.open tempdir: tempdir, &block
30
+ end
31
+
31
32
  # Executes a system command, capturing its binary output in a tempfile. Yields the tempfile.
32
33
  #
33
- # Use this method to shell out to a system library (e.g. mupdf or ffmpeg) for preview image
34
+ # Use this method to shell out to a system library (e.g. muPDF or FFmpeg) for preview image
34
35
  # generation. The resulting tempfile can be used as the +:io+ value in an attachable Hash:
35
36
  #
36
37
  # def preview
@@ -41,18 +42,19 @@ module ActiveStorage
41
42
  # end
42
43
  # end
43
44
  #
44
- # The output tempfile is opened in the directory returned by ActiveStorage::Downloading#tempdir.
45
+ # The output tempfile is opened in the directory returned by #tempdir.
45
46
  def draw(*argv) #:doc:
46
- ActiveSupport::Notifications.instrument("preview.active_storage") do
47
- open_tempfile_for_drawing do |file|
47
+ open_tempfile do |file|
48
+ instrument :preview, key: blob.key do
48
49
  capture(*argv, to: file)
49
- yield file
50
50
  end
51
+
52
+ yield file
51
53
  end
52
54
  end
53
55
 
54
- def open_tempfile_for_drawing
55
- tempfile = Tempfile.open("ActiveStorage", tempdir)
56
+ def open_tempfile
57
+ tempfile = Tempfile.open("ActiveStorage-", tempdir)
56
58
 
57
59
  begin
58
60
  yield tempfile
@@ -61,6 +63,10 @@ module ActiveStorage
61
63
  end
62
64
  end
63
65
 
66
+ def instrument(operation, payload = {}, &block)
67
+ ActiveSupport::Notifications.instrument "#{operation}.active_storage", payload, &block
68
+ end
69
+
64
70
  def capture(*argv, to:)
65
71
  to.binmode
66
72
  IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) }
@@ -70,5 +76,9 @@ module ActiveStorage
70
76
  def logger #:doc:
71
77
  ActiveStorage.logger
72
78
  end
79
+
80
+ def tempdir #:doc:
81
+ Dir.tmpdir
82
+ end
73
83
  end
74
84
  end
@@ -12,7 +12,7 @@ module ActiveStorage
12
12
  end
13
13
 
14
14
  def pdftoppm_exists?
15
- return @pdftoppm_exists unless @pdftoppm_exists.nil?
15
+ return @pdftoppm_exists if defined?(@pdftoppm_exists)
16
16
 
17
17
  @pdftoppm_exists = system(pdftoppm_path, "-v", out: File::NULL, err: File::NULL)
18
18
  end
@@ -9,15 +9,14 @@ module ActiveStorage
9
9
  def preview
10
10
  download_blob_to_tempfile do |input|
11
11
  draw_relevant_frame_from input do |output|
12
- yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
12
+ yield io: output, filename: "#{blob.filename.base}.jpg", content_type: "image/jpeg"
13
13
  end
14
14
  end
15
15
  end
16
16
 
17
17
  private
18
18
  def draw_relevant_frame_from(file, &block)
19
- draw ffmpeg_path, "-i", file.path, "-y", "-vcodec", "png",
20
- "-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
19
+ draw ffmpeg_path, "-i", file.path, "-y", "-vframes", "1", "-f", "image2", "-", &block
21
20
  end
22
21
 
23
22
  def ffmpeg_path
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module Reflection
5
+ # Holds all the metadata about a has_one_attached attachment as it was
6
+ # specified in the Active Record class.
7
+ class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
8
+ def macro
9
+ :has_one_attached
10
+ end
11
+ end
12
+
13
+ # Holds all the metadata about a has_many_attached attachment as it was
14
+ # specified in the Active Record class.
15
+ class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
16
+ def macro
17
+ :has_many_attached
18
+ end
19
+ end
20
+
21
+ module ReflectionExtension # :nodoc:
22
+ def add_attachment_reflection(model, name, reflection)
23
+ model.attachment_reflections = model.attachment_reflections.merge(name.to_s => reflection)
24
+ end
25
+
26
+ private
27
+ def reflection_class_for(macro)
28
+ case macro
29
+ when :has_one_attached
30
+ HasOneAttachedReflection
31
+ when :has_many_attached
32
+ HasManyAttachedReflection
33
+ else
34
+ super
35
+ end
36
+ end
37
+ end
38
+
39
+ module ActiveRecordExtensions
40
+ extend ActiveSupport::Concern
41
+
42
+ included do
43
+ class_attribute :attachment_reflections, instance_writer: false, default: {}
44
+ end
45
+
46
+ module ClassMethods
47
+ # Returns an array of reflection objects for all the attachments in the
48
+ # class.
49
+ def reflect_on_all_attachments
50
+ attachment_reflections.values
51
+ end
52
+
53
+ # Returns the reflection object for the named +attachment+.
54
+ #
55
+ # User.reflect_on_attachment(:avatar)
56
+ # # => the avatar reflection
57
+ #
58
+ def reflect_on_attachment(attachment)
59
+ attachment_reflections[attachment.to_s]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_storage/log_subscriber"
4
+ require "action_dispatch"
5
+ require "action_dispatch/http/content_disposition"
4
6
 
5
7
  module ActiveStorage
6
- class IntegrityError < StandardError; end
7
-
8
8
  # Abstract class serving as an interface for concrete services.
9
9
  #
10
10
  # The available services are:
@@ -41,8 +41,6 @@ module ActiveStorage
41
41
  extend ActiveSupport::Autoload
42
42
  autoload :Configurator
43
43
 
44
- class_attribute :url_expires_in, default: 5.minutes
45
-
46
44
  class << self
47
45
  # Configure an Active Storage service by name from a set of configurations,
48
46
  # typically loaded from a YAML file. The Active Storage engine uses this
@@ -100,7 +98,7 @@ module ActiveStorage
100
98
  end
101
99
 
102
100
  # Returns a signed, temporary URL for the file at the +key+. The URL will be valid for the amount
103
- # of seconds specified in +expires_in+. You most also provide the +disposition+ (+:inline+ or +:attachment+),
101
+ # of seconds specified in +expires_in+. You must also provide the +disposition+ (+:inline+ or +:attachment+),
104
102
  # +filename+, and +content_type+ that you wish the file to be served with on request.
105
103
  def url(key, expires_in:, disposition:, filename:, content_type:)
106
104
  raise NotImplementedError
@@ -132,7 +130,8 @@ module ActiveStorage
132
130
  end
133
131
 
134
132
  def content_disposition_with(type: "inline", filename:)
135
- (type.to_s.presence_in(%w( attachment inline )) || "inline") + "; #{filename.parameters}"
133
+ disposition = (type.to_s.presence_in(%w( attachment inline )) || "inline")
134
+ ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename.sanitized)
136
135
  end
137
136
  end
138
137
  end