activestorage 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.

Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +198 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +162 -0
  5. data/app/assets/javascripts/activestorage.js +942 -0
  6. data/app/controllers/active_storage/base_controller.rb +8 -0
  7. data/app/controllers/active_storage/blobs_controller.rb +14 -0
  8. data/app/controllers/active_storage/direct_uploads_controller.rb +23 -0
  9. data/app/controllers/active_storage/disk_controller.rb +66 -0
  10. data/app/controllers/active_storage/representations_controller.rb +14 -0
  11. data/app/controllers/concerns/active_storage/set_blob.rb +16 -0
  12. data/app/controllers/concerns/active_storage/set_current.rb +15 -0
  13. data/app/javascript/activestorage/blob_record.js +73 -0
  14. data/app/javascript/activestorage/blob_upload.js +35 -0
  15. data/app/javascript/activestorage/direct_upload.js +48 -0
  16. data/app/javascript/activestorage/direct_upload_controller.js +67 -0
  17. data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
  18. data/app/javascript/activestorage/file_checksum.js +53 -0
  19. data/app/javascript/activestorage/helpers.js +51 -0
  20. data/app/javascript/activestorage/index.js +11 -0
  21. data/app/javascript/activestorage/ujs.js +86 -0
  22. data/app/jobs/active_storage/analyze_job.rb +12 -0
  23. data/app/jobs/active_storage/base_job.rb +4 -0
  24. data/app/jobs/active_storage/purge_job.rb +13 -0
  25. data/app/models/active_storage/attachment.rb +50 -0
  26. data/app/models/active_storage/blob.rb +278 -0
  27. data/app/models/active_storage/blob/analyzable.rb +57 -0
  28. data/app/models/active_storage/blob/identifiable.rb +31 -0
  29. data/app/models/active_storage/blob/representable.rb +93 -0
  30. data/app/models/active_storage/current.rb +5 -0
  31. data/app/models/active_storage/filename.rb +77 -0
  32. data/app/models/active_storage/preview.rb +89 -0
  33. data/app/models/active_storage/variant.rb +131 -0
  34. data/app/models/active_storage/variation.rb +80 -0
  35. data/config/routes.rb +32 -0
  36. data/db/migrate/20170806125915_create_active_storage_tables.rb +26 -0
  37. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +9 -0
  38. data/lib/active_storage.rb +73 -0
  39. data/lib/active_storage/analyzer.rb +38 -0
  40. data/lib/active_storage/analyzer/image_analyzer.rb +52 -0
  41. data/lib/active_storage/analyzer/null_analyzer.rb +13 -0
  42. data/lib/active_storage/analyzer/video_analyzer.rb +118 -0
  43. data/lib/active_storage/attached.rb +25 -0
  44. data/lib/active_storage/attached/changes.rb +16 -0
  45. data/lib/active_storage/attached/changes/create_many.rb +46 -0
  46. data/lib/active_storage/attached/changes/create_one.rb +69 -0
  47. data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
  48. data/lib/active_storage/attached/changes/delete_many.rb +27 -0
  49. data/lib/active_storage/attached/changes/delete_one.rb +19 -0
  50. data/lib/active_storage/attached/many.rb +65 -0
  51. data/lib/active_storage/attached/model.rb +147 -0
  52. data/lib/active_storage/attached/one.rb +79 -0
  53. data/lib/active_storage/downloader.rb +43 -0
  54. data/lib/active_storage/downloading.rb +47 -0
  55. data/lib/active_storage/engine.rb +149 -0
  56. data/lib/active_storage/errors.rb +26 -0
  57. data/lib/active_storage/gem_version.rb +17 -0
  58. data/lib/active_storage/log_subscriber.rb +58 -0
  59. data/lib/active_storage/previewer.rb +84 -0
  60. data/lib/active_storage/previewer/mupdf_previewer.rb +36 -0
  61. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +35 -0
  62. data/lib/active_storage/previewer/video_previewer.rb +26 -0
  63. data/lib/active_storage/reflection.rb +64 -0
  64. data/lib/active_storage/service.rb +141 -0
  65. data/lib/active_storage/service/azure_storage_service.rb +165 -0
  66. data/lib/active_storage/service/configurator.rb +34 -0
  67. data/lib/active_storage/service/disk_service.rb +166 -0
  68. data/lib/active_storage/service/gcs_service.rb +141 -0
  69. data/lib/active_storage/service/mirror_service.rb +55 -0
  70. data/lib/active_storage/service/s3_service.rb +116 -0
  71. data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
  72. data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
  73. data/lib/active_storage/transformers/transformer.rb +42 -0
  74. data/lib/active_storage/version.rb +10 -0
  75. data/lib/tasks/activestorage.rake +22 -0
  76. metadata +174 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module ActiveStorage
6
+ # Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
7
+ # classes that both provide proxy access to the blob association for a record.
8
+ class Attached
9
+ attr_reader :name, :record
10
+
11
+ def initialize(name, record)
12
+ @name, @record = name, record
13
+ end
14
+
15
+ private
16
+ def change
17
+ record.attachment_changes[name]
18
+ end
19
+ end
20
+ end
21
+
22
+ require "active_storage/attached/model"
23
+ require "active_storage/attached/one"
24
+ require "active_storage/attached/many"
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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne #:nodoc:
5
+ private
6
+ def find_attachment
7
+ record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::DeleteMany #:nodoc:
5
+ attr_reader :name, :record
6
+
7
+ def initialize(name, record)
8
+ @name, @record = name, record
9
+ end
10
+
11
+ def attachables
12
+ []
13
+ end
14
+
15
+ def attachments
16
+ ActiveStorage::Attachment.none
17
+ end
18
+
19
+ def blobs
20
+ ActiveStorage::Blob.none
21
+ end
22
+
23
+ def save
24
+ record.public_send("#{name}_attachments=", [])
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ class Attached::Changes::DeleteOne #:nodoc:
5
+ attr_reader :name, :record
6
+
7
+ def initialize(name, record)
8
+ @name, @record = name, record
9
+ end
10
+
11
+ def attachment
12
+ nil
13
+ end
14
+
15
+ def save
16
+ record.public_send("#{name}_attachment=", nil)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
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
+
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
+ change.present? ? change.attachments : record.public_send("#{name}_attachments")
13
+ end
14
+
15
+ # Returns all attached blobs.
16
+ def blobs
17
+ change.present? ? change.blobs : record.public_send("#{name}_blobs")
18
+ end
19
+
20
+ # Attaches one or more +attachables+ to the record.
21
+ #
22
+ # If the record is persisted and unchanged, the attachments are saved to
23
+ # the database immediately. Otherwise, they'll be saved to the DB when the
24
+ # record is next saved.
25
+ #
26
+ # document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
27
+ # document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
28
+ # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
29
+ # document.images.attach([ first_blob, second_blob ])
30
+ def attach(*attachables)
31
+ if record.persisted? && !record.changed?
32
+ record.update(name => blobs + attachables.flatten)
33
+ else
34
+ record.public_send("#{name}=", (change&.attachables || blobs) + attachables.flatten)
35
+ end
36
+ end
37
+
38
+ # Returns true if any attachments has been made.
39
+ #
40
+ # class Gallery < ActiveRecord::Base
41
+ # has_many_attached :photos
42
+ # end
43
+ #
44
+ # Gallery.new.photos.attached? # => false
45
+ def attached?
46
+ attachments.any?
47
+ end
48
+
49
+ # Deletes associated attachments without purging them, leaving their respective blobs in place.
50
+ def detach
51
+ attachments.delete_all if attached?
52
+ end
53
+
54
+ ##
55
+ # :method: purge
56
+ #
57
+ # Directly purges each associated attachment (i.e. destroys the blobs and
58
+ # attachments and deletes the files on the service).
59
+
60
+ ##
61
+ # :method: purge_later
62
+ #
63
+ # Purges each associated attachment through the queuing system.
64
+ end
65
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ # Provides the class-level DSL for declaring an Active Record model's attachments.
5
+ module Attached::Model
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ # Specifies the relation between a single attachment and the model.
10
+ #
11
+ # class User < ActiveRecord::Base
12
+ # has_one_attached :avatar
13
+ # end
14
+ #
15
+ # There is no column defined on the model side, Active Storage takes
16
+ # care of the mapping between your records and the attachment.
17
+ #
18
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
19
+ #
20
+ # User.with_attached_avatar
21
+ #
22
+ # Under the covers, this relationship is implemented as a +has_one+ association to a
23
+ # ActiveStorage::Attachment record and a +has_one-through+ association to a
24
+ # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
25
+ # and +avatar_blob+. But you shouldn't need to work with these associations directly in
26
+ # most circumstances.
27
+ #
28
+ # The system has been designed to having you go through the ActiveStorage::Attached::One
29
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
30
+ #
31
+ # If the +:dependent+ option isn't set, the attachment will be purged
32
+ # (i.e. destroyed) whenever the record is destroyed.
33
+ def has_one_attached(name, dependent: :purge_later)
34
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
35
+ def #{name}
36
+ @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self)
37
+ end
38
+
39
+ def #{name}=(attachable)
40
+ attachment_changes["#{name}"] =
41
+ if attachable.nil?
42
+ ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
43
+ else
44
+ ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
45
+ end
46
+ end
47
+ CODE
48
+
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
51
+
52
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
53
+
54
+ after_save { attachment_changes[name.to_s]&.save }
55
+
56
+ after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
57
+
58
+ ActiveRecord::Reflection.add_attachment_reflection(
59
+ self,
60
+ name,
61
+ ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
62
+ )
63
+ end
64
+
65
+ # Specifies the relation between multiple attachments and the model.
66
+ #
67
+ # class Gallery < ActiveRecord::Base
68
+ # has_many_attached :photos
69
+ # end
70
+ #
71
+ # There are no columns defined on the model side, Active Storage takes
72
+ # care of the mapping between your records and the attachments.
73
+ #
74
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
75
+ #
76
+ # Gallery.where(user: Current.user).with_attached_photos
77
+ #
78
+ # Under the covers, this relationship is implemented as a +has_many+ association to a
79
+ # ActiveStorage::Attachment record and a +has_many-through+ association to a
80
+ # ActiveStorage::Blob record. These associations are available as +photos_attachments+
81
+ # and +photos_blobs+. But you shouldn't need to work with these associations directly in
82
+ # most circumstances.
83
+ #
84
+ # The system has been designed to having you go through the ActiveStorage::Attached::Many
85
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
86
+ #
87
+ # If the +:dependent+ option isn't set, all the attachments will be purged
88
+ # (i.e. destroyed) whenever the record is destroyed.
89
+ def has_many_attached(name, dependent: :purge_later)
90
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
91
+ def #{name}
92
+ @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self)
93
+ end
94
+
95
+ def #{name}=(attachables)
96
+ if ActiveStorage.replace_on_assign_to_many
97
+ attachment_changes["#{name}"] =
98
+ if attachables.nil? || Array(attachables).none?
99
+ ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
100
+ else
101
+ ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
102
+ end
103
+ else
104
+ if !attachables.nil? || Array(attachables).any?
105
+ attachment_changes["#{name}"] =
106
+ ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, #{name}.blobs + attachables)
107
+ end
108
+ end
109
+ end
110
+ CODE
111
+
112
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy do
113
+ def purge
114
+ each(&:purge)
115
+ reset
116
+ end
117
+
118
+ def purge_later
119
+ each(&:purge_later)
120
+ reset
121
+ end
122
+ end
123
+ has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
124
+
125
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
126
+
127
+ after_save { attachment_changes[name.to_s]&.save }
128
+
129
+ after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
130
+
131
+ ActiveRecord::Reflection.add_attachment_reflection(
132
+ self,
133
+ name,
134
+ ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
135
+ )
136
+ end
137
+ end
138
+
139
+ def attachment_changes #:nodoc:
140
+ @attachment_changes ||= {}
141
+ end
142
+
143
+ def reload(*) #:nodoc:
144
+ super.tap { @attachment_changes = nil }
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ # Representation of a single attachment to a model.
5
+ class Attached::One < Attached
6
+ delegate_missing_to :attachment
7
+
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
+ change.present? ? change.attachment : record.public_send("#{name}_attachment")
14
+ end
15
+
16
+ def blank?
17
+ !attached?
18
+ end
19
+
20
+ # Attaches an +attachable+ to the record.
21
+ #
22
+ # If the record is persisted and unchanged, the attachment is saved to
23
+ # the database immediately. Otherwise, it'll be saved to the DB when the
24
+ # record is next saved.
25
+ #
26
+ # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
27
+ # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
28
+ # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
29
+ # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
30
+ def attach(attachable)
31
+ if record.persisted? && !record.changed?
32
+ record.update(name => attachable)
33
+ else
34
+ record.public_send("#{name}=", attachable)
35
+ end
36
+ end
37
+
38
+ # Returns +true+ if an attachment has been made.
39
+ #
40
+ # class User < ActiveRecord::Base
41
+ # has_one_attached :avatar
42
+ # end
43
+ #
44
+ # User.new.avatar.attached? # => false
45
+ def attached?
46
+ attachment.present?
47
+ end
48
+
49
+ # Deletes the attachment without purging it, leaving its blob in place.
50
+ def detach
51
+ if attached?
52
+ attachment.delete
53
+ write_attachment nil
54
+ end
55
+ end
56
+
57
+ # Directly purges the attachment (i.e. destroys the blob and
58
+ # attachment and deletes the file on the service).
59
+ def purge
60
+ if attached?
61
+ attachment.purge
62
+ write_attachment nil
63
+ end
64
+ end
65
+
66
+ # Purges the attachment through the queuing system.
67
+ def purge_later
68
+ if attached?
69
+ attachment.purge_later
70
+ write_attachment nil
71
+ end
72
+ end
73
+
74
+ private
75
+ def write_attachment(attachment)
76
+ record.public_send("#{name}_attachment=", attachment)
77
+ end
78
+ end
79
+ end