diff options
Diffstat (limited to 'activestorage')
-rw-r--r-- | activestorage/README.md | 4 | ||||
-rw-r--r-- | activestorage/app/models/active_storage/filename.rb | 20 | ||||
-rw-r--r-- | activestorage/app/models/active_storage/variation.rb | 2 | ||||
-rw-r--r-- | activestorage/config/routes.rb | 10 | ||||
-rw-r--r-- | activestorage/lib/active_storage/attached/macros.rb | 6 | ||||
-rw-r--r-- | activestorage/lib/active_storage/attached/many.rb | 2 | ||||
-rw-r--r-- | activestorage/lib/active_storage/attached/one.rb | 2 | ||||
-rw-r--r-- | activestorage/lib/active_storage/service/gcs_service.rb | 6 | ||||
-rw-r--r-- | activestorage/test/models/attachments_test.rb | 13 |
9 files changed, 45 insertions, 20 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/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/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb index a0ba5654a1..685dd61a0a 100644 --- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -36,7 +36,7 @@ module ActiveStorage def delete(key) instrument :delete, key do begin - file_for(key).try(:delete) + file_for(key).delete rescue Google::Cloud::NotFoundError # Ignore files already deleted end @@ -45,7 +45,7 @@ module ActiveStorage def exist?(key) instrument :exist, key do |payload| - answer = file_for(key).present? + answer = file_for(key).exists? payload[:exist] = answer answer end @@ -81,7 +81,7 @@ module ActiveStorage private def file_for(key) - bucket.file(key) + bucket.file(key, skip_lookup: true) end end end 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") |