activestorage 0.1 → 5.2.0.beta1
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 +5 -0
- data/README.md +94 -25
- data/app/assets/javascripts/activestorage.js +1 -0
- data/app/controllers/active_storage/blobs_controller.rb +16 -0
- data/app/controllers/active_storage/direct_uploads_controller.rb +23 -0
- data/app/controllers/active_storage/disk_controller.rb +51 -0
- data/app/controllers/active_storage/previews_controller.rb +12 -0
- data/app/controllers/active_storage/variants_controller.rb +16 -0
- data/app/javascript/activestorage/blob_record.js +54 -0
- data/app/javascript/activestorage/blob_upload.js +35 -0
- data/app/javascript/activestorage/direct_upload.js +42 -0
- data/app/javascript/activestorage/direct_upload_controller.js +67 -0
- data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
- data/app/javascript/activestorage/file_checksum.js +53 -0
- data/app/javascript/activestorage/helpers.js +42 -0
- data/app/javascript/activestorage/index.js +11 -0
- data/app/javascript/activestorage/ujs.js +75 -0
- data/app/jobs/active_storage/analyze_job.rb +8 -0
- data/app/jobs/active_storage/base_job.rb +5 -0
- data/app/jobs/active_storage/purge_job.rb +11 -0
- data/app/models/active_storage/attachment.rb +35 -0
- data/app/models/active_storage/blob.rb +313 -0
- data/app/models/active_storage/filename.rb +73 -0
- data/app/models/active_storage/filename/parameters.rb +36 -0
- data/app/models/active_storage/preview.rb +90 -0
- data/app/models/active_storage/variant.rb +86 -0
- data/app/models/active_storage/variation.rb +67 -0
- data/config/routes.rb +43 -0
- data/lib/active_storage.rb +37 -2
- data/lib/active_storage/analyzer.rb +33 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +36 -0
- data/lib/active_storage/analyzer/null_analyzer.rb +13 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +79 -0
- data/lib/active_storage/attached.rb +28 -22
- data/lib/active_storage/attached/macros.rb +89 -16
- data/lib/active_storage/attached/many.rb +53 -21
- data/lib/active_storage/attached/one.rb +74 -20
- data/lib/active_storage/downloading.rb +26 -0
- data/lib/active_storage/engine.rb +72 -0
- data/lib/active_storage/gem_version.rb +17 -0
- data/lib/active_storage/log_subscriber.rb +52 -0
- data/lib/active_storage/previewer.rb +58 -0
- data/lib/active_storage/previewer/pdf_previewer.rb +17 -0
- data/lib/active_storage/previewer/video_previewer.rb +23 -0
- data/lib/active_storage/service.rb +112 -24
- data/lib/active_storage/service/azure_storage_service.rb +124 -0
- data/lib/active_storage/service/configurator.rb +32 -0
- data/lib/active_storage/service/disk_service.rb +103 -44
- data/lib/active_storage/service/gcs_service.rb +87 -29
- data/lib/active_storage/service/mirror_service.rb +38 -22
- data/lib/active_storage/service/s3_service.rb +83 -38
- data/lib/active_storage/version.rb +10 -0
- data/lib/tasks/activestorage.rake +4 -15
- metadata +64 -108
- data/.gitignore +0 -1
- data/Gemfile +0 -11
- data/Gemfile.lock +0 -235
- data/Rakefile +0 -11
- data/activestorage.gemspec +0 -21
- data/lib/active_storage/attachment.rb +0 -30
- data/lib/active_storage/blob.rb +0 -80
- data/lib/active_storage/disk_controller.rb +0 -28
- data/lib/active_storage/download.rb +0 -90
- data/lib/active_storage/filename.rb +0 -31
- data/lib/active_storage/migration.rb +0 -28
- data/lib/active_storage/purge_job.rb +0 -10
- data/lib/active_storage/railtie.rb +0 -56
- data/lib/active_storage/storage_services.yml +0 -27
- data/lib/active_storage/verified_key_with_expiration.rb +0 -24
- data/test/attachments_test.rb +0 -95
- data/test/blob_test.rb +0 -28
- data/test/database/create_users_migration.rb +0 -7
- data/test/database/setup.rb +0 -6
- data/test/disk_controller_test.rb +0 -34
- data/test/filename_test.rb +0 -36
- data/test/service/.gitignore +0 -1
- data/test/service/configurations-example.yml +0 -11
- data/test/service/disk_service_test.rb +0 -8
- data/test/service/gcs_service_test.rb +0 -20
- data/test/service/mirror_service_test.rb +0 -50
- data/test/service/s3_service_test.rb +0 -11
- data/test/service/shared_service_tests.rb +0 -68
- data/test/test_helper.rb +0 -28
- data/test/verified_key_with_expiration_test.rb +0 -19
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_storage/downloading"
|
4
|
+
|
5
|
+
module ActiveStorage
|
6
|
+
# This is an abstract base class for analyzers, which extract metadata from blobs. See
|
7
|
+
# ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
|
8
|
+
class Analyzer
|
9
|
+
include Downloading
|
10
|
+
|
11
|
+
attr_reader :blob
|
12
|
+
|
13
|
+
# Implement this method in a concrete subclass. Have it return true when given a blob from which
|
14
|
+
# the analyzer can extract metadata.
|
15
|
+
def self.accept?(blob)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(blob)
|
20
|
+
@blob = blob
|
21
|
+
end
|
22
|
+
|
23
|
+
# Override this method in a concrete subclass. Have it return a Hash of metadata.
|
24
|
+
def metadata
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def logger
|
30
|
+
ActiveStorage.logger
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# Extracts width and height in pixels from an image blob.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
|
9
|
+
# # => { width: 4104, height: 2736 }
|
10
|
+
#
|
11
|
+
# This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
|
12
|
+
# the {ImageMagick}[http://www.imagemagick.org] system library. These libraries are not provided by Rails; you must
|
13
|
+
# install them yourself to use this analyzer.
|
14
|
+
class Analyzer::ImageAnalyzer < Analyzer
|
15
|
+
def self.accept?(blob)
|
16
|
+
blob.image?
|
17
|
+
end
|
18
|
+
|
19
|
+
def metadata
|
20
|
+
read_image do |image|
|
21
|
+
{ width: image.width, height: image.height }
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def read_image
|
30
|
+
download_blob_to_tempfile do |file|
|
31
|
+
require "mini_magick"
|
32
|
+
yield MiniMagick::Image.new(file.path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/compact"
|
4
|
+
|
5
|
+
module ActiveStorage
|
6
|
+
# Extracts the following from a video blob:
|
7
|
+
#
|
8
|
+
# * Width (pixels)
|
9
|
+
# * Height (pixels)
|
10
|
+
# * Duration (seconds)
|
11
|
+
# * Angle (degrees)
|
12
|
+
# * Aspect ratio
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# ActiveStorage::VideoAnalyzer.new(blob).metadata
|
17
|
+
# # => { width: 640, height: 480, duration: 5.0, angle: 0, aspect_ratio: [4, 3] }
|
18
|
+
#
|
19
|
+
# This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails. You must
|
20
|
+
# install ffmpeg yourself to use this analyzer.
|
21
|
+
class Analyzer::VideoAnalyzer < Analyzer
|
22
|
+
def self.accept?(blob)
|
23
|
+
blob.video?
|
24
|
+
end
|
25
|
+
|
26
|
+
def metadata
|
27
|
+
{ width: width, height: height, duration: duration, angle: angle, aspect_ratio: aspect_ratio }.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def width
|
32
|
+
Integer(video_stream["width"]) if video_stream["width"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def height
|
36
|
+
Integer(video_stream["height"]) if video_stream["height"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def duration
|
40
|
+
Float(video_stream["duration"]) if video_stream["duration"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def angle
|
44
|
+
Integer(tags["rotate"]) if tags["rotate"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def aspect_ratio
|
48
|
+
if descriptor = video_stream["display_aspect_ratio"]
|
49
|
+
descriptor.split(":", 2).collect(&:to_i)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def tags
|
55
|
+
@tags ||= video_stream["tags"] || {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def video_stream
|
59
|
+
@video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def streams
|
63
|
+
probe["streams"] || []
|
64
|
+
end
|
65
|
+
|
66
|
+
def probe
|
67
|
+
download_blob_to_tempfile { |file| probe_from(file) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def probe_from(file)
|
71
|
+
IO.popen([ "ffprobe", "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
|
72
|
+
JSON.parse(output.read)
|
73
|
+
end
|
74
|
+
rescue Errno::ENOENT
|
75
|
+
logger.info "Skipping video analysis because ffmpeg isn't installed"
|
76
|
+
{}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -1,32 +1,38 @@
|
|
1
|
-
|
2
|
-
require "active_storage/attachment"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
require "action_dispatch"
|
4
4
|
require "action_dispatch/http/upload"
|
5
5
|
require "active_support/core_ext/module/delegation"
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
module ActiveStorage
|
8
|
+
# Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
|
9
|
+
# classes that both provide proxy access to the blob association for a record.
|
10
|
+
class Attached
|
11
|
+
attr_reader :name, :record, :dependent
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
def initialize(name, record, dependent:)
|
14
|
+
@name, @record, @dependent = name, record, dependent
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
private
|
18
|
+
def create_blob_from(attachable)
|
19
|
+
case attachable
|
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
|
28
34
|
end
|
29
|
-
|
35
|
+
end
|
30
36
|
end
|
31
37
|
|
32
38
|
require "active_storage/attached/one"
|
@@ -1,23 +1,96 @@
|
|
1
|
-
|
2
|
-
def has_one_attached(name, dependent: :purge_later)
|
3
|
-
define_method(name) do
|
4
|
-
instance_variable_get("@active_storage_attached_#{name}") ||
|
5
|
-
instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::One.new(name, self))
|
6
|
-
end
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module ActiveStorage
|
4
|
+
# Provides the class-level DSL for declaring that an Active Record model has attached blobs.
|
5
|
+
module Attached::Macros
|
6
|
+
# Specifies the relation between a single attachment and the model.
|
7
|
+
#
|
8
|
+
# class User < ActiveRecord::Base
|
9
|
+
# has_one_attached :avatar
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# There is no column defined on the model side, Active Storage takes
|
13
|
+
# care of the mapping between your records and the attachment.
|
14
|
+
#
|
15
|
+
# To avoid N+1 queries, you can include the attached blobs in your query like so:
|
16
|
+
#
|
17
|
+
# User.with_attached_avatar
|
18
|
+
#
|
19
|
+
# Under the covers, this relationship is implemented as a +has_one+ association to a
|
20
|
+
# ActiveStorage::Attachment record and a +has_one-through+ association to a
|
21
|
+
# ActiveStorage::Blob record. These associations are available as +avatar_attachment+
|
22
|
+
# and +avatar_blob+. But you shouldn't need to work with these associations directly in
|
23
|
+
# most circumstances.
|
24
|
+
#
|
25
|
+
# The system has been designed to having you go through the ActiveStorage::Attached::One
|
26
|
+
# proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
|
27
|
+
#
|
28
|
+
# If the +:dependent+ option isn't set, the attachment will be purged
|
29
|
+
# (i.e. destroyed) whenever the record is destroyed.
|
30
|
+
def has_one_attached(name, dependent: :purge_later)
|
31
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
32
|
+
def #{name}
|
33
|
+
@active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
|
34
|
+
end
|
35
|
+
|
36
|
+
def #{name}=(attachable)
|
37
|
+
#{name}.attach(attachable)
|
38
|
+
end
|
39
|
+
CODE
|
40
|
+
|
41
|
+
has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record
|
42
|
+
has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
|
12
43
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
44
|
+
scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
|
45
|
+
|
46
|
+
if dependent == :purge_later
|
47
|
+
before_destroy { public_send(name).purge_later }
|
48
|
+
end
|
17
49
|
end
|
18
50
|
|
19
|
-
|
20
|
-
|
51
|
+
# Specifies the relation between multiple attachments and the model.
|
52
|
+
#
|
53
|
+
# class Gallery < ActiveRecord::Base
|
54
|
+
# has_many_attached :photos
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# There are no columns defined on the model side, Active Storage takes
|
58
|
+
# care of the mapping between your records and the attachments.
|
59
|
+
#
|
60
|
+
# To avoid N+1 queries, you can include the attached blobs in your query like so:
|
61
|
+
#
|
62
|
+
# Gallery.where(user: Current.user).with_attached_photos
|
63
|
+
#
|
64
|
+
# Under the covers, this relationship is implemented as a +has_many+ association to a
|
65
|
+
# ActiveStorage::Attachment record and a +has_many-through+ association to a
|
66
|
+
# ActiveStorage::Blob record. These associations are available as +photos_attachments+
|
67
|
+
# and +photos_blobs+. But you shouldn't need to work with these associations directly in
|
68
|
+
# most circumstances.
|
69
|
+
#
|
70
|
+
# The system has been designed to having you go through the ActiveStorage::Attached::Many
|
71
|
+
# proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
|
72
|
+
#
|
73
|
+
# If the +:dependent+ option isn't set, all the attachments will be purged
|
74
|
+
# (i.e. destroyed) whenever the record is destroyed.
|
75
|
+
def has_many_attached(name, dependent: :purge_later)
|
76
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
77
|
+
def #{name}
|
78
|
+
@active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
|
79
|
+
end
|
80
|
+
|
81
|
+
def #{name}=(attachables)
|
82
|
+
#{name}.attach(attachables)
|
83
|
+
end
|
84
|
+
CODE
|
85
|
+
|
86
|
+
has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment"
|
87
|
+
has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
|
88
|
+
|
89
|
+
scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
|
90
|
+
|
91
|
+
if dependent == :purge_later
|
92
|
+
before_destroy { public_send(name).purge_later }
|
93
|
+
end
|
21
94
|
end
|
22
95
|
end
|
23
96
|
end
|
@@ -1,31 +1,63 @@
|
|
1
|
-
|
2
|
-
delegate_missing_to :attachments
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module ActiveStorage
|
4
|
+
# Decorated proxy object representing of multiple attachments to a model.
|
5
|
+
class Attached::Many < Attached
|
6
|
+
delegate_missing_to :attachments
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
# Returns all the associated attachment records.
|
9
|
+
#
|
10
|
+
# All methods called on this proxy object that aren't listed here will automatically be delegated to +attachments+.
|
11
|
+
def attachments
|
12
|
+
record.public_send("#{name}_attachments")
|
11
13
|
end
|
12
|
-
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
# Associates one or several attachments with the current record, saving them to the database.
|
16
|
+
#
|
17
|
+
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
|
18
|
+
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
|
19
|
+
# document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
|
20
|
+
# document.images.attach([ first_blob, second_blob ])
|
21
|
+
def attach(*attachables)
|
22
|
+
attachables.flatten.collect do |attachable|
|
23
|
+
if record.new_record?
|
24
|
+
attachments.build(record: record, blob: create_blob_from(attachable))
|
25
|
+
else
|
26
|
+
attachments.create!(record: record, blob: create_blob_from(attachable))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
17
30
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
31
|
+
# Returns true if any attachments has been made.
|
32
|
+
#
|
33
|
+
# class Gallery < ActiveRecord::Base
|
34
|
+
# has_many_attached :photos
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Gallery.new.photos.attached? # => false
|
38
|
+
def attached?
|
39
|
+
attachments.any?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Deletes associated attachments without purging them, leaving their respective blobs in place.
|
43
|
+
def detach
|
44
|
+
attachments.destroy_all if attached?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Directly purges each associated attachment (i.e. destroys the blobs and
|
48
|
+
# attachments and deletes the files on the service).
|
49
|
+
def purge
|
50
|
+
if attached?
|
51
|
+
attachments.each(&:purge)
|
52
|
+
attachments.reload
|
53
|
+
end
|
22
54
|
end
|
23
|
-
end
|
24
55
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
56
|
+
# Purges each associated attachment through the queuing system.
|
57
|
+
def purge_later
|
58
|
+
if attached?
|
59
|
+
attachments.each(&:purge_later)
|
60
|
+
end
|
29
61
|
end
|
30
62
|
end
|
31
63
|
end
|
@@ -1,29 +1,83 @@
|
|
1
|
-
|
2
|
-
delegate_missing_to :attachment
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module ActiveStorage
|
4
|
+
# Representation of a single attachment to a model.
|
5
|
+
class Attached::One < Attached
|
6
|
+
delegate_missing_to :attachment
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
# Returns the associated attachment record.
|
9
|
+
#
|
10
|
+
# You don't have to call this method to access the attachment's methods as
|
11
|
+
# they are all available at the model level.
|
12
|
+
def attachment
|
13
|
+
record.public_send("#{name}_attachment")
|
14
|
+
end
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
# Associates a given attachment with the current record, saving it to the database.
|
17
|
+
#
|
18
|
+
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
|
19
|
+
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
|
20
|
+
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
|
21
|
+
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
|
22
|
+
def attach(attachable)
|
23
|
+
if attached? && dependent == :purge_later
|
24
|
+
replace attachable
|
25
|
+
else
|
26
|
+
write_attachment build_attachment_from(attachable)
|
27
|
+
end
|
28
|
+
end
|
15
29
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
30
|
+
# Returns +true+ if an attachment has been made.
|
31
|
+
#
|
32
|
+
# class User < ActiveRecord::Base
|
33
|
+
# has_one_attached :avatar
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# User.new.avatar.attached? # => false
|
37
|
+
def attached?
|
38
|
+
attachment.present?
|
20
39
|
end
|
21
|
-
end
|
22
40
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
41
|
+
# Deletes the attachment without purging it, leaving its blob in place.
|
42
|
+
def detach
|
43
|
+
if attached?
|
44
|
+
attachment.destroy
|
45
|
+
write_attachment nil
|
46
|
+
end
|
27
47
|
end
|
48
|
+
|
49
|
+
# Directly purges the attachment (i.e. destroys the blob and
|
50
|
+
# attachment and deletes the file on the service).
|
51
|
+
def purge
|
52
|
+
if attached?
|
53
|
+
attachment.purge
|
54
|
+
write_attachment nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Purges the attachment through the queuing system.
|
59
|
+
def purge_later
|
60
|
+
if attached?
|
61
|
+
attachment.purge_later
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def replace(attachable)
|
67
|
+
blob.tap do
|
68
|
+
transaction do
|
69
|
+
detach
|
70
|
+
write_attachment build_attachment_from(attachable)
|
71
|
+
end
|
72
|
+
end.purge_later
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_attachment_from(attachable)
|
76
|
+
ActiveStorage::Attachment.new(record: record, name: name, blob: create_blob_from(attachable))
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_attachment(attachment)
|
80
|
+
record.public_send("#{name}_attachment=", attachment)
|
81
|
+
end
|
28
82
|
end
|
29
83
|
end
|