aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage')
-rw-r--r--activestorage/README.md4
-rw-r--r--activestorage/Rakefile2
-rw-r--r--activestorage/app/models/active_storage/filename.rb20
-rw-r--r--activestorage/app/models/active_storage/variation.rb2
-rw-r--r--activestorage/config/routes.rb10
-rw-r--r--activestorage/lib/active_storage/attached/macros.rb6
-rw-r--r--activestorage/lib/active_storage/attached/many.rb2
-rw-r--r--activestorage/lib/active_storage/attached/one.rb2
-rw-r--r--activestorage/test/models/attachments_test.rb13
9 files changed, 44 insertions, 17 deletions
diff --git a/activestorage/README.md b/activestorage/README.md
index 17d98978bb..8814887950 100644
--- a/activestorage/README.md
+++ b/activestorage/README.md
@@ -24,7 +24,7 @@ class User < ApplicationRecord
end
# Attach an avatar to the user.
-user.avatar.attach(io: File.open("~/face.jpg"), filename: "avatar.jpg", content_type: "image/jpg")
+user.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
# Does the user have an avatar?
user.avatar.attached? # => true
@@ -63,7 +63,7 @@ end
```
```erb
-<%= form_with model: @message do |form| %>
+<%= form_with model: @message, local: true do |form| %>
<%= form.text_field :title, placeholder: "Title" %><br>
<%= form.text_area :content %><br><br>
diff --git a/activestorage/Rakefile b/activestorage/Rakefile
index aa71a65f6e..2aa4d2a76f 100644
--- a/activestorage/Rakefile
+++ b/activestorage/Rakefile
@@ -11,4 +11,6 @@ Rake::TestTask.new do |test|
test.warning = false
end
+task :package
+
task default: :test
diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb
index dead6b6d33..79d55dc889 100644
--- a/activestorage/app/models/active_storage/filename.rb
+++ b/activestorage/app/models/active_storage/filename.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-# Encapsulates a string representing a filename to provide convenience access to parts of it and a sanitized version.
-# This is what's returned by ActiveStorage::Blob#filename. A Filename instance is comparable so it can be used for sorting.
+# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
+# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
class ActiveStorage::Filename
include Comparable
@@ -9,23 +9,31 @@ class ActiveStorage::Filename
@filename = filename
end
- # Returns the basename of the filename.
+ # Returns the part of the filename preceding any extension.
#
# ActiveStorage::Filename.new("racecar.jpg").base # => "racecar"
+ # ActiveStorage::Filename.new("racecar").base # => "racecar"
+ # ActiveStorage::Filename.new(".gitignore").base # => ".gitignore"
def base
File.basename @filename, extension_with_delimiter
end
- # Returns the extension with delimiter of the filename.
+ # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at the
+ # beginning) with the dot that precedes it. If the filename has no extension, an empty string is returned.
#
# ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter # => ".jpg"
+ # ActiveStorage::Filename.new("racecar").extension_with_delimiter # => ""
+ # ActiveStorage::Filename.new(".gitignore").extension_with_delimiter # => ""
def extension_with_delimiter
File.extname @filename
end
- # Returns the extension without delimiter of the filename.
+ # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at
+ # the beginning). If the filename has no extension, an empty string is returned.
#
# ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter # => "jpg"
+ # ActiveStorage::Filename.new("racecar").extension_without_delimiter # => ""
+ # ActiveStorage::Filename.new(".gitignore").extension_without_delimiter # => ""
def extension_without_delimiter
extension_with_delimiter.from(1).to_s
end
@@ -37,7 +45,7 @@ class ActiveStorage::Filename
# ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg"
# ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg"
#
- # ...and any other character unsafe for URLs or storage is converted or stripped.
+ # Characters considered unsafe for storage (e.g. \, $, and the RTL override character) are replaced with a dash.
def sanitized
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
end
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index bf269e2a8f..cf04a879eb 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/object/inclusion"
-
# A set of transformations that can be applied to a blob to create a variant. This class is exposed via
# the ActiveStorage::Blob#variant method and should rarely be used directly.
#
diff --git a/activestorage/config/routes.rb b/activestorage/config/routes.rb
index 168788475c..c3194887be 100644
--- a/activestorage/config/routes.rb
+++ b/activestorage/config/routes.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
Rails.application.routes.draw do
- get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
+ get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob, internal: true
direct :rails_blob do |blob|
route_for(:rails_service_blob, blob.signed_id, blob.filename)
@@ -11,7 +11,7 @@ Rails.application.routes.draw do
resolve("ActiveStorage::Attachment") { |attachment| route_for(:rails_blob, attachment.blob) }
- get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation
+ get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation, internal: true
direct :rails_variant do |variant|
signed_blob_id = variant.blob.signed_id
@@ -24,7 +24,7 @@ Rails.application.routes.draw do
resolve("ActiveStorage::Variant") { |variant| route_for(:rails_variant, variant) }
- get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
- put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
- post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
+ get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service, internal: true
+ put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service, internal: true
+ post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads, internal: true
end
diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb
index 35a081adc4..f0256718ac 100644
--- a/activestorage/lib/active_storage/attached/macros.rb
+++ b/activestorage/lib/active_storage/attached/macros.rb
@@ -12,6 +12,10 @@ module ActiveStorage
# There is no column defined on the model side, Active Storage takes
# care of the mapping between your records and the attachment.
#
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
+ #
+ # User.with_attached_avatar
+ #
# Under the covers, this relationship is implemented as a +has_one+ association to a
# ActiveStorage::Attachment record and a +has_one-through+ association to a
# ActiveStorage::Blob record. These associations are available as +avatar_attachment+
@@ -33,6 +37,8 @@ module ActiveStorage
has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record
has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
+
if dependent == :purge_later
before_destroy { public_send(name).purge_later }
end
diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb
index 59b7d7d559..1e0657c33c 100644
--- a/activestorage/lib/active_storage/attached/many.rb
+++ b/activestorage/lib/active_storage/attached/many.rb
@@ -17,7 +17,7 @@ module ActiveStorage
#
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
- # document.images.attach(io: File.open("~/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
+ # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
# document.images.attach([ first_blob, second_blob ])
def attach(*attachables)
attachables.flatten.collect do |attachable|
diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb
index ac90f32d95..c66be08f58 100644
--- a/activestorage/lib/active_storage/attached/one.rb
+++ b/activestorage/lib/active_storage/attached/one.rb
@@ -18,7 +18,7 @@ module ActiveStorage
#
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
- # person.avatar.attach(io: File.open("~/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
+ # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
def attach(attachable)
if attached? && dependent == :purge_later
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
index ac346c0087..379ae0a416 100644
--- a/activestorage/test/models/attachments_test.rb
+++ b/activestorage/test/models/attachments_test.rb
@@ -84,6 +84,19 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
end
end
+ test "find with attached blob" do
+ records = %w[alice bob].map do |name|
+ User.create!(name: name).tap do |user|
+ user.avatar.attach create_blob(filename: "#{name}.jpg")
+ end
+ end
+
+ users = User.where(id: records.map(&:id)).with_attached_avatar.all
+
+ assert_equal "alice.jpg", users.first.avatar.filename.to_s
+ assert_equal "bob.jpg", users.second.avatar.filename.to_s
+ end
+
test "attach existing blobs" do
@user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")