activestorage 6.0.3.3 → 6.1.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +152 -149
  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/proxy_controller.rb +19 -0
  11. data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +2 -2
  12. data/app/controllers/concerns/active_storage/file_server.rb +18 -0
  13. data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
  14. data/app/controllers/concerns/active_storage/set_current.rb +2 -2
  15. data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
  16. data/app/jobs/active_storage/mirror_job.rb +15 -0
  17. data/app/models/active_storage/attachment.rb +18 -10
  18. data/app/models/active_storage/blob.rb +114 -59
  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/preview.rb +31 -10
  23. data/app/models/active_storage/record.rb +7 -0
  24. data/app/models/active_storage/variant.rb +28 -41
  25. data/app/models/active_storage/variant_record.rb +8 -0
  26. data/app/models/active_storage/variant_with_record.rb +54 -0
  27. data/app/models/active_storage/variation.rb +25 -20
  28. data/config/routes.rb +58 -8
  29. data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
  30. data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
  31. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
  32. data/lib/active_storage.rb +5 -2
  33. data/lib/active_storage/analyzer.rb +6 -0
  34. data/lib/active_storage/analyzer/image_analyzer.rb +3 -0
  35. data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
  36. data/lib/active_storage/analyzer/video_analyzer.rb +14 -3
  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 +77 -16
  41. data/lib/active_storage/attached/one.rb +4 -3
  42. data/lib/active_storage/engine.rb +25 -27
  43. data/lib/active_storage/gem_version.rb +3 -3
  44. data/lib/active_storage/log_subscriber.rb +6 -0
  45. data/lib/active_storage/previewer.rb +3 -2
  46. data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
  47. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +3 -3
  48. data/lib/active_storage/previewer/video_previewer.rb +2 -2
  49. data/lib/active_storage/service.rb +36 -7
  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 +53 -23
  57. data/lib/active_storage/transformers/image_processing_transformer.rb +13 -7
  58. data/lib/active_storage/transformers/transformer.rb +0 -3
  59. metadata +56 -20
  60. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +0 -9
  61. data/lib/active_storage/downloading.rb +0 -47
  62. data/lib/active_storage/transformers/mini_magick_transformer.rb +0 -38
@@ -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,13 +1,14 @@
1
1
  class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :active_storage_blobs do |t|
4
- t.string :key, null: false
5
- t.string :filename, null: false
4
+ t.string :key, null: false
5
+ t.string :filename, null: false
6
6
  t.string :content_type
7
7
  t.text :metadata
8
- t.bigint :byte_size, null: false
9
- t.string :checksum, null: false
10
- t.datetime :created_at, null: false
8
+ t.string :service_name, null: false
9
+ t.bigint :byte_size, null: false
10
+ t.string :checksum, null: false
11
+ t.datetime :created_at, null: false
11
12
 
12
13
  t.index [ :key ], unique: true
13
14
  end
@@ -22,5 +23,13 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
22
23
  t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
23
24
  t.foreign_key :active_storage_blobs, column: :blob_id
24
25
  end
26
+
27
+ create_table :active_storage_variant_records do |t|
28
+ t.belongs_to :blob, null: false, index: false
29
+ t.string :variation_digest, null: false
30
+
31
+ t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
32
+ t.foreign_key :active_storage_blobs, column: :blob_id
33
+ end
25
34
  end
26
35
  end
@@ -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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2017-2019 David Heinemeier Hansson, Basecamp
4
+ # Copyright (c) 2017-2020 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
@@ -53,6 +53,7 @@ module ActiveStorage
53
53
  mattr_accessor :paths, default: {}
54
54
 
55
55
  mattr_accessor :variable_content_types, default: []
56
+ mattr_accessor :web_image_content_types, default: []
56
57
  mattr_accessor :binary_content_type, default: "application/octet-stream"
57
58
  mattr_accessor :content_types_to_serve_as_binary, default: []
58
59
  mattr_accessor :content_types_allowed_inline, default: []
@@ -60,14 +61,16 @@ module ActiveStorage
60
61
  mattr_accessor :service_urls_expire_in, default: 5.minutes
61
62
 
62
63
  mattr_accessor :routes_prefix, default: "/rails/active_storage"
64
+ mattr_accessor :draw_routes, default: true
65
+ mattr_accessor :resolve_model_to_route, default: :rails_storage_redirect
63
66
 
64
67
  mattr_accessor :replace_on_assign_to_many, default: false
68
+ mattr_accessor :track_variants, default: false
65
69
 
66
70
  module Transformers
67
71
  extend ActiveSupport::Autoload
68
72
 
69
73
  autoload :Transformer
70
74
  autoload :ImageProcessingTransformer
71
- autoload :MiniMagickTransformer
72
75
  end
73
76
  end
@@ -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
@@ -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
@@ -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)
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,10 +32,29 @@ 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
- @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self)
56
+ @active_storage_attached ||= {}
57
+ @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
37
58
  end
38
59
 
39
60
  def #{name}=(attachable)
@@ -46,8 +67,8 @@ module ActiveStorage
46
67
  end
47
68
  CODE
48
69
 
49
- has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy
50
- 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
51
72
 
52
73
  scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
53
74
 
@@ -55,16 +76,19 @@ module ActiveStorage
55
76
 
56
77
  after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
57
78
 
58
- ActiveRecord::Reflection.add_attachment_reflection(
59
- self,
79
+ reflection = ActiveRecord::Reflection.create(
80
+ :has_one_attached,
60
81
  name,
61
- ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
82
+ nil,
83
+ { dependent: dependent, service_name: service },
84
+ self
62
85
  )
86
+ ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
63
87
  end
64
88
 
65
89
  # Specifies the relation between multiple attachments and the model.
66
90
  #
67
- # class Gallery < ActiveRecord::Base
91
+ # class Gallery < ApplicationRecord
68
92
  # has_many_attached :photos
69
93
  # end
70
94
  #
@@ -86,10 +110,29 @@ module ActiveStorage
86
110
  #
87
111
  # If the +:dependent+ option isn't set, all the attachments will be purged
88
112
  # (i.e. destroyed) whenever the record is destroyed.
89
- 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
+
90
131
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
132
+ # frozen_string_literal: true
91
133
  def #{name}
92
- @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self)
134
+ @active_storage_attached ||= {}
135
+ @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
93
136
  end
94
137
 
95
138
  def #{name}=(attachables)
@@ -109,7 +152,7 @@ module ActiveStorage
109
152
  end
110
153
  CODE
111
154
 
112
- 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
113
156
  def purge
114
157
  each(&:purge)
115
158
  reset
@@ -120,7 +163,7 @@ module ActiveStorage
120
163
  reset
121
164
  end
122
165
  end
123
- 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
124
167
 
125
168
  scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
126
169
 
@@ -128,12 +171,24 @@ module ActiveStorage
128
171
 
129
172
  after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
130
173
 
131
- ActiveRecord::Reflection.add_attachment_reflection(
132
- self,
174
+ reflection = ActiveRecord::Reflection.create(
175
+ :has_many_attached,
133
176
  name,
134
- ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
177
+ nil,
178
+ { dependent: dependent, service_name: service },
179
+ self
135
180
  )
181
+ ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
136
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
137
192
  end
138
193
 
139
194
  def attachment_changes #:nodoc:
@@ -144,6 +199,12 @@ module ActiveStorage
144
199
  super || attachment_changes.any?
145
200
  end
146
201
 
202
+ def initialize_dup(*) #:nodoc:
203
+ super
204
+ @active_storage_attached = nil
205
+ @attachment_changes = nil
206
+ end
207
+
147
208
  def reload(*) #:nodoc:
148
209
  super.tap { @attachment_changes = nil }
149
210
  end