activestorage 6.0.6.1 → 6.1.7.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +215 -167
- data/MIT-LICENSE +1 -1
- data/README.md +35 -3
- data/app/controllers/active_storage/base_controller.rb +11 -0
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +2 -2
- data/app/controllers/active_storage/direct_uploads_controller.rb +1 -1
- data/app/controllers/active_storage/disk_controller.rb +8 -20
- data/app/controllers/active_storage/representations/base_controller.rb +14 -0
- data/app/controllers/active_storage/representations/proxy_controller.rb +13 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +2 -4
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +2 -2
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/models/active_storage/attachment.rb +19 -11
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +34 -4
- data/app/models/active_storage/blob.rb +122 -57
- data/app/models/active_storage/preview.rb +31 -10
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +31 -44
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +26 -21
- data/config/routes.rb +58 -8
- data/db/migrate/20170806125915_create_active_storage_tables.rb +30 -9
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +21 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +26 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +3 -0
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +14 -3
- data/lib/active_storage/analyzer.rb +6 -0
- data/lib/active_storage/attached/changes/create_many.rb +1 -0
- data/lib/active_storage/attached/changes/create_one.rb +17 -4
- data/lib/active_storage/attached/many.rb +4 -3
- data/lib/active_storage/attached/model.rb +67 -14
- data/lib/active_storage/attached/one.rb +4 -3
- data/lib/active_storage/engine.rb +41 -43
- data/lib/active_storage/errors.rb +3 -0
- data/lib/active_storage/gem_version.rb +3 -3
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +2 -2
- data/lib/active_storage/previewer/video_previewer.rb +5 -3
- data/lib/active_storage/previewer.rb +13 -3
- data/lib/active_storage/service/azure_storage_service.rb +40 -35
- data/lib/active_storage/service/configurator.rb +3 -1
- data/lib/active_storage/service/disk_service.rb +36 -31
- data/lib/active_storage/service/gcs_service.rb +18 -16
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +51 -23
- data/lib/active_storage/service.rb +35 -7
- data/lib/active_storage/transformers/image_processing_transformer.rb +21 -308
- data/lib/active_storage/transformers/transformer.rb +0 -3
- data/lib/active_storage.rb +301 -7
- data/lib/tasks/activestorage.rake +5 -1
- metadata +53 -16
- data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +0 -9
- data/lib/active_storage/downloading.rb +0 -47
- 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(
|
23
|
-
resolve("ActiveStorage::
|
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(
|
31
|
-
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(
|
32
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
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.
|
9
|
-
t.
|
10
|
-
t.
|
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)
|
@@ -44,7 +44,8 @@ module ActiveStorage
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def duration
|
47
|
-
|
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,
|
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
|
@@ -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(
|
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.
|
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
|
39
|
+
# Returns true if any attachments have been made.
|
39
40
|
#
|
40
|
-
# class Gallery <
|
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 <
|
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
|
-
|
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.
|
60
|
-
|
79
|
+
reflection = ActiveRecord::Reflection.create(
|
80
|
+
:has_one_attached,
|
61
81
|
name,
|
62
|
-
|
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 <
|
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
|
-
|
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.
|
134
|
-
|
174
|
+
reflection = ActiveRecord::Reflection.create(
|
175
|
+
:has_many_attached,
|
135
176
|
name,
|
136
|
-
|
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.
|
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 <
|
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::
|
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
|
-
|
120
|
-
|
121
|
-
config_file =
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|