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
data/config/routes.rb CHANGED
@@ -2,9 +2,13 @@
2
2
 
3
3
  Rails.application.routes.draw do
4
4
  scope ActiveStorage.routes_prefix do
5
- get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
5
+ get "/blobs/redirect/:signed_id/*filename" => "active_storage/blobs/redirect#show", as: :rails_service_blob
6
+ get "/blobs/proxy/:signed_id/*filename" => "active_storage/blobs/proxy#show", as: :rails_service_blob_proxy
7
+ get "/blobs/:signed_id/*filename" => "active_storage/blobs/redirect#show"
6
8
 
7
- get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
9
+ get "/representations/redirect/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/redirect#show", as: :rails_blob_representation
10
+ get "/representations/proxy/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/proxy#show", as: :rails_blob_representation_proxy
11
+ get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/redirect#show"
8
12
 
9
13
  get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
10
14
  put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
@@ -19,14 +23,60 @@ Rails.application.routes.draw do
19
23
  route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
20
24
  end
21
25
 
22
- resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_representation, variant, options) }
23
- resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
24
-
26
+ resolve("ActiveStorage::Variant") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
27
+ resolve("ActiveStorage::VariantWithRecord") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
28
+ resolve("ActiveStorage::Preview") { |preview, options| route_for(ActiveStorage.resolve_model_to_route, preview, options) }
25
29
 
26
30
  direct :rails_blob do |blob, options|
27
31
  route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
28
32
  end
29
33
 
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) }
32
- end
34
+ resolve("ActiveStorage::Blob") { |blob, options| route_for(ActiveStorage.resolve_model_to_route, blob, options) }
35
+ resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
36
+
37
+ direct :rails_storage_proxy do |model, options|
38
+ if model.respond_to?(:signed_id)
39
+ route_for(
40
+ :rails_service_blob_proxy,
41
+ model.signed_id,
42
+ model.filename,
43
+ options
44
+ )
45
+ else
46
+ signed_blob_id = model.blob.signed_id
47
+ variation_key = model.variation.key
48
+ filename = model.blob.filename
49
+
50
+ route_for(
51
+ :rails_blob_representation_proxy,
52
+ signed_blob_id,
53
+ variation_key,
54
+ filename,
55
+ options
56
+ )
57
+ end
58
+ end
59
+
60
+ direct :rails_storage_redirect do |model, options|
61
+ if model.respond_to?(:signed_id)
62
+ route_for(
63
+ :rails_service_blob,
64
+ model.signed_id,
65
+ model.filename,
66
+ options
67
+ )
68
+ else
69
+ signed_blob_id = model.blob.signed_id
70
+ variation_key = model.variation.key
71
+ filename = model.blob.filename
72
+
73
+ route_for(
74
+ :rails_blob_representation,
75
+ signed_blob_id,
76
+ variation_key,
77
+ filename,
78
+ options
79
+ )
80
+ end
81
+ end
82
+ end if ActiveStorage.draw_routes
@@ -1,26 +1,47 @@
1
1
  class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
2
2
  def change
3
- create_table :active_storage_blobs do |t|
4
- t.string :key, null: false
5
- t.string :filename, null: false
3
+ # Use Active Record's configured type for primary and foreign keys
4
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
5
+
6
+ create_table :active_storage_blobs, id: primary_key_type do |t|
7
+ t.string :key, null: false
8
+ t.string :filename, null: false
6
9
  t.string :content_type
7
10
  t.text :metadata
8
- t.bigint :byte_size, null: false
9
- t.string :checksum, null: false
10
- t.datetime :created_at, null: false
11
+ t.string :service_name, null: false
12
+ t.bigint :byte_size, null: false
13
+ t.string :checksum, null: false
14
+ t.datetime :created_at, null: false
11
15
 
12
16
  t.index [ :key ], unique: true
13
17
  end
14
18
 
15
- create_table :active_storage_attachments do |t|
19
+ create_table :active_storage_attachments, id: primary_key_type do |t|
16
20
  t.string :name, null: false
17
- t.references :record, null: false, polymorphic: true, index: false
18
- t.references :blob, null: false
21
+ t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
22
+ t.references :blob, null: false, type: foreign_key_type
19
23
 
20
24
  t.datetime :created_at, null: false
21
25
 
22
26
  t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
23
27
  t.foreign_key :active_storage_blobs, column: :blob_id
24
28
  end
29
+
30
+ create_table :active_storage_variant_records, id: primary_key_type do |t|
31
+ t.belongs_to :blob, null: false, index: false, type: foreign_key_type
32
+ t.string :variation_digest, null: false
33
+
34
+ t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
35
+ t.foreign_key :active_storage_blobs, column: :blob_id
36
+ end
25
37
  end
38
+
39
+ private
40
+ def primary_and_foreign_key_types
41
+ config = Rails.configuration.generators
42
+ setting = config.options[config.orm][:primary_key_type]
43
+ primary_key_type = setting || :primary_key
44
+ foreign_key_type = setting || :bigint
45
+ [primary_key_type, foreign_key_type]
46
+ end
26
47
  end
@@ -0,0 +1,21 @@
1
+ class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
2
+ def up
3
+ return unless table_exists?(:active_storage_blobs)
4
+
5
+ unless column_exists?(:active_storage_blobs, :service_name)
6
+ add_column :active_storage_blobs, :service_name, :string
7
+
8
+ if configured_service = ActiveStorage::Blob.service.name
9
+ ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
10
+ end
11
+
12
+ change_column :active_storage_blobs, :service_name, :string, null: false
13
+ end
14
+ end
15
+
16
+ def down
17
+ return unless table_exists?(:active_storage_blobs)
18
+
19
+ remove_column :active_storage_blobs, :service_name
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
2
+ def change
3
+ return unless table_exists?(:active_storage_blobs)
4
+
5
+ # Use Active Record's configured type for primary key
6
+ create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t|
7
+ t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
8
+ t.string :variation_digest, null: false
9
+
10
+ t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
11
+ t.foreign_key :active_storage_blobs, column: :blob_id
12
+ end
13
+ end
14
+
15
+ private
16
+ def primary_key_type
17
+ config = Rails.configuration.generators
18
+ config.options[config.orm][:primary_key_type] || :primary_key
19
+ end
20
+
21
+ def blobs_primary_key_type
22
+ pkey_name = connection.primary_key(:active_storage_blobs)
23
+ pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
24
+ pkey_column.bigint? ? :bigint : pkey_column.type
25
+ end
26
+ 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,26 @@ 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
+ # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
44
+ # pass the +:strict_loading+ option. You can do:
45
+ #
46
+ # class User < ApplicationRecord
47
+ # has_one_attached :avatar, strict_loading: true
48
+ # end
49
+ #
50
+ def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
51
+ validate_service_configuration(name, service)
52
+
34
53
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
54
+ # frozen_string_literal: true
35
55
  def #{name}
36
56
  @active_storage_attached ||= {}
37
57
  @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
@@ -47,8 +67,8 @@ module ActiveStorage
47
67
  end
48
68
  CODE
49
69
 
50
- has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy
51
- has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
70
+ has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy, strict_loading: strict_loading
71
+ has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading
52
72
 
53
73
  scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
54
74
 
@@ -56,16 +76,19 @@ module ActiveStorage
56
76
 
57
77
  after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
58
78
 
59
- ActiveRecord::Reflection.add_attachment_reflection(
60
- self,
79
+ reflection = ActiveRecord::Reflection.create(
80
+ :has_one_attached,
61
81
  name,
62
- ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
82
+ nil,
83
+ { dependent: dependent, service_name: service },
84
+ self
63
85
  )
86
+ ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
64
87
  end
65
88
 
66
89
  # Specifies the relation between multiple attachments and the model.
67
90
  #
68
- # class Gallery < ActiveRecord::Base
91
+ # class Gallery < ApplicationRecord
69
92
  # has_many_attached :photos
70
93
  # end
71
94
  #
@@ -87,8 +110,26 @@ module ActiveStorage
87
110
  #
88
111
  # If the +:dependent+ option isn't set, all the attachments will be purged
89
112
  # (i.e. destroyed) whenever the record is destroyed.
90
- def has_many_attached(name, dependent: :purge_later)
113
+ #
114
+ # If you need the attachment to use a service which differs from the globally configured one,
115
+ # pass the +:service+ option. For instance:
116
+ #
117
+ # class Gallery < ActiveRecord::Base
118
+ # has_many_attached :photos, service: :s3
119
+ # end
120
+ #
121
+ # If you need to enable +strict_loading+ to prevent lazy loading of attachments,
122
+ # pass the +:strict_loading+ option. You can do:
123
+ #
124
+ # class Gallery < ApplicationRecord
125
+ # has_many_attached :photos, strict_loading: true
126
+ # end
127
+ #
128
+ def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
129
+ validate_service_configuration(name, service)
130
+
91
131
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
132
+ # frozen_string_literal: true
92
133
  def #{name}
93
134
  @active_storage_attached ||= {}
94
135
  @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
@@ -111,7 +152,7 @@ module ActiveStorage
111
152
  end
112
153
  CODE
113
154
 
114
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy do
155
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy, strict_loading: strict_loading do
115
156
  def purge
116
157
  each(&:purge)
117
158
  reset
@@ -122,7 +163,7 @@ module ActiveStorage
122
163
  reset
123
164
  end
124
165
  end
125
- has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
166
+ has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob, strict_loading: strict_loading
126
167
 
127
168
  scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
128
169
 
@@ -130,12 +171,24 @@ module ActiveStorage
130
171
 
131
172
  after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
132
173
 
133
- ActiveRecord::Reflection.add_attachment_reflection(
134
- self,
174
+ reflection = ActiveRecord::Reflection.create(
175
+ :has_many_attached,
135
176
  name,
136
- ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
177
+ nil,
178
+ { dependent: dependent, service_name: service },
179
+ self
137
180
  )
181
+ ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
138
182
  end
183
+
184
+ private
185
+ def validate_service_configuration(association_name, service)
186
+ if service.present?
187
+ ActiveStorage::Blob.services.fetch(service) do
188
+ raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
189
+ end
190
+ end
191
+ end
139
192
  end
140
193
 
141
194
  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
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(
@@ -63,20 +73,6 @@ module ActiveStorage
63
73
  application/pdf
64
74
  )
65
75
 
66
- default_unsupported_image_processing_arguments = %w(
67
- -debug
68
- -display
69
- -distribute-cache
70
- -help
71
- -path
72
- -print
73
- -set
74
- -verbose
75
- -version
76
- -write
77
- -write-mask
78
- )
79
-
80
76
  config.eager_load_namespaces << ActiveStorage
81
77
 
82
78
  initializer "active_storage.configs" do
@@ -87,16 +83,34 @@ module ActiveStorage
87
83
  ActiveStorage.analyzers = app.config.active_storage.analyzers || []
88
84
  ActiveStorage.paths = app.config.active_storage.paths || {}
89
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
88
+
89
+ ActiveStorage.supported_image_processing_methods += app.config.active_storage.supported_image_processing_methods || []
90
+ ActiveStorage.unsupported_image_processing_arguments = app.config.active_storage.unsupported_image_processing_arguments || %w(
91
+ -debug
92
+ -display
93
+ -distribute-cache
94
+ -help
95
+ -path
96
+ -print
97
+ -set
98
+ -verbose
99
+ -version
100
+ -write
101
+ -write-mask
102
+ )
90
103
 
91
- ActiveStorage.supported_image_processing_methods = app.config.active_storage.supported_image_processing_methods || []
92
- ActiveStorage.unsupported_image_processing_arguments = app.config.active_storage.unsupported_image_processing_arguments || default_unsupported_image_processing_arguments
93
104
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
105
+ ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
94
106
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
95
107
  ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
96
108
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
97
109
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
110
+ ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
98
111
 
99
112
  ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
113
+ ActiveStorage.track_variants = app.config.active_storage.track_variants || false
100
114
  end
101
115
  end
102
116
 
@@ -116,42 +130,26 @@ module ActiveStorage
116
130
 
117
131
  initializer "active_storage.services" do
118
132
  ActiveSupport.on_load(:active_storage_blob) do
119
- if config_choice = Rails.configuration.active_storage.service
120
- configs = Rails.configuration.active_storage.service_configurations ||= begin
121
- config_file = Pathname.new(Rails.root.join("config/storage.yml"))
133
+ configs = Rails.configuration.active_storage.service_configurations ||=
134
+ begin
135
+ config_file = Rails.root.join("config/storage/#{Rails.env}.yml")
136
+ config_file = Rails.root.join("config/storage.yml") unless config_file.exist?
122
137
  raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
123
138
 
124
- require "yaml"
125
- require "erb"
126
-
127
- YAML.load(ERB.new(config_file.read).result) || {}
128
- rescue Psych::SyntaxError => e
129
- raise "YAML syntax error occurred while parsing #{config_file}. " \
130
- "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
131
- "Error: #{e.message}"
139
+ ActiveSupport::ConfigurationFile.parse(config_file)
132
140
  end
133
141
 
134
- ActiveStorage::Blob.service =
135
- begin
136
- ActiveStorage::Service.configure config_choice, configs
137
- rescue => e
138
- raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
139
- end
142
+ ActiveStorage::Blob.services = ActiveStorage::Service::Registry.new(configs)
143
+
144
+ if config_choice = Rails.configuration.active_storage.service
145
+ ActiveStorage::Blob.service = ActiveStorage::Blob.services.fetch(config_choice)
140
146
  end
141
147
  end
142
148
  end
143
149
 
144
150
  initializer "active_storage.queues" do
145
151
  config.after_initialize do |app|
146
- if queue = app.config.active_storage.queue
147
- ActiveSupport::Deprecation.warn \
148
- "config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \
149
- "Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead."
150
-
151
- ActiveStorage.queues = { purge: queue, analysis: queue }
152
- else
153
- ActiveStorage.queues = app.config.active_storage.queues || {}
154
- end
152
+ ActiveStorage.queues = app.config.active_storage.queues || {}
155
153
  end
156
154
  end
157
155
 
@@ -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