activestorage 5.2.3 → 6.0.0
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 +140 -59
- data/MIT-LICENSE +1 -1
- data/README.md +9 -6
- data/app/assets/javascripts/activestorage.js +4 -1
- data/app/controllers/active_storage/base_controller.rb +3 -5
- data/app/controllers/active_storage/blobs_controller.rb +1 -1
- data/app/controllers/active_storage/disk_controller.rb +5 -2
- data/app/controllers/active_storage/representations_controller.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/javascript/activestorage/blob_record.js +6 -1
- data/app/jobs/active_storage/analyze_job.rb +4 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +20 -9
- data/app/models/active_storage/blob.rb +66 -24
- data/app/models/active_storage/blob/representable.rb +5 -5
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +3 -3
- data/app/models/active_storage/variant.rb +51 -52
- data/app/models/active_storage/variation.rb +24 -33
- data/config/routes.rb +13 -12
- data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +9 -0
- data/lib/active_storage.rb +26 -6
- data/lib/active_storage/analyzer.rb +9 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +11 -4
- data/lib/active_storage/analyzer/video_analyzer.rb +3 -5
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/changes/create_many.rb +46 -0
- data/lib/active_storage/attached/changes/create_one.rb +69 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +27 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/many.rb +16 -10
- data/lib/active_storage/attached/model.rb +147 -0
- data/lib/active_storage/attached/one.rb +16 -19
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/downloading.rb +8 -0
- data/lib/active_storage/engine.rb +43 -6
- data/lib/active_storage/errors.rb +22 -3
- data/lib/active_storage/gem_version.rb +3 -3
- data/lib/active_storage/previewer.rb +21 -11
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +2 -2
- data/lib/active_storage/previewer/video_previewer.rb +2 -3
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service.rb +9 -6
- data/lib/active_storage/service/azure_storage_service.rb +30 -14
- data/lib/active_storage/service/configurator.rb +3 -1
- data/lib/active_storage/service/disk_service.rb +24 -12
- data/lib/active_storage/service/gcs_service.rb +49 -47
- data/lib/active_storage/service/s3_service.rb +10 -6
- data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
- data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
- data/lib/active_storage/transformers/transformer.rb +42 -0
- data/lib/tasks/activestorage.rake +7 -0
- metadata +38 -12
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
@@ -6,17 +6,9 @@
|
|
6
6
|
# In case you do need to use this directly, it's instantiated using a hash of transformations where
|
7
7
|
# the key is the command and the value is the arguments. Example:
|
8
8
|
#
|
9
|
-
# ActiveStorage::Variation.new(
|
9
|
+
# ActiveStorage::Variation.new(resize_to_limit: [100, 100], monochrome: true, trim: true, rotate: "-90")
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# ActiveStorage::Variation.new(combine_options: {
|
14
|
-
# resize: "100x100^",
|
15
|
-
# gravity: "center",
|
16
|
-
# crop: "100x100+0+0",
|
17
|
-
# })
|
18
|
-
#
|
19
|
-
# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
|
11
|
+
# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
|
20
12
|
class ActiveStorage::Variation
|
21
13
|
attr_reader :transformations
|
22
14
|
|
@@ -48,24 +40,16 @@ class ActiveStorage::Variation
|
|
48
40
|
end
|
49
41
|
|
50
42
|
def initialize(transformations)
|
51
|
-
@transformations = transformations
|
43
|
+
@transformations = transformations.deep_symbolize_keys
|
52
44
|
end
|
53
45
|
|
54
|
-
# Accepts
|
55
|
-
#
|
56
|
-
|
46
|
+
# Accepts a File object, performs the +transformations+ against it, and
|
47
|
+
# saves the transformed image into a temporary file. If +format+ is specified
|
48
|
+
# it will be the format of the result image, otherwise the result image
|
49
|
+
# retains the source format.
|
50
|
+
def transform(file, format: nil, &block)
|
57
51
|
ActiveSupport::Notifications.instrument("transform.active_storage") do
|
58
|
-
|
59
|
-
image.mogrify do |command|
|
60
|
-
if name.to_s == "combine_options"
|
61
|
-
argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
|
62
|
-
pass_transform_argument(command, subtransformation_name, subtransformation_argument)
|
63
|
-
end
|
64
|
-
else
|
65
|
-
pass_transform_argument(command, name, argument_or_subtransformations)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
52
|
+
transformer.transform(file, format: format, &block)
|
69
53
|
end
|
70
54
|
end
|
71
55
|
|
@@ -75,15 +59,22 @@ class ActiveStorage::Variation
|
|
75
59
|
end
|
76
60
|
|
77
61
|
private
|
78
|
-
def
|
79
|
-
if
|
80
|
-
|
62
|
+
def transformer
|
63
|
+
if ActiveStorage.variant_processor
|
64
|
+
begin
|
65
|
+
require "image_processing"
|
66
|
+
rescue LoadError
|
67
|
+
ActiveSupport::Deprecation.warn <<~WARNING.squish
|
68
|
+
Generating image variants will require the image_processing gem in Rails 6.1.
|
69
|
+
Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
|
70
|
+
WARNING
|
71
|
+
|
72
|
+
ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
|
73
|
+
else
|
74
|
+
ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations)
|
75
|
+
end
|
81
76
|
else
|
82
|
-
|
77
|
+
ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
|
83
78
|
end
|
84
79
|
end
|
85
|
-
|
86
|
-
def eligible_argument?(argument)
|
87
|
-
argument.present? && argument != true
|
88
|
-
end
|
89
80
|
end
|
data/config/routes.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Rails.application.routes.draw do
|
4
|
-
|
5
|
-
|
6
|
-
direct :rails_blob do |blob, options|
|
7
|
-
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
|
8
|
-
end
|
9
|
-
|
10
|
-
resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
|
11
|
-
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
|
4
|
+
scope ActiveStorage.routes_prefix do
|
5
|
+
get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
|
12
6
|
|
7
|
+
get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
|
13
8
|
|
14
|
-
|
9
|
+
get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
|
10
|
+
put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
|
11
|
+
post "/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
|
12
|
+
end
|
15
13
|
|
16
14
|
direct :rails_representation do |representation, options|
|
17
15
|
signed_blob_id = representation.blob.signed_id
|
@@ -25,7 +23,10 @@ Rails.application.routes.draw do
|
|
25
23
|
resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
|
26
24
|
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
direct :rails_blob do |blob, options|
|
27
|
+
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
|
28
|
+
end
|
29
|
+
|
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) }
|
31
32
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
|
2
|
+
def up
|
3
|
+
return if foreign_key_exists?(:active_storage_attachments, column: :blob_id)
|
4
|
+
|
5
|
+
if table_exists?(:active_storage_blobs)
|
6
|
+
add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/lib/active_storage.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#--
|
4
|
-
# Copyright (c) 2017-
|
4
|
+
# Copyright (c) 2017-2019 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
|
@@ -26,6 +26,7 @@
|
|
26
26
|
require "active_record"
|
27
27
|
require "active_support"
|
28
28
|
require "active_support/rails"
|
29
|
+
require "active_support/core_ext/numeric/time"
|
29
30
|
|
30
31
|
require "active_storage/version"
|
31
32
|
require "active_storage/errors"
|
@@ -42,12 +43,31 @@ module ActiveStorage
|
|
42
43
|
|
43
44
|
mattr_accessor :logger
|
44
45
|
mattr_accessor :verifier
|
45
|
-
mattr_accessor :
|
46
|
+
mattr_accessor :variant_processor, default: :mini_magick
|
47
|
+
|
48
|
+
mattr_accessor :queues, default: {}
|
49
|
+
|
46
50
|
mattr_accessor :previewers, default: []
|
47
|
-
mattr_accessor :analyzers,
|
51
|
+
mattr_accessor :analyzers, default: []
|
52
|
+
|
48
53
|
mattr_accessor :paths, default: {}
|
49
|
-
|
54
|
+
|
55
|
+
mattr_accessor :variable_content_types, default: []
|
56
|
+
mattr_accessor :binary_content_type, default: "application/octet-stream"
|
50
57
|
mattr_accessor :content_types_to_serve_as_binary, default: []
|
51
|
-
mattr_accessor :content_types_allowed_inline,
|
52
|
-
|
58
|
+
mattr_accessor :content_types_allowed_inline, default: []
|
59
|
+
|
60
|
+
mattr_accessor :service_urls_expire_in, default: 5.minutes
|
61
|
+
|
62
|
+
mattr_accessor :routes_prefix, default: "/rails/active_storage"
|
63
|
+
|
64
|
+
mattr_accessor :replace_on_assign_to_many, default: false
|
65
|
+
|
66
|
+
module Transformers
|
67
|
+
extend ActiveSupport::Autoload
|
68
|
+
|
69
|
+
autoload :Transformer
|
70
|
+
autoload :ImageProcessingTransformer
|
71
|
+
autoload :MiniMagickTransformer
|
72
|
+
end
|
53
73
|
end
|
@@ -1,13 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_storage/downloading"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# This is an abstract base class for analyzers, which extract metadata from blobs. See
|
7
5
|
# ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
|
8
6
|
class Analyzer
|
9
|
-
include Downloading
|
10
|
-
|
11
7
|
attr_reader :blob
|
12
8
|
|
13
9
|
# Implement this method in a concrete subclass. Have it return true when given a blob from which
|
@@ -26,8 +22,17 @@ module ActiveStorage
|
|
26
22
|
end
|
27
23
|
|
28
24
|
private
|
25
|
+
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
26
|
+
def download_blob_to_tempfile(&block) #:doc:
|
27
|
+
blob.open tmpdir: tmpdir, &block
|
28
|
+
end
|
29
|
+
|
29
30
|
def logger #:doc:
|
30
31
|
ActiveStorage.logger
|
31
32
|
end
|
33
|
+
|
34
|
+
def tmpdir #:doc:
|
35
|
+
Dir.tmpdir
|
36
|
+
end
|
32
37
|
end
|
33
38
|
end
|
@@ -25,17 +25,24 @@ module ActiveStorage
|
|
25
25
|
{ width: image.width, height: image.height }
|
26
26
|
end
|
27
27
|
end
|
28
|
-
rescue LoadError
|
29
|
-
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
30
|
-
{}
|
31
28
|
end
|
32
29
|
|
33
30
|
private
|
34
31
|
def read_image
|
35
32
|
download_blob_to_tempfile do |file|
|
36
33
|
require "mini_magick"
|
37
|
-
|
34
|
+
image = MiniMagick::Image.new(file.path)
|
35
|
+
|
36
|
+
if image.valid?
|
37
|
+
yield image
|
38
|
+
else
|
39
|
+
logger.info "Skipping image analysis because ImageMagick doesn't support the file"
|
40
|
+
{}
|
41
|
+
end
|
38
42
|
end
|
43
|
+
rescue LoadError
|
44
|
+
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
45
|
+
{}
|
39
46
|
end
|
40
47
|
|
41
48
|
def rotated_image?(image)
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/hash/compact"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# Extracts the following from a video blob:
|
7
5
|
#
|
@@ -13,12 +11,12 @@ module ActiveStorage
|
|
13
11
|
#
|
14
12
|
# Example:
|
15
13
|
#
|
16
|
-
# ActiveStorage::VideoAnalyzer.new(blob).metadata
|
14
|
+
# ActiveStorage::Analyzer::VideoAnalyzer.new(blob).metadata
|
17
15
|
# # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3] }
|
18
16
|
#
|
19
17
|
# When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
|
20
18
|
#
|
21
|
-
# This analyzer requires the {
|
19
|
+
# This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
|
22
20
|
class Analyzer::VideoAnalyzer < Analyzer
|
23
21
|
def self.accept?(blob)
|
24
22
|
blob.video?
|
@@ -109,7 +107,7 @@ module ActiveStorage
|
|
109
107
|
JSON.parse(output.read)
|
110
108
|
end
|
111
109
|
rescue Errno::ENOENT
|
112
|
-
logger.info "Skipping video analysis because
|
110
|
+
logger.info "Skipping video analysis because FFmpeg isn't installed"
|
113
111
|
{}
|
114
112
|
end
|
115
113
|
|
@@ -1,40 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_dispatch"
|
4
|
-
require "action_dispatch/http/upload"
|
5
3
|
require "active_support/core_ext/module/delegation"
|
6
4
|
|
7
5
|
module ActiveStorage
|
8
6
|
# Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
|
9
7
|
# classes that both provide proxy access to the blob association for a record.
|
10
8
|
class Attached
|
11
|
-
attr_reader :name, :record
|
9
|
+
attr_reader :name, :record
|
12
10
|
|
13
|
-
def initialize(name, record
|
14
|
-
@name, @record
|
11
|
+
def initialize(name, record)
|
12
|
+
@name, @record = name, record
|
15
13
|
end
|
16
14
|
|
17
15
|
private
|
18
|
-
def
|
19
|
-
|
20
|
-
when ActiveStorage::Blob
|
21
|
-
attachable
|
22
|
-
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
23
|
-
ActiveStorage::Blob.create_after_upload! \
|
24
|
-
io: attachable.open,
|
25
|
-
filename: attachable.original_filename,
|
26
|
-
content_type: attachable.content_type
|
27
|
-
when Hash
|
28
|
-
ActiveStorage::Blob.create_after_upload!(attachable)
|
29
|
-
when String
|
30
|
-
ActiveStorage::Blob.find_signed(attachable)
|
31
|
-
else
|
32
|
-
nil
|
33
|
-
end
|
16
|
+
def change
|
17
|
+
record.attachment_changes[name]
|
34
18
|
end
|
35
19
|
end
|
36
20
|
end
|
37
21
|
|
22
|
+
require "active_storage/attached/model"
|
38
23
|
require "active_storage/attached/one"
|
39
24
|
require "active_storage/attached/many"
|
40
|
-
require "active_storage/attached/
|
25
|
+
require "active_storage/attached/changes"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
module Attached::Changes #:nodoc:
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
eager_autoload do
|
8
|
+
autoload :CreateOne
|
9
|
+
autoload :CreateMany
|
10
|
+
autoload :CreateOneOfMany
|
11
|
+
|
12
|
+
autoload :DeleteOne
|
13
|
+
autoload :DeleteMany
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Attached::Changes::CreateMany #:nodoc:
|
5
|
+
attr_reader :name, :record, :attachables
|
6
|
+
|
7
|
+
def initialize(name, record, attachables)
|
8
|
+
@name, @record, @attachables = name, record, Array(attachables)
|
9
|
+
end
|
10
|
+
|
11
|
+
def attachments
|
12
|
+
@attachments ||= subchanges.collect(&:attachment)
|
13
|
+
end
|
14
|
+
|
15
|
+
def blobs
|
16
|
+
@blobs ||= subchanges.collect(&:blob)
|
17
|
+
end
|
18
|
+
|
19
|
+
def upload
|
20
|
+
subchanges.each(&:upload)
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
assign_associated_attachments
|
25
|
+
reset_associated_blobs
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def subchanges
|
30
|
+
@subchanges ||= attachables.collect { |attachable| build_subchange_from(attachable) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_subchange_from(attachable)
|
34
|
+
ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def assign_associated_attachments
|
39
|
+
record.public_send("#{name}_attachments=", attachments)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset_associated_blobs
|
43
|
+
record.public_send("#{name}_blobs").reset
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch"
|
4
|
+
require "action_dispatch/http/upload"
|
5
|
+
|
6
|
+
module ActiveStorage
|
7
|
+
class Attached::Changes::CreateOne #:nodoc:
|
8
|
+
attr_reader :name, :record, :attachable
|
9
|
+
|
10
|
+
def initialize(name, record, attachable)
|
11
|
+
@name, @record, @attachable = name, record, attachable
|
12
|
+
end
|
13
|
+
|
14
|
+
def attachment
|
15
|
+
@attachment ||= find_or_build_attachment
|
16
|
+
end
|
17
|
+
|
18
|
+
def blob
|
19
|
+
@blob ||= find_or_build_blob
|
20
|
+
end
|
21
|
+
|
22
|
+
def upload
|
23
|
+
case attachable
|
24
|
+
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
25
|
+
blob.upload_without_unfurling(attachable.open)
|
26
|
+
when Hash
|
27
|
+
blob.upload_without_unfurling(attachable.fetch(:io))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def save
|
32
|
+
record.public_send("#{name}_attachment=", attachment)
|
33
|
+
record.public_send("#{name}_blob=", blob)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def find_or_build_attachment
|
38
|
+
find_attachment || build_attachment
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_attachment
|
42
|
+
if record.public_send("#{name}_blob") == blob
|
43
|
+
record.public_send("#{name}_attachment")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_attachment
|
48
|
+
ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_or_build_blob
|
52
|
+
case attachable
|
53
|
+
when ActiveStorage::Blob
|
54
|
+
attachable
|
55
|
+
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
56
|
+
ActiveStorage::Blob.build_after_unfurling \
|
57
|
+
io: attachable.open,
|
58
|
+
filename: attachable.original_filename,
|
59
|
+
content_type: attachable.content_type
|
60
|
+
when Hash
|
61
|
+
ActiveStorage::Blob.build_after_unfurling(attachable)
|
62
|
+
when String
|
63
|
+
ActiveStorage::Blob.find_signed(attachable)
|
64
|
+
else
|
65
|
+
raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|