activestorage 6.0.4.1 → 6.1.0.rc1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +141 -192
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +36 -4
  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/disk_controller.rb +8 -20
  9. data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
  10. data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +2 -2
  11. data/app/controllers/concerns/active_storage/file_server.rb +18 -0
  12. data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
  13. data/app/controllers/concerns/active_storage/set_current.rb +2 -2
  14. data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
  15. data/app/jobs/active_storage/mirror_job.rb +15 -0
  16. data/app/models/active_storage/attachment.rb +18 -10
  17. data/app/models/active_storage/blob/analyzable.rb +6 -2
  18. data/app/models/active_storage/blob/identifiable.rb +7 -6
  19. data/app/models/active_storage/blob/representable.rb +34 -4
  20. data/app/models/active_storage/blob.rb +114 -57
  21. data/app/models/active_storage/preview.rb +31 -10
  22. data/app/models/active_storage/record.rb +7 -0
  23. data/app/models/active_storage/variant.rb +28 -41
  24. data/app/models/active_storage/variant_record.rb +8 -0
  25. data/app/models/active_storage/variant_with_record.rb +54 -0
  26. data/app/models/active_storage/variation.rb +25 -20
  27. data/config/routes.rb +58 -8
  28. data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
  29. data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
  30. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
  31. data/lib/active_storage/analyzer/image_analyzer.rb +3 -0
  32. data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
  33. data/lib/active_storage/analyzer/video_analyzer.rb +14 -3
  34. data/lib/active_storage/analyzer.rb +6 -0
  35. data/lib/active_storage/attached/changes/create_many.rb +1 -0
  36. data/lib/active_storage/attached/changes/create_one.rb +17 -4
  37. data/lib/active_storage/attached/many.rb +4 -3
  38. data/lib/active_storage/attached/model.rb +49 -10
  39. data/lib/active_storage/attached/one.rb +4 -3
  40. data/lib/active_storage/engine.rb +25 -27
  41. data/lib/active_storage/gem_version.rb +3 -3
  42. data/lib/active_storage/log_subscriber.rb +6 -0
  43. data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
  44. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +2 -2
  45. data/lib/active_storage/previewer/video_previewer.rb +2 -2
  46. data/lib/active_storage/previewer.rb +3 -2
  47. data/lib/active_storage/service/azure_storage_service.rb +40 -35
  48. data/lib/active_storage/service/configurator.rb +3 -1
  49. data/lib/active_storage/service/disk_service.rb +36 -31
  50. data/lib/active_storage/service/gcs_service.rb +18 -16
  51. data/lib/active_storage/service/mirror_service.rb +31 -7
  52. data/lib/active_storage/service/registry.rb +32 -0
  53. data/lib/active_storage/service/s3_service.rb +51 -23
  54. data/lib/active_storage/service.rb +35 -7
  55. data/lib/active_storage/transformers/image_processing_transformer.rb +13 -7
  56. data/lib/active_storage/transformers/transformer.rb +0 -3
  57. data/lib/active_storage.rb +5 -2
  58. metadata +60 -24
  59. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +0 -9
  60. data/lib/active_storage/downloading.rb +0 -47
  61. data/lib/active_storage/transformers/mini_magick_transformer.rb +0 -38
@@ -0,0 +1,17 @@
1
+ class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
2
+ def up
3
+ unless column_exists?(:active_storage_blobs, :service_name)
4
+ add_column :active_storage_blobs, :service_name, :string
5
+
6
+ if configured_service = ActiveStorage::Blob.service.name
7
+ ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
8
+ end
9
+
10
+ change_column :active_storage_blobs, :service_name, :string, null: false
11
+ end
12
+ end
13
+
14
+ def down
15
+ remove_column :active_storage_blobs, :service_name
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :active_storage_variant_records do |t|
4
+ t.belongs_to :blob, null: false, index: false
5
+ t.string :variation_digest, null: false
6
+
7
+ t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
8
+ t.foreign_key :active_storage_blobs, column: :blob_id
9
+ end
10
+ end
11
+ end
@@ -43,6 +43,9 @@ module ActiveStorage
43
43
  rescue LoadError
44
44
  logger.info "Skipping image analysis because the mini_magick gem isn't installed"
45
45
  {}
46
+ rescue MiniMagick::Error => error
47
+ logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
48
+ {}
46
49
  end
47
50
 
48
51
  def rotated_image?(image)
@@ -6,6 +6,10 @@ module ActiveStorage
6
6
  true
7
7
  end
8
8
 
9
+ def self.analyze_later?
10
+ false
11
+ end
12
+
9
13
  def metadata
10
14
  {}
11
15
  end
@@ -44,7 +44,8 @@ module ActiveStorage
44
44
  end
45
45
 
46
46
  def duration
47
- Float(video_stream["duration"]) if video_stream["duration"]
47
+ duration = video_stream["duration"] || container["duration"]
48
+ Float(duration) if duration
48
49
  end
49
50
 
50
51
  def angle
@@ -98,12 +99,22 @@ module ActiveStorage
98
99
  probe["streams"] || []
99
100
  end
100
101
 
102
+ def container
103
+ probe["format"] || {}
104
+ end
105
+
101
106
  def probe
102
- download_blob_to_tempfile { |file| probe_from(file) }
107
+ @probe ||= download_blob_to_tempfile { |file| probe_from(file) }
103
108
  end
104
109
 
105
110
  def probe_from(file)
106
- IO.popen([ ffprobe_path, "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
111
+ IO.popen([ ffprobe_path,
112
+ "-print_format", "json",
113
+ "-show_streams",
114
+ "-show_format",
115
+ "-v", "error",
116
+ file.path
117
+ ]) do |output|
107
118
  JSON.parse(output.read)
108
119
  end
109
120
  rescue Errno::ENOENT
@@ -12,6 +12,12 @@ module ActiveStorage
12
12
  false
13
13
  end
14
14
 
15
+ # Implement this method in concrete subclasses. It will determine if blob analysis
16
+ # should be done in a job or performed inline. By default, analysis is enqueued in a job.
17
+ def self.analyze_later?
18
+ true
19
+ end
20
+
15
21
  def initialize(blob)
16
22
  @blob = blob
17
23
  end
@@ -6,6 +6,7 @@ module ActiveStorage
6
6
 
7
7
  def initialize(name, record, attachables)
8
8
  @name, @record, @attachables = name, record, Array(attachables)
9
+ blobs.each(&:identify_without_saving)
9
10
  end
10
11
 
11
12
  def attachments
@@ -9,6 +9,7 @@ module ActiveStorage
9
9
 
10
10
  def initialize(name, record, attachable)
11
11
  @name, @record, @attachable = name, record, attachable
12
+ blob.identify_without_saving
12
13
  end
13
14
 
14
15
  def attachment
@@ -53,17 +54,29 @@ module ActiveStorage
53
54
  when ActiveStorage::Blob
54
55
  attachable
55
56
  when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
56
- ActiveStorage::Blob.build_after_unfurling \
57
+ ActiveStorage::Blob.build_after_unfurling(
57
58
  io: attachable.open,
58
59
  filename: attachable.original_filename,
59
- content_type: attachable.content_type
60
+ content_type: attachable.content_type,
61
+ record: record,
62
+ service_name: attachment_service_name
63
+ )
60
64
  when Hash
61
- ActiveStorage::Blob.build_after_unfurling(**attachable.symbolize_keys)
65
+ ActiveStorage::Blob.build_after_unfurling(
66
+ **attachable.reverse_merge(
67
+ record: record,
68
+ service_name: attachment_service_name
69
+ ).symbolize_keys
70
+ )
62
71
  when String
63
- ActiveStorage::Blob.find_signed(attachable)
72
+ ActiveStorage::Blob.find_signed!(attachable, record: record)
64
73
  else
65
74
  raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
66
75
  end
67
76
  end
77
+
78
+ def attachment_service_name
79
+ record.attachment_reflections[name].options[:service_name]
80
+ end
68
81
  end
69
82
  end
@@ -29,15 +29,16 @@ module ActiveStorage
29
29
  # document.images.attach([ first_blob, second_blob ])
30
30
  def attach(*attachables)
31
31
  if record.persisted? && !record.changed?
32
- record.update(name => blobs + attachables.flatten)
32
+ record.public_send("#{name}=", blobs + attachables.flatten)
33
+ record.save
33
34
  else
34
35
  record.public_send("#{name}=", (change&.attachables || blobs) + attachables.flatten)
35
36
  end
36
37
  end
37
38
 
38
- # Returns true if any attachments has been made.
39
+ # Returns true if any attachments have been made.
39
40
  #
40
- # class Gallery < ActiveRecord::Base
41
+ # class Gallery < ApplicationRecord
41
42
  # has_many_attached :photos
42
43
  # end
43
44
  #
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/try"
4
+
3
5
  module ActiveStorage
4
6
  # Provides the class-level DSL for declaring an Active Record model's attachments.
5
7
  module Attached::Model
@@ -8,7 +10,7 @@ module ActiveStorage
8
10
  class_methods do
9
11
  # Specifies the relation between a single attachment and the model.
10
12
  #
11
- # class User < ActiveRecord::Base
13
+ # class User < ApplicationRecord
12
14
  # has_one_attached :avatar
13
15
  # end
14
16
  #
@@ -30,8 +32,19 @@ module ActiveStorage
30
32
  #
31
33
  # If the +:dependent+ option isn't set, the attachment will be purged
32
34
  # (i.e. destroyed) whenever the record is destroyed.
33
- def has_one_attached(name, dependent: :purge_later)
35
+ #
36
+ # If you need the attachment to use a service which differs from the globally configured one,
37
+ # pass the +:service+ option. For instance:
38
+ #
39
+ # class User < ActiveRecord::Base
40
+ # has_one_attached :avatar, service: :s3
41
+ # end
42
+ #
43
+ def has_one_attached(name, dependent: :purge_later, service: nil)
44
+ validate_service_configuration(name, service)
45
+
34
46
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
47
+ # frozen_string_literal: true
35
48
  def #{name}
36
49
  @active_storage_attached ||= {}
37
50
  @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
@@ -56,16 +69,19 @@ module ActiveStorage
56
69
 
57
70
  after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
58
71
 
59
- ActiveRecord::Reflection.add_attachment_reflection(
60
- self,
72
+ reflection = ActiveRecord::Reflection.create(
73
+ :has_one_attached,
61
74
  name,
62
- ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
75
+ nil,
76
+ { dependent: dependent, service_name: service },
77
+ self
63
78
  )
79
+ ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
64
80
  end
65
81
 
66
82
  # Specifies the relation between multiple attachments and the model.
67
83
  #
68
- # class Gallery < ActiveRecord::Base
84
+ # class Gallery < ApplicationRecord
69
85
  # has_many_attached :photos
70
86
  # end
71
87
  #
@@ -87,8 +103,19 @@ module ActiveStorage
87
103
  #
88
104
  # If the +:dependent+ option isn't set, all the attachments will be purged
89
105
  # (i.e. destroyed) whenever the record is destroyed.
90
- def has_many_attached(name, dependent: :purge_later)
106
+ #
107
+ # If you need the attachment to use a service which differs from the globally configured one,
108
+ # pass the +:service+ option. For instance:
109
+ #
110
+ # class Gallery < ActiveRecord::Base
111
+ # has_many_attached :photos, service: :s3
112
+ # end
113
+ #
114
+ def has_many_attached(name, dependent: :purge_later, service: nil)
115
+ validate_service_configuration(name, service)
116
+
91
117
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
118
+ # frozen_string_literal: true
92
119
  def #{name}
93
120
  @active_storage_attached ||= {}
94
121
  @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
@@ -130,12 +157,24 @@ module ActiveStorage
130
157
 
131
158
  after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
132
159
 
133
- ActiveRecord::Reflection.add_attachment_reflection(
134
- self,
160
+ reflection = ActiveRecord::Reflection.create(
161
+ :has_many_attached,
135
162
  name,
136
- ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
163
+ nil,
164
+ { dependent: dependent, service_name: service },
165
+ self
137
166
  )
167
+ ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
138
168
  end
169
+
170
+ private
171
+ def validate_service_configuration(association_name, service)
172
+ if service.present?
173
+ ActiveStorage::Blob.services.fetch(service) do
174
+ raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
175
+ end
176
+ end
177
+ end
139
178
  end
140
179
 
141
180
  def attachment_changes #:nodoc:
@@ -3,7 +3,7 @@
3
3
  module ActiveStorage
4
4
  # Representation of a single attachment to a model.
5
5
  class Attached::One < Attached
6
- delegate_missing_to :attachment
6
+ delegate_missing_to :attachment, allow_nil: true
7
7
 
8
8
  # Returns the associated attachment record.
9
9
  #
@@ -29,7 +29,8 @@ module ActiveStorage
29
29
  # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
30
30
  def attach(attachable)
31
31
  if record.persisted? && !record.changed?
32
- record.update(name => attachable)
32
+ record.public_send("#{name}=", attachable)
33
+ record.save
33
34
  else
34
35
  record.public_send("#{name}=", attachable)
35
36
  end
@@ -37,7 +38,7 @@ module ActiveStorage
37
38
 
38
39
  # Returns +true+ if an attachment has been made.
39
40
  #
40
- # class User < ActiveRecord::Base
41
+ # class User < ApplicationRecord
41
42
  # has_one_attached :avatar
42
43
  # end
43
44
  #
@@ -14,6 +14,8 @@ require "active_storage/previewer/video_previewer"
14
14
  require "active_storage/analyzer/image_analyzer"
15
15
  require "active_storage/analyzer/video_analyzer"
16
16
 
17
+ require "active_storage/service/registry"
18
+
17
19
  require "active_storage/reflection"
18
20
 
19
21
  module ActiveStorage
@@ -24,7 +26,7 @@ module ActiveStorage
24
26
  config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
25
27
  config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
26
28
  config.active_storage.paths = ActiveSupport::OrderedOptions.new
27
- config.active_storage.queues = ActiveSupport::OrderedOptions.new
29
+ config.active_storage.queues = ActiveSupport::InheritableOptions.new(mirror: :active_storage_mirror)
28
30
 
29
31
  config.active_storage.variable_content_types = %w(
30
32
  image/png
@@ -36,6 +38,14 @@ module ActiveStorage
36
38
  image/bmp
37
39
  image/vnd.adobe.photoshop
38
40
  image/vnd.microsoft.icon
41
+ image/webp
42
+ )
43
+
44
+ config.active_storage.web_image_content_types = %w(
45
+ image/png
46
+ image/jpeg
47
+ image/jpg
48
+ image/gif
39
49
  )
40
50
 
41
51
  config.active_storage.content_types_to_serve_as_binary = %w(
@@ -73,14 +83,18 @@ module ActiveStorage
73
83
  ActiveStorage.analyzers = app.config.active_storage.analyzers || []
74
84
  ActiveStorage.paths = app.config.active_storage.paths || {}
75
85
  ActiveStorage.routes_prefix = app.config.active_storage.routes_prefix || "/rails/active_storage"
86
+ ActiveStorage.draw_routes = app.config.active_storage.draw_routes != false
87
+ ActiveStorage.resolve_model_to_route = app.config.active_storage.resolve_model_to_route || :rails_storage_redirect
76
88
 
77
89
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
90
+ ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
78
91
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
79
92
  ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
80
93
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
81
94
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
82
95
 
83
96
  ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
97
+ ActiveStorage.track_variants = app.config.active_storage.track_variants || false
84
98
  end
85
99
  end
86
100
 
@@ -100,42 +114,26 @@ module ActiveStorage
100
114
 
101
115
  initializer "active_storage.services" do
102
116
  ActiveSupport.on_load(:active_storage_blob) do
103
- if config_choice = Rails.configuration.active_storage.service
104
- configs = Rails.configuration.active_storage.service_configurations ||= begin
105
- config_file = Pathname.new(Rails.root.join("config/storage.yml"))
117
+ configs = Rails.configuration.active_storage.service_configurations ||=
118
+ begin
119
+ config_file = Rails.root.join("config/storage/#{Rails.env}.yml")
120
+ config_file = Rails.root.join("config/storage.yml") unless config_file.exist?
106
121
  raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
107
122
 
108
- require "yaml"
109
- require "erb"
110
-
111
- YAML.load(ERB.new(config_file.read).result) || {}
112
- rescue Psych::SyntaxError => e
113
- raise "YAML syntax error occurred while parsing #{config_file}. " \
114
- "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
115
- "Error: #{e.message}"
123
+ ActiveSupport::ConfigurationFile.parse(config_file)
116
124
  end
117
125
 
118
- ActiveStorage::Blob.service =
119
- begin
120
- ActiveStorage::Service.configure config_choice, configs
121
- rescue => e
122
- raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
123
- end
126
+ ActiveStorage::Blob.services = ActiveStorage::Service::Registry.new(configs)
127
+
128
+ if config_choice = Rails.configuration.active_storage.service
129
+ ActiveStorage::Blob.service = ActiveStorage::Blob.services.fetch(config_choice)
124
130
  end
125
131
  end
126
132
  end
127
133
 
128
134
  initializer "active_storage.queues" do
129
135
  config.after_initialize do |app|
130
- if queue = app.config.active_storage.queue
131
- ActiveSupport::Deprecation.warn \
132
- "config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \
133
- "Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead."
134
-
135
- ActiveStorage.queues = { purge: queue, analysis: queue }
136
- else
137
- ActiveStorage.queues = app.config.active_storage.queues || {}
138
- end
136
+ ActiveStorage.queues = app.config.active_storage.queues || {}
139
137
  end
140
138
  end
141
139
 
@@ -8,9 +8,9 @@ module ActiveStorage
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 6
11
- MINOR = 0
12
- TINY = 4
13
- PRE = "1"
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -32,6 +32,12 @@ module ActiveStorage
32
32
  debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE)
33
33
  end
34
34
 
35
+ def service_mirror(event)
36
+ message = "Mirrored file at key: #{key_in(event)}"
37
+ message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum]
38
+ debug event, color(message, GREEN)
39
+ end
40
+
35
41
  def logger
36
42
  ActiveStorage.logger
37
43
  end
@@ -12,7 +12,7 @@ module ActiveStorage
12
12
  end
13
13
 
14
14
  def mutool_exists?
15
- return @mutool_exists unless @mutool_exists.nil?
15
+ return @mutool_exists if defined?(@mutool_exists) && !@mutool_exists.nil?
16
16
 
17
17
  system mutool_path, out: File::NULL, err: File::NULL
18
18
 
@@ -20,10 +20,10 @@ module ActiveStorage
20
20
  end
21
21
  end
22
22
 
23
- def preview
23
+ def preview(**options)
24
24
  download_blob_to_tempfile do |input|
25
25
  draw_first_page_from input do |output|
26
- yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
26
+ yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png", **options
27
27
  end
28
28
  end
29
29
  end