edge
เพิ่มเติมที่ rubyonrails.org: เพิ่มเติมเกี่ยวกับ Ruby on Rails

ภาพรวมของ Active Storage

เอกสารนี้เป็นคู่มือเกี่ยวกับวิธีการแนบไฟล์กับโมเดล Active Record ของคุณ

หลังจากอ่านคู่มือนี้คุณจะรู้:

Chapters

  1. Active Storage คืออะไร?
  2. การติดตั้ง
  3. การแนบไฟล์กับเรคคอร์ด
  4. การลบไฟล์
  5. การให้บริการไฟล์
  6. ดาวน์โหลดไฟล์
  7. วิเคราะห์ไฟล์
  8. การแสดงภาพ วิดีโอ และ PDF
  9. การอัปโหลดโดยตรง
  10. การทดสอบ
  11. การสนับสนุนบริการคลาวด์อื่น
  12. การล้างอัปโหลดที่ไม่ได้แนบ

1 Active Storage คืออะไร?

Active Storage ช่วยให้คุณสามารถอัปโหลดไฟล์ไปยังบริการจัดเก็บในคลาวด์ เช่น Amazon S3, Google Cloud Storage หรือ Microsoft Azure Storage และแนบไฟล์เหล่านั้นกับอ็อบเจ็กต์ Active Record มันมาพร้อมกับบริการดิสก์ในเครื่องสำหรับการพัฒนาและทดสอบและสนับสนุนการสำรองข้อมูลและการย้ายไฟล์ไปยังบริการย่อย

โดยใช้ Active Storage แอปพลิเคชันสามารถแปลงการอัปโหลดรูปภาพหรือสร้างรูปภาพแทนของการอัปโหลดที่ไม่ใช่รูปภาพ เช่น PDF และวิดีโอ และสามารถแยกข้อมูลเมตาดาต้าจากไฟล์ที่ไม่เป็นรูปภาพได้

1.1 ความต้องการ

คุณลักษณะต่าง ๆ ของ Active Storage ขึ้นอยู่กับซอฟต์แวร์จากบุคคลที่สามที่ Rails จะไม่ติดตั้งและต้องติดตั้งแยกกัน:

  • libvips เวอร์ชัน 8.6+ หรือ ImageMagick สำหรับการวิเคราะห์และการแปลงรูปภาพ
  • ffmpeg เวอร์ชัน 3.4+ สำหรับการแสดงตัวอย่างวิดีโอและ ffprobe สำหรับการวิเคราะห์วิดีโอ/เสียง
  • poppler หรือ muPDF สำหรับการแสดงตัวอย่าง PDF

การวิเคราะห์และการแปลงรูปภาพยังต้องการแพคเกจ image_processing ถ้ายังไม่ได้เปิดคอมเมนต์ใน Gemfile ให้เปิดคอมเมนต์หรือเพิ่มแพคเกจดังกล่าว:

gem "image_processing", ">= 1.2"

เคล็ดลับ: เมื่อเปรียบเทียบกับ libvips, ImageMagick มีชื่อที่ดีกว่าและมีความพร้อมใช้งานมากกว่า อย่างไรก็ตาม libvips สามารถทำงานได้เร็วขึ้นถึง 10 เท่าและใช้หน่วยความจำน้อยลง สำหรับไฟล์ JPEG สามารถปรับปรุงได้อีกโดยการแทนที่ libjpeg-dev ด้วย libjpeg-turbo-dev ซึ่งเร็วขึ้น 2-7 เท่า

คำเตือน: ก่อนที่คุณจะติดตั้งและใช้ซอฟต์แวร์จากบุคคลที่สาม ตรวจสอบให้แน่ใจว่าคุณเข้าใจผลที่เกิดจากการใช้งานที่เกี่ยวข้องกับการอนุญาตในการใช้งาน โดยเฉพาะอย่างยิ่ง MuPDF ที่มีการอนุญาตภายใต้ AGPL และต้องใช้ใบอนุญาตพาณิชย์สำหรับบางการใช้งาน

2 การติดตั้ง

$ bin/rails active_storage:install
$ bin/rails db:migrate

การตั้งค่าและสร้างตารางสามตารางที่ Active Storage ใช้งาน: active_storage_blobs, active_storage_attachments, และ active_storage_variant_records.

ตาราง วัตถุประสงค์
active_storage_blobs เก็บข้อมูลเกี่ยวกับไฟล์ที่อัปโหลด เช่นชื่อไฟล์และประเภทเนื้อหา
active_storage_attachments เป็นตารางเชื่อมโยงโพลิมอร์ฟิกที่ เชื่อมโยงโมเดลของคุณกับ blobs หากชื่อคลาสของโมเดลเปลี่ยน คุณจะต้องเรียกใช้การเคลื่อนย้ายบนตารางนี้เพื่ออัปเดต record_type ให้เป็นชื่อคลาสใหม่ของโมเดล
active_storage_variant_records หากเปิดใช้งาน การติดตามตัวแปรแบบ จะเก็บบันทึกสำหรับแต่ละตัวแปรที่ถูกสร้าง

คำเตือน: หากคุณใช้ UUID แทนตัวระบุหลักเป็นจำนวนเต็มบนโมเดลของคุณ คุณควรตั้งค่า Rails.application.config.generators { |g| g.orm :active_record, primary_key_type: :uuid } ในไฟล์คอนฟิก

ประกาศบริการ Active Storage ใน config/storage.yml สำหรับแต่ละบริการที่แอปพลิเคชันของคุณใช้ ให้ระบุชื่อและการกำหนดค่าที่จำเป็น ตัวอย่างด้านล่างประกาศบริการสามบริการชื่อ local, test, และ amazon: ```yaml local: service: Disk root: <%= Rails.root.join("storage") %>

test: service: Disk root: <%= Rails.root.join("tmp/storage") %>

amazon: service: S3 access_key_id: "" secret_access_key: "" bucket: "" region: "" # เช่น 'us-east-1' ```

ตั้งค่าให้ Active Storage ใช้บริการใดโดยการตั้งค่า Rails.application.config.active_storage.service โดยแนะนำให้ทำการตั้งค่านี้ในแต่ละ environment โดยเฉพาะ ตัวอย่างเช่น เพื่อใช้บริการ disk จากตัวอย่างก่อนหน้านี้ใน environment การพัฒนา คุณควรเพิ่มต่อไปนี้ใน config/environments/development.rb:

# เก็บไฟล์ในเครื่อง
config.active_storage.service = :local

เพื่อใช้บริการ S3 ใน production คุณควรเพิ่มต่อไปนี้ใน config/environments/production.rb:

# เก็บไฟล์ใน Amazon S3
config.active_storage.service = :amazon

เพื่อใช้บริการ test เมื่อทดสอบ คุณควรเพิ่มต่อไปนี้ใน config/environments/test.rb:

# เก็บไฟล์ที่อัปโหลดในระบบไฟล์ใน temporary directory
config.active_storage.service = :test

หมายเหตุ: ไฟล์การตั้งค่าที่เฉพาะกับแต่ละ environment จะมีความสำคัญกว่า: ตัวอย่างเช่นใน production ไฟล์ config/storage/production.yml (หากมี) จะมีความสำคัญกว่าไฟล์ config/storage.yml

แนะนำให้ใช้ Rails.env ในชื่อ bucket เพิ่มเติมเพื่อลดความเสี่ยงในการทำลายข้อมูลใน production

amazon:
  service: S3
  # ...
  bucket: your_own_bucket-<%= Rails.env %>

google:
  service: GCS
  # ...
  bucket: your_own_bucket-<%= Rails.env %>

azure:
  service: AzureStorage
  # ...
  container: your_container_name-<%= Rails.env %>

อ่านต่อเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับตัวอย่างการใช้งานแอดาปเตอร์บริการที่มีอยู่ (เช่น Disk และ S3) และการตั้งค่าที่ต้องการ

2.1 บริการ Disk

ประกาศบริการ Disk ใน config/storage.yml:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

2.2 บริการ S3 (Amazon S3 และ S3-compatible APIs)

เพื่อเชื่อมต่อกับ Amazon S3 ให้ประกาศบริการ S3 ใน config/storage.yml:

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

ให้ระบุตัวเลือก client และ upload ได้ตามต้องการ:

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""
  http_open_timeout: 0
  http_read_timeout: 0
  retry_limit: 0
  upload:
    server_side_encryption: "" # 'aws:kms' หรือ 'AES256'
    cache_control: "private, max-age=<%= 1.day.to_i %>"

เคล็ดลับ: ตั้งค่า client HTTP timeouts และ retry limits ที่เหมาะสมสำหรับแอปพลิเคชันของคุณ ในสถานการณ์ที่เกิดข้อผิดพลาดบางกรณี การตั้งค่าเริ่มต้นของ AWS client อาจทำให้เกิดการเก็บเชื่อมต่อไว้เป็นเวลานานถึงหลายนาทีและทำให้เกิดการจัดคิวของคำขอ

เพิ่ม aws-sdk-s3 gem เข้าไปใน Gemfile:

gem "aws-sdk-s3", require: false

หมายเหตุ: คุณจำเป็นต้องมีสิทธิ์ต่อตัวอย่างหลักของ Active Storage ดังต่อไปนี้: s3:ListBucket, s3:PutObject, s3:GetObject, และ s3:DeleteObject การเข้าถึงสาธารณะ ต้องการ s3:PutObjectAcl อีกด้วย หากคุณมีการตั้งค่าตัวเลือกการอัปโหลดเพิ่มเติม เช่น การตั้งค่า ACLs อาจต้องการสิทธิ์เพิ่มเติม

หากคุณต้องการใช้ตัวแปรสภาพแวดล้อม ไฟล์การตั้งค่ามาตรฐานของ SDK โปรไฟล์ โปรไฟล์ของตัวอย่าง IAM หรือ task roles คุณสามารถละเว้นคีย์ access_key_id, secret_access_key, และ region ในตัวอย่างข้างต้นได้ บริการ S3 รองรับทุกตัวเลือกการรับรองตัวตนที่อธิบายใน เอกสาร AWS SDK

เพื่อเชื่อมต่อกับ S3-compatible object storage API เช่น DigitalOcean Spaces ให้ระบุ endpoint:

digitalocean:
  service: S3
  endpoint: https://nyc3.digitaloceanspaces.com
  access_key_id: ...
  secret_access_key: ...
  # ...และตัวเลือกอื่นๆ

มีตัวเลือกอื่นๆอีกมากมาย คุณสามารถตรวจสอบได้ที่ AWS S3 Client เอกสาร

2.3 บริการเก็บข้อมูล Microsoft Azure Storage

ประกาศบริการเก็บข้อมูล Azure Storage ใน config/storage.yml:

azure:
  service: AzureStorage
  storage_account_name: ""
  storage_access_key: ""
  container: ""

เพิ่ม gem azure-storage-blob เข้าไปใน Gemfile:

gem "azure-storage-blob", "~> 2.0", require: false

2.4 บริการเก็บข้อมูล Google Cloud Storage

ประกาศบริการเก็บข้อมูล Google Cloud Storage ใน config/storage.yml:

google:
  service: GCS
  credentials: <%= Rails.root.join("path/to/keyfile.json") %>
  project: ""
  bucket: ""

สามารถให้ Hash ของข้อมูลรับรองแทนที่เส้นทางไฟล์คีย์ได้ตามต้องการ:

google:
  service: GCS
  credentials:
    type: "service_account"
    project_id: ""
    private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %>
    private_key: <%= Rails.application.credentials.dig(:gcs, :private_key).dump %>
    client_email: ""
    client_id: ""
    auth_uri: "https://accounts.google.com/o/oauth2/auth"
    token_uri: "https://accounts.google.com/o/oauth2/token"
    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs"
    client_x509_cert_url: ""
  project: ""
  bucket: ""

สามารถให้ Cache-Control metadata เพื่อตั้งค่าบนสิ่งที่อัปโหลดได้ตามต้องการ:

google:
  service: GCS
  ...
  cache_control: "public, max-age=3600"

สามารถใช้ IAM แทนที่ credentials เมื่อเซ็นต์ URL ได้ตามต้องการ ซึ่งมีประโยชน์หากคุณกำลังรับรองความถูกต้องของแอปพลิเคชัน GKE ของคุณด้วย Workload Identity ดูรายละเอียดเพิ่มเติมใน บล็อกโพสต์ Google Cloud นี้

google:
  service: GCS
  ...
  iam: true

สามารถใช้ GSA ที่เฉพาะเจาะจงเมื่อเซ็นต์ URL ได้ตามต้องการ ในกรณีที่ใช้ IAM จะมีการติดต่อกับ metadata server เพื่อรับอีเมล GSA แต่เซิร์ฟเวอร์ metadata นี้ไม่มีอยู่เสมอ (เช่นการทดสอบในเครื่อง) และคุณอาจต้องการใช้ GSA ที่ไม่ใช่ค่าเริ่มต้น

google:
  service: GCS
  ...
  iam: true
  gsa_email: "[email protected]"

เพิ่ม gem google-cloud-storage เข้าไปใน Gemfile:

gem "google-cloud-storage", "~> 1.11", require: false

2.5 บริการ Mirror

คุณสามารถทำให้บริการหลาย ๆ รายการสะท้อนกันได้โดยกำหนดบริการ Mirror บริการ Mirror จะทำการทำซ้ำการอัปโหลดและการลบข้อมูลข้ามบริการย่อยสองรายการหรือมากกว่า

บริการ Mirror ถูกออกแบบมาเพื่อใช้ชั่วคราวในระหว่างการย้ายระหว่างบริการในการใช้งานจริง คุณสามารถเริ่มการสะท้อนกับบริการใหม่ คัดลอกไฟล์ที่มีอยู่ก่อนหน้านี้จากบริการเก่าไปยังบริการใหม่ แล้วเปลี่ยนไปใช้บริการใหม่

หมายเหตุ: การสะท้อนไม่เป็นแบบอะตอมิก อาจมีการอัปโหลดสำเร็จในบริการหลักและล้มเหลวในบริการย่อยใดบางรายการ ก่อนที่จะเปลี่ยนไปใช้บริการใหม่ ตรวจสอบให้แน่ใจว่าไฟล์ทั้งหมดถูกคัดลอก

กำหนดบริการที่คุณต้องการสะท้อนตามที่อธิบายไว้ด้านบน อ้างถึงชื่อของบริการเมื่อกำหนดบริการ Mirror:

s3_west_coast:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

s3_east_coast:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

production:
  service: Mirror
  primary: s3_east_coast
  mirrors:
    - s3_west_coast

แม้ว่าบริการรองทั้งหมดจะได้รับการอัปโหลด การดาวน์โหลดจะถูกจัดการโดยบริการหลักเสมอ

บริการ Mirror เข้ากันได้กับการอัปโหลดโดยตรง ไฟล์ใหม่ถูกอัปโหลดโดยตรงไปยังบริการหลัก เมื่อไฟล์ที่อัปโหลดโดยตรงถูกแนบกับบันทึก งานพื้นหลังจะถูกเพิ่มเข้าไปในคิวเพื่อคัดลอกไปยังบริการรอง

2.6 การเข้าถึงสาธารณะ

ตามค่าเริ่มต้น Active Storage ถือว่าการเข้าถึงบริการเป็นส่วนตัว ซึ่งหมายความว่าจะสร้างลายเซ็น URL ที่ใช้ได้เพียงครั้งเดียวสำหรับ blobs หากคุณต้องการให้ blobs เป็นสาธารณะได้ ระบุ public: true ใน config/storage.yml ของแอปของคุณ:

gcs: &gcs
  service: GCS
  project: ""

private_gcs:
  <<: *gcs
  credentials: <%= Rails.root.join("path/to/private_key.json") %>
  bucket: ""

public_gcs:
  <<: *gcs
  credentials: <%= Rails.root.join("path/to/public_key.json") %>
  bucket: ""
  public: true

ตรวจสอบให้แน่ใจว่า Bucket ของคุณได้รับการกำหนดค่าให้สามารถเข้าถึงได้สาธารณะอย่างถูกต้อง ดูเอกสารเกี่ยวกับวิธีเปิดใช้งานสิทธิ์การอ่านสาธารณะสำหรับบริการเก็บข้อมูล Amazon S3, Google Cloud Storage, และ Microsoft Azure นอกจากนี้ Amazon S3 ยังต้องการให้คุณมีสิทธิ์ s3:PutObjectAcl

เมื่อแปลงแอปพลิเคชันที่มีอยู่ให้ใช้ public: true ตรวจสอบให้แน่ใจว่าอัปเดตไฟล์แต่ละไฟล์ใน Bucket เพื่อให้สามารถอ่านได้สาธารณะก่อนที่จะสลับไปใช้งาน

3 การแนบไฟล์กับเรคคอร์ด

3.1 has_one_attached

แมโคร has_one_attached จะตั้งค่าการแมปหนึ่งต่อหนึ่งระหว่างเรคคอร์ดและไฟล์ แต่ละเรคคอร์ดสามารถมีไฟล์ที่แนบมาหนึ่งไฟล์ได้

ตัวอย่างเช่น สมมติว่าแอปพลิเคชันของคุณมีโมเดล User หากคุณต้องการให้แต่ละผู้ใช้มีอวาตาร์ กำหนดโมเดล User ดังนี้:

class User < ApplicationRecord
  has_one_attached :avatar
end

หรือหากคุณใช้ Rails 6.0+ คุณสามารถเรียกใช้คำสั่งสร้างโมเดลได้ดังนี้:

bin/rails generate model User avatar:attachment

คุณสามารถสร้างผู้ใช้พร้อมอวาตาร์ได้:

<%= form.file_field :avatar %>
class SignupController < ApplicationController
  def create
    user = User.create!(user_params)
    session[:user_id] = user.id
    redirect_to root_path
  end

  private
    def user_params
      params.require(:user).permit(:email_address, :password, :avatar)
    end
end

เรียกใช้ avatar.attach เพื่อแนบอวาตาร์กับผู้ใช้ที่มีอยู่:

user.avatar.attach(params[:avatar])

เรียกใช้ avatar.attached? เพื่อตรวจสอบว่าผู้ใช้รายบุคคลใดบางรายมีอวาตาร์หรือไม่:

user.avatar.attached?

ในบางกรณีคุณอาจต้องการแทนที่บริการเริ่มต้นสำหรับการแนบที่เฉพาะ คุณสามารถกำหนดค่าบริการเฉพาะต่อไปนี้ต่อไปนี้โดยใช้ตัวเลือก service:

class User < ApplicationRecord
  has_one_attached :avatar, service: :s3
end

คุณสามารถกำหนดค่าตัวแปรเฉพาะต่อไปนี้สำหรับการแนบโดยเรียกใช้เมธอด variant บนวัตถุที่ได้รับการแนบ:

class User < ApplicationRecord
  has_one_attached :avatar do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

เรียกใช้ avatar.variant(:thumb) เพื่อรับตัวแปร thumb ของอวาตาร์:

<%= image_tag user.avatar.variant(:thumb) %>

คุณสามารถใช้ตัวแปรเฉพาะสำหรับการแสดงตัวอย่างด้วย:

class User < ApplicationRecord
  has_one_attached :video do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end
<%= image_tag user.video.preview(:thumb) %>

3.2 has_many_attached

แมโคร has_many_attached จะตั้งค่าความสัมพันธ์หนึ่งต่อหลายระหว่างเรคคอร์ดและไฟล์ แต่ละเรคคอร์ดสามารถมีไฟล์หลายไฟล์ที่แนบมาได้

ตัวอย่างเช่น สมมติว่าแอปพลิเคชันของคุณมีโมเดล Message หากคุณต้องการให้แต่ละข้อความมีภาพหลายภาพ กำหนดโมเดล Message ดังนี้:

class Message < ApplicationRecord
  has_many_attached :images
end

หรือหากคุณใช้ Rails 6.0+ คุณสามารถเรียกใช้คำสั่งสร้างโมเดลได้ดังนี้:

bin/rails generate model Message images:attachments

คุณสามารถสร้างข้อความพร้อมภาพได้:

class MessagesController < ApplicationController
  def create
    message = Message.create!(message_params)
    redirect_to message
  end

  private
    def message_params
      params.require(:message).permit(:title, :content, images: [])
    end
end

เรียกใช้ images.attach เพื่อเพิ่มภาพใหม่ในข้อความที่มีอยู่:

@message.images.attach(params[:images])

เรียกใช้ images.attached? เพื่อตรวจสอบว่าข้อความใดข้อความหนึ่งมีภาพหรือไม่:

@message.images.attached?

การแทนที่บริการเริ่มต้นทำได้เหมือนกับ has_one_attached โดยใช้ตัวเลือก service:

class Message < ApplicationRecord
  has_many_attached :images, service: :s3
end

การกำหนดค่าตัวแปรเฉพาะทำได้เหมือนกับ has_one_attached โดยเรียกใช้เมธอด variant บนวัตถุที่ได้รับการแนบ: ruby class Message < ApplicationRecord has_many_attached :images do |attachable| attachable.variant :thumb, resize_to_limit: [100, 100] end end

3.3 การแนบไฟล์/วัตถุ IO

บางครั้งคุณอาจต้องแนบไฟล์ที่ไม่ได้มาจาก HTTP request เช่น คุณอาจต้องการแนบไฟล์ที่คุณสร้างบนดิสก์หรือดาวน์โหลดจาก URL ที่ผู้ใช้ส่งเข้ามา คุณอาจต้องการแนบไฟล์ fixture ในการทดสอบโมเดล ในกรณีนั้น คุณสามารถให้ Hash ที่มีอย่างน้อย IO object ที่เปิดและชื่อไฟล์:

@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf')

เมื่อเป็นไปได้ โปรดระบุ content type ด้วย Active Storage พยายาม กำหนดประเภทของไฟล์จากข้อมูลของมัน หากไม่สามารถทำได้ Active Storage จะใช้ content type ที่คุณระบุแทน

@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')

คุณสามารถข้ามการตรวจสอบประเภทเนื้อหาจากข้อมูลได้โดยส่ง identify: false พร้อมกับ content_type

@message.images.attach(
  io: File.open('/path/to/file'),
  filename: 'file.pdf',
  content_type: 'application/pdf',
  identify: false
)

หากคุณไม่ได้ระบุ content type และ Active Storage ไม่สามารถกำหนดประเภทของไฟล์ได้โดยอัตโนมัติ Active Storage จะใช้ค่าเริ่มต้นเป็น application/octet-stream

4 การลบไฟล์

ในการลบไฟล์ที่แนบในโมเดล ให้เรียกใช้ purge บน attachment นั้น หากแอปพลิเคชันของคุณตั้งค่าให้ใช้ Active Job การลบสามารถทำได้ในพื้นหลังโดยเรียกใช้ purge_later การลบจะลบ blob และไฟล์จากบริการจัดเก็บ

# ทำลาย avatar และไฟล์ทรัพยากรจริงๆ แบบเสียงตอบกลับ
user.avatar.purge

# ทำลายโมเดลที่เกี่ยวข้องและไฟล์ทรัพยากรจริงๆ แบบเสียงตอบกลับ ผ่าน Active Job
user.avatar.purge_later

5 การให้บริการไฟล์

Active Storage สนับสนุนวิธีการให้บริการไฟล์สองวิธี: การเปลี่ยนเส้นทางและการโปรกซี

คำเตือน: คอนโทรลเลอร์ Active Storage ทั้งหมดสามารถเข้าถึงได้โดยสาธารณะตามค่าเริ่มต้น URL ที่สร้างขึ้นยากที่จะเดา แต่ถาวรตามออกแบบ หากไฟล์ของคุณต้องการระดับความปลอดภัยที่สูงขึ้น คุณควรพิจารณาการดำเนินการ Authenticated Controllers.

5.1 โหมดการเปลี่ยนเส้นทาง

ในการสร้าง URL ถาวรสำหรับ blob คุณสามารถส่ง blob ไปยัง url_for view helper นี้จะสร้าง URL ด้วย signed_id ของ blob ที่เชื่อมต่อกับ RedirectController ของ blob

url_for(user.avatar)
# => /rails/active_storage/blobs/:signed_id/my-avatar.png

RedirectController จะเปลี่ยนเส้นทางไปยังจุดปลายทางบริการจริง การเปลี่ยนเส้นทางนี้ ทำให้ URL บริการแยกต่างหากจาก URL จริง และอนุญาตให้เกิดการเชื่อมโยงไฟล์ในบริการต่าง ๆ เพื่อให้มีความพร้อมสูง ตัวเลือกการเปลี่ยนเส้นทางมีอายุการใช้งาน HTTP 5 นาที

ในการสร้างลิงก์ดาวน์โหลด ให้ใช้ rails_blob_{path|url} helper นี้ ช่วยให้คุณสามารถตั้งค่า disposition ได้

rails_blob_path(user.avatar, disposition: "attachment")

คำเตือน: เพื่อป้องกันการโจมตี XSS Active Storage บังคับให้ Content-Disposition header เป็น "attachment" สำหรับบางประเภทของไฟล์ หากต้องการเปลี่ยนแปลงพฤติกรรมนี้ โปรดดู ตัวเลือกการกำหนดค่าที่มีอยู่ใน การกำหนดค่าแอปพลิเคชัน Rails.

หากคุณต้องการสร้างลิงก์จากภายนอกคอนโทรลเลอร์/วิว (Background jobs, Cronjobs, เป็นต้น) คุณสามารถเข้าถึง rails_blob_path ได้ดังนี้:

Rails.application.routes.url_helpers.rails_blob_path(user.avatar, only_path: true)

5.2 โหมดการโปรกซี

ตัวเลือกอื่น ๆ คือการโปรกซีไฟล์ ซึ่งหมายความว่าเซิร์ฟเวอร์แอปพลิเคชันของคุณจะดาวน์โหลดข้อมูลไฟล์จากบริการจัดเก็บเป็นการตอบสนองต่อคำขอ สิ่งนี้สามารถใช้งานได้สำหรับการให้บริการไฟล์จาก CDN คุณสามารถกำหนดค่า Active Storage เพื่อใช้โหมด proxying เป็นค่าเริ่มต้นได้:

# config/initializers/active_storage.rb
Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy

หรือหากคุณต้องการ proxy แนบที่เฉพาะเฉพาะ คุณสามารถใช้ URL helpers ในรูปแบบของ rails_storage_proxy_path และ rails_storage_proxy_url ได้

<%= image_tag rails_storage_proxy_path(@user.avatar) %>

5.2.1 การใช้ CDN สำหรับ Active Storage

นอกจากนี้ เพื่อใช้ CDN สำหรับการแนบ Active Storage คุณจะต้องสร้าง URL ด้วยโหมด proxy เพื่อให้บริการโดยแอปของคุณและ CDN จะแคชแนบโดยไม่ต้องมีการกำหนดค่าเพิ่มเติม สิ่งนี้ทำงานได้โดยอัตโนมัติเนื่องจากคอนโทรลเลอร์ proxy Active Storage เริ่มต้นกำหนด HTTP header เพื่อแจ้งให้ CDN แคชการตอบสนอง

คุณยังควรตรวจสอบให้แน่ใจว่า URL ที่สร้างขึ้นใช้โฮสต์ของ CDN แทนโฮสต์แอปของคุณ มีหลายวิธีในการทำสิ่งนี้ แต่โดยทั่วไปนั้นเกี่ยวข้องกับการปรับแต่งไฟล์ config/routes.rb เพื่อให้คุณสามารถสร้าง URL ที่เหมาะสมสำหรับการแนบและการแปลงแบบต่าง ๆ ตัวอย่างเช่น คุณสามารถเพิ่มโค้ดนี้:

# config/routes.rb
direct :cdn_image do |model, options|
  expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }

  if model.respond_to?(:signed_id)
    route_for(
      :rails_service_blob_proxy,
      model.signed_id(expires_in: expires_in),
      model.filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  else
    signed_blob_id = model.blob.signed_id(expires_in: expires_in)
    variation_key  = model.variation.key
    filename       = model.blob.filename

    route_for(
      :rails_blob_representation_proxy,
      signed_blob_id,
      variation_key,
      filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  end
end

แล้วสร้างเส้นทางดังนี้:

<%= cdn_image_url(user.avatar.variant(resize_to_limit: [128, 128])) %>

5.3 คอนโทรลเลอร์ที่ต้องการการรับรองสิทธิ์

คอนโทรลเลอร์ Active Storage ทั้งหมดสามารถเข้าถึงได้โดยสาธารณะตามค่าเริ่มต้น URL ที่สร้างขึ้นใช้ signed_id ธรรมดา ทำให้ยากต่อการเดาแต่ถาวร ใครก็ตามที่รู้ URL ของ blob จะสามารถเข้าถึงได้ แม้ว่า before_action ใน ApplicationController ของคุณจะต้องการการเข้าสู่ระบบ หากไฟล์ของคุณต้องการระดับความปลอดภัยที่สูงขึ้น คุณสามารถสร้างคอนโทรลเลอร์ที่ต้องการการรับรองสิทธิ์ของคุณเอง โดยใช้ ActiveStorage::Blobs::RedirectController, ActiveStorage::Blobs::ProxyController, ActiveStorage::Representations::RedirectController และ ActiveStorage::Representations::ProxyController

เพื่ออนุญาตให้บัญชีเฉพาะเข้าถึงโลโก้ของตนเองเท่านั้น คุณสามารถทำตามขั้นตอนต่อไปนี้:

# config/routes.rb
resource :account do
  resource :logo
end
# app/controllers/logos_controller.rb
class LogosController < ApplicationController
  # ผ่าน ApplicationController:
  # include Authenticate, SetCurrentAccount

  def show
    redirect_to Current.account.logo.url
  end
end
<%= image_tag account_logo_path %>

และคุณควรปิดใช้งานเส้นทางเริ่มต้นของ Active Storage ด้วย:

config.active_storage.draw_routes = false

เพื่อป้องกันการเข้าถึงไฟล์ด้วย URL ที่สาธารณะได้

6 ดาวน์โหลดไฟล์

บางครั้งคุณอาจต้องประมวลผล blob หลังจากอัปโหลด เช่น เพื่อแปลงรูปแบบไฟล์ ใช้เมธอด download ของแนบเพื่ออ่านข้อมูลไบนารีของ blob เข้าสู่หน่วยความจำ:

binary = user.avatar.download

คุณอาจต้องการดาวน์โหลด blob เป็นไฟล์บนดิสก์เพื่อให้โปรแกรมภายนอก (เช่น โปรแกรมสแกนไวรัสหรือโปรแกรมแปลงสื่อ) สามารถดำเนินการได้ ใช้เมธอด open ของแนบเพื่อดาวน์โหลด blob เป็น tempfile บนดิสก์:

message.video.open do |file|
  system '/path/to/virus/scanner', file.path
  # ...
end

สิ่งสำคัญที่ต้องรู้คือไฟล์ยังไม่พร้อมใช้งานใน callback after_create แต่จะพร้อมใช้งานใน after_create_commit เท่านั้น

7 วิเคราะห์ไฟล์

Active Storage วิเคราะห์ไฟล์หลังจากอัปโหลดโดยจัดคิวงานใน Active Job ไฟล์ที่วิเคราะห์แล้วจะเก็บข้อมูลเพิ่มเติมในแฮช metadata รวมถึง analyzed: true คุณสามารถตรวจสอบว่า blob ได้รับการวิเคราะห์แล้วหรือไม่ โดยเรียกใช้ analyzed? บน blob การวิเคราะห์ภาพให้คุณสมบัติ width และ height ภาพยนตร์วิเคราะห์ให้คุณสมบัติเหล่านี้รวมทั้ง duration angle display_aspect_ratio และ video และ audio ที่เป็นบูลีนเพื่อแสดงถึงการมีช่องเสียงเหล่านั้น การวิเคราะห์เสียงให้คุณสมบัติ duration และ bit_rate

8 การแสดงภาพ วิดีโอ และ PDF

Active Storage สนับสนุนการแสดงผลไฟล์หลากหลายรูปแบบ คุณสามารถเรียกใช้ representation บน attachment เพื่อแสดงตัวแปรภาพ หรือตัวอย่างวิดีโอหรือ PDF ก่อนที่จะเรียกใช้ representation ตรวจสอบว่า attachment สามารถแสดงได้โดยเรียก representable? บางรูปแบบไฟล์ไม่สามารถดูตัวอย่างได้โดย Active Storage อย่างเช่นเอกสาร Word หาก representable? คืนค่าเป็นเท็จคุณอาจต้องการ ลิงก์ไปยัง ไฟล์แทน

<ul>
  <% @message.files.each do |file| %>
    <li>
      <% if file.representable? %>
        <%= image_tag file.representation(resize_to_limit: [100, 100]) %>
      <% else %>
        <%= link_to rails_blob_path(file, disposition: "attachment") do %>
          <%= image_tag "placeholder.png", alt: "Download file" %>
        <% end %>
      <% end %>
    </li>
  <% end %>
</ul>

ภายใน representation เรียกใช้ variant สำหรับภาพ และ preview สำหรับไฟล์ที่สามารถดูตัวอย่างได้ คุณยังสามารถเรียกใช้เมธอดเหล่านี้โดยตรง

8.1 การโหลดแบบเกียวกับการโหลดทันที

โดยค่าเริ่มต้น Active Storage จะประมวลผลตัวแทนอย่างเกียวกับการโหลด โค้ดนี้:

image_tag file.representation(resize_to_limit: [100, 100])

จะสร้างแท็ก <img> โดย src ชี้ไปที่ ActiveStorage::Representations::RedirectController เบราว์เซอร์จะส่งคำขอไปยังตัวควบคุมนั้น ซึ่งจะดำเนินการดังต่อไปนี้:

  1. ประมวลผลไฟล์และอัปโหลดไฟล์ที่ประมวลผลได้ตามที่จำเป็น
  2. ส่ง 302 ไปยังไฟล์ทั้งนี้ไปที่
    • บริการระยะไกล (เช่น S3)
    • หรือ ActiveStorage::Blobs::ProxyController ซึ่งจะส่งคืนเนื้อหาของไฟล์หาก โหมดพร็อกซี เปิดใช้งาน

การโหลดไฟล์แบบเกียวกับการโหลดทันทีช่วยให้ฟีเจอร์เช่น URL ที่ใช้ครั้งเดียว ทำงานโดยไม่ทำให้หน้าเว็บโหลดช้าลง

สำหรับกรณีส่วนใหญ่นี้ทำงานได้ดี

หากคุณต้องการสร้าง URL สำหรับภาพทันทีคุณสามารถเรียกใช้ .processed.url:

image_tag file.representation(resize_to_limit: [100, 100]).processed.url

ตัวติดตามตัวแปร Active Storage ปรับปรุงประสิทธิภาพของสิ่งนี้โดยการเก็บบันทึกในฐานข้อมูลหากตัวแทนที่ร้องขอได้ถูกประมวลผลไว้ก่อนหน้านี้ ดังนั้นโค้ดด้านบนจะทำการเรียก API ไปยังบริการระยะไกล (เช่น S3) เพียงครั้งเดียวและเมื่อตัวแปรถูกเก็บไว้แล้วจะใช้ตัวแปรนั้น ตัวติดตามตัวแปรทำงานโดยอัตโนมัติ แต่สามารถปิดใช้งานได้ผ่าน config.active_storage.track_variants

หากคุณกำลังแสดงภาพจำนวนมากบนหน้าเว็บตัวอย่างด้านบนอาจส่งผลให้เกิดคำถาม N+1 โหลดเร็คคอร์ดตัวแปรทั้งหมด หากต้องการหลีกเลี่ยงคำถาม N+1 เรียกใช้งานขอบเขตชื่อบน ActiveStorage::Attachment

message.images.with_all_variant_records.each do |file|
  image_tag file.representation(resize_to_limit: [100, 100]).processed.url
end

8.2 การแปลงภาพ

การแปลงภาพช่วยให้คุณสามารถแสดงภาพในขนาดที่คุณเลือกได้ เพื่อสร้างตัวแปรของภาพให้เรียก variant บน attachment คุณสามารถส่งการแปลงใดก็ได้ที่รองรับโดยตัวประมวลผลตัวแปรไปยังเมธอดนี้ เมื่อเบราว์เซอร์เข้าถึง URL ตัวแปร Active Storage จะแปลงภาพต้นฉบับเป็นรูปแบบที่ระบุและเปลี่ยนเส้นทางไปยังตำแหน่งบริการใหม่ของมัน

<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>

หากร้องขอตัวแปร Active Storage จะใช้การแปลงโดยอัตโนมัติขึ้นอยู่กับรูปแบบของภาพ 1. ประเภทเนื้อหาที่เปลี่ยนแปลงได้ (ตามที่กำหนดโดย config.active_storage.variable_content_types ) และไม่ถือเป็นรูปภาพเว็บ (ตามที่กำหนดโดย config.active_storage.web_image_content_types ) จะถูกแปลงเป็น PNG

  1. หากไม่ระบุ quality จะใช้คุณภาพเริ่มต้นของตัวประมวลผลแบร์เวียนตามรูปแบบ

Active Storage สามารถใช้ Vips หรือ MiniMagick เป็นตัวประมวลผลแบร์เวียนได้ ค่าเริ่มต้นขึ้นอยู่กับเวอร์ชันเป้าหมายของ config.load_defaults และ สามารถเปลี่ยนตัวประมวลผลได้โดยการตั้งค่า config.active_storage.variant_processor

สองตัวประมวลผลนี้ไม่สามารถทำงานร่วมกันได้อย่างสมบูรณ์ ดังนั้นเมื่อย้ายแอปพลิเคชันที่มีอยู่ระหว่าง MiniMagick และ Vips จะต้องทำการเปลี่ยนแปลงบางอย่างหากใช้ตัวเลือกที่เฉพาะเจาะจงตามรูปแบบ:

<!-- MiniMagick -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>

<!-- Vips -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>

พารามิเตอร์ที่ใช้ให้ใช้งานได้ถูกกำหนดโดย gem image_processing และขึ้นอยู่กับตัวประมวลผลแบร์เวียนที่คุณใช้ แต่ทั้งสองรองรับพารามิเตอร์ต่อไปนี้:

พารามิเตอร์ ตัวอย่าง คำอธิบาย
resize_to_limit resize_to_limit: [100, 100] ลดขนาดรูปภาพให้พอดีกับขนาดที่ระบุ โดยรักษาอัตราส่วนเดิม จะปรับขนาดรูปภาพเฉพาะเมื่อมีขนาดใหญ่กว่าที่ระบุ
resize_to_fit resize_to_fit: [100, 100] ปรับขนาดรูปภาพให้พอดีกับขนาดที่ระบุ โดยรักษาอัตราส่วนเดิม จะลดขนาดรูปภาพเมื่อมีขนาดใหญ่กว่าที่ระบุ หรือขยายขนาดเมื่อมีขนาดเล็กกว่า
resize_to_fill resize_to_fill: [100, 100] ปรับขนาดรูปภาพให้เต็มพื้นที่ที่ระบุ โดยรักษาอัตราส่วนเดิม หากจำเป็นจะตัดรูปภาพในมิติที่ใหญ่กว่า
resize_and_pad resize_and_pad: [100, 100] ปรับขนาดรูปภาพให้พอดีกับขนาดที่ระบุ โดยรักษาอัตราส่วนเดิม หากจำเป็นจะเติมพื้นที่ที่เหลือด้วยสีโปร่งใสหากภาพต้นฉบับมีช่องทางอัลฟา หรือสีดำหากไม่มี
crop crop: [20, 50, 300, 300] สกัดพื้นที่จากรูปภาพ อาร์กิวเมนต์ 2 ตัวแรกคือขอบซ้ายและบนของพื้นที่ที่จะสกัด ในขณะที่อาร์กิวเมนต์ 2 ตัวสุดท้ายคือความกว้างและความสูงของพื้นที่ที่จะสกัด
rotate rotate: 90 หมุนภาพตามมุมที่ระบุ

image_processing มีตัวเลือกเพิ่มเติม (เช่น saver ซึ่งช่วยกำหนดการบีบอัดรูปภาพ) ในเอกสารของตัวประมวลผลแบร์เวียน Vips และ MiniMagick ได้เอง

8.3 การแสดงตัวอย่างไฟล์

บางไฟล์ที่ไม่ใช่รูปภาพสามารถแสดงตัวอย่างได้: กล่าวคือ สามารถนำเสนอเป็นรูปภาพได้ ตัวอย่างเช่น ไฟล์วิดีโอสามารถแสดงตัวอย่างได้โดยการแยกเฟรมแรกของมัน โดยอัตโนมัติ Active Storage สนับสนุนการแสดงตัวอย่างวิดีโอและเอกสาร PDF โดยอัตโนมัติ ในการสร้าง ลิงก์ไปยังตัวอย่างที่สร้างขึ้นแบบ lazy ให้ใช้เมธอด preview ของ attachment:

<%= image_tag message.video.preview(resize_to_limit: [100, 100]) %>

หากต้องการเพิ่มการสนับสนุนสำหรับรูปแบบอื่น ๆ ให้เพิ่มตัวแสดงตัวอย่างของคุณเอง ดู เอกสาร ActiveStorage::Preview เพื่อข้อมูลเพิ่มเติม

9 การอัปโหลดโดยตรง

Active Storage ร่วมกับไลบรารี JavaScript ที่มาพร้อมในการสนับสนุนการอัปโหลด โดยตรงจากไคลเอ็นต์ไปยังคลาวด์

9.1 วิธีการใช้งาน

  1. เพิ่ม activestorage.js ในแบนเดิล JavaScript ของแอปพลิเคชันของคุณ

    ใช้ asset pipeline:

    //= require activestorage
    

    ใช้ npm package:

    import * as ActiveStorage from "@rails/activestorage"
    ActiveStorage.start()
    
  2. เพิ่ม direct_upload: true ใน file field ของคุณ

    <%= form.file_field :attachments, multiple: true, direct_upload: true %>
    

    หรือหากคุณไม่ได้ใช้ FormBuilder ให้เพิ่มแอตทริบิวต์ข้อมูลโดยตรง:

    <input type="file" data-direct-upload-url="<%= rails_direct_uploads_url %>" />
    
  3. กำหนดค่า CORS บนบริการจัดเก็บข้อมูลจากบุคคลที่สามเพื่ออนุญาตให้สามารถส่งคำขออัปโหลดโดยตรงได้

  4. เสร็จสิ้น! การอัปโหลดจะเริ่มต้นเมื่อฟอร์มถูกส่ง

9.2 การกำหนดค่า Cross-Origin Resource Sharing (CORS)

เพื่อให้การอัปโหลดโดยตรงไปยังบริการจัดเก็บข้อมูลจากบุคคลที่สามทำงานได้ คุณจะต้องกำหนดค่าบริการเพื่ออนุญาตให้รับคำขอ Cross-Origin จากแอปของคุณ โปรดอ่านเอกสาร CORS สำหรับบริการของคุณ:

ให้แน่ใจว่าคุณอนุญาต:

  • ทุกต้นทางที่แอปของคุณถูกเข้าถึง
  • วิธีการคำขอ PUT
  • ส่วนหัวต่อไปนี้:
    • Origin
    • Content-Type
    • Content-MD5
    • Content-Disposition (ยกเว้นสำหรับ Azure Storage)
    • x-ms-blob-content-disposition (สำหรับ Azure Storage เท่านั้น)
    • x-ms-blob-type (สำหรับ Azure Storage เท่านั้น)
    • Cache-Control (สำหรับ GCS เท่านั้น หากตั้งค่า cache_control)

ไม่จำเป็นต้องกำหนดค่า CORS สำหรับ Disk service เนื่องจากมันใช้ origin ของแอปของคุณ

9.2.1 ตัวอย่าง: การกำหนดค่า CORS สำหรับ S3

[
  {
    "AllowedHeaders": [
      "*"
    ],
    "AllowedMethods": [
      "PUT"
    ],
    "AllowedOrigins": [
      "https://www.example.com"
    ],
    "ExposeHeaders": [
      "Origin",
      "Content-Type",
      "Content-MD5",
      "Content-Disposition"
    ],
    "MaxAgeSeconds": 3600
  }
]

9.2.2 ตัวอย่าง: การกำหนดค่า CORS สำหรับ Google Cloud Storage

[
  {
    "origin": ["https://www.example.com"],
    "method": ["PUT"],
    "responseHeader": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],
    "maxAgeSeconds": 3600
  }
]

9.2.3 ตัวอย่าง: การกำหนดค่า CORS สำหรับ Azure Storage

<Cors>
  <CorsRule>
    <AllowedOrigins>https://www.example.com</AllowedOrigins>
    <AllowedMethods>PUT</AllowedMethods>
    <AllowedHeaders>Origin, Content-Type, Content-MD5, x-ms-blob-content-disposition, x-ms-blob-type</AllowedHeaders>
    <MaxAgeInSeconds>3600</MaxAgeInSeconds>
  </CorsRule>
</Cors>

9.3 เหตุการณ์ JavaScript สำหรับการอัปโหลดโดยตรง

ชื่อเหตุการณ์ เป้าหมายเหตุการณ์ ข้อมูลเหตุการณ์ (event.detail) คำอธิบาย
direct-uploads:start <form> ไม่มี ฟอร์มที่มีไฟล์สำหรับฟิลด์อัปโหลดโดยตรงถูกส่ง
direct-upload:initialize <input> {id, file} ส่งออกสำหรับทุกไฟล์หลังจากการส่งฟอร์ม
direct-upload:start <input> {id, file} เริ่มการอัปโหลดโดยตรง
direct-upload:before-blob-request <input> {id, file, xhr} ก่อนที่จะทำคำขอไปยังแอปของคุณสำหรับข้อมูลอัปโหลดโดยตรง
direct-upload:before-storage-request <input> {id, file, xhr} ก่อนที่จะทำคำขอเพื่อเก็บไฟล์
direct-upload:progress <input> {id, file, progress} เมื่อคำขอเพื่อเก็บไฟล์กำลังดำเนินการ
direct-upload:error <input> {id, file, error} เกิดข้อผิดพลาด จะแสดง alert ถ้าไม่มีการยกเลิกเหตุการณ์นี้
direct-upload:end <input> {id, file} การอัปโหลดโดยตรงเสร็จสิ้น
direct-uploads:end <form> ไม่มี การอัปโหลดโดยตรงทั้งหมดเสร็จสิ้น

9.4 ตัวอย่าง

คุณสามารถใช้เหตุการณ์เหล่านี้เพื่อแสดงความคืบหน้าของการอัปโหลด

direct-uploads

เพื่อแสดงไฟล์ที่อัปโหลดในฟอร์ม:

// direct_uploads.js

addEventListener("direct-upload:initialize", event => {
  const { target, detail } = event
  const { id, file } = detail
  target.insertAdjacentHTML("beforebegin", `
    <div id="direct-upload-${id}" class="direct-upload direct-upload--pending">
      <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div>
      <span class="direct-upload__filename"></span>
    </div>
  `)
  target.previousElementSibling.querySelector(`.direct-upload__filename`).textContent = file.name
})

addEventListener("direct-upload:start", event => {
  const { id } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.remove("direct-upload--pending")
})

addEventListener("direct-upload:progress", event => {
  const { id, progress } = event.detail
  const progressElement = document.getElementById(`direct-upload-progress-${id}`)
  progressElement.style.width = `${progress}%`
})

addEventListener("direct-upload:error", event => {
  event.preventDefault()
  const { id, error } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.add("direct-upload--error")
  element.setAttribute("title", error)
})

addEventListener("direct-upload:end", event => {
  const { id } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.add("direct-upload--complete")
})

เพิ่มสไตล์:

/* direct_uploads.css */

.direct-upload {
  display: inline-block;
  position: relative;
  padding: 2px 4px;
  margin: 0 3px 3px 0;
  border: 1px solid rgba(0, 0, 0, 0.3);
  border-radius: 3px;
  font-size: 11px;
  line-height: 13px;
}

.direct-upload--pending {
  opacity: 0.6;
}

.direct-upload__progress {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  opacity: 0.2;
  background: #0076ff;
  transition: width 120ms ease-out, opacity 60ms 60ms ease-in;
  transform: translate3d(0, 0, 0);
}

.direct-upload--complete .direct-upload__progress {
  opacity: 0.4;
}

.direct-upload--error {
  border-color: red;
}

input[type=file][data-direct-upload-url][disabled] {
  display: none;
}

9.5 การใช้งาน Drag and Drop ที่กำหนดเอง

คุณสามารถใช้คลาส DirectUpload เพื่อวัตถุประสงค์นี้ โดยเมื่อได้รับไฟล์จากไลบรารีที่คุณเลือก ให้สร้างอินสแตนซ์ของ DirectUpload และเรียกใช้เมธอด create ของมัน ซึ่ง create จะรับค่า callback เพื่อเรียกใช้เมื่อการอัปโหลดเสร็จสิ้น

import { DirectUpload } from "@rails/activestorage"

const input = document.querySelector('input[type=file]')

// ผูกกับการลากและวางไฟล์ - ใช้ ondrop บนองค์ประกอบหลักหรือใช้
// ไลบรารีเช่น Dropzone
const onDrop = (event) => {
  event.preventDefault()
  const files = event.dataTransfer.files;
  Array.from(files).forEach(file => uploadFile(file))
}

// ผูกกับการเลือกไฟล์ปกติ
input.addEventListener('change', (event) => {
  Array.from(input.files).forEach(file => uploadFile(file))
  // คุณอาจล้างไฟล์ที่เลือกจากอินพุต
  input.value = null
})

const uploadFile = (file) => {
  // แบบฟอร์มของคุณต้องมี file_field direct_upload: true ซึ่ง
  // จะให้ข้อมูล data-direct-upload-url
  const url = input.dataset.directUploadUrl
  const upload = new DirectUpload(file, url)

  upload.create((error, blob) => {
    if (error) {
      // จัดการข้อผิดพลาด
    } else {
      // เพิ่มอินพุตที่ซ่อนอยู่ในแบบฟอร์มด้วยชื่อที่เหมาะสม
      // และมีค่าเป็น blob.signed_id เพื่อให้ไอดีของ blob ถูกส่งผ่านกระแสการอัปโหลดปกติ
      const hiddenField = document.createElement('input')
      hiddenField.setAttribute("type", "hidden");
      hiddenField.setAttribute("value", blob.signed_id);
      hiddenField.name = input.name
      document.querySelector('form').appendChild(hiddenField)
    }
  })
}

9.6 ติดตามความคืบหน้าของการอัปโหลดไฟล์

เมื่อใช้คอนสตรักเตอร์ DirectUpload คุณสามารถรวมพารามิเตอร์ที่สามเข้าไปได้ ซึ่งจะช่วยให้วัตถุ DirectUpload เรียกใช้เมธอด directUploadWillStoreFileWithXHR ระหว่างกระบวนการอัปโหลด จากนั้นคุณสามารถแนบตัวจัดการความคืบหน้าของคุณเองกับ XHR เพื่อตอบสนองตามความต้องการของคุณ

import { DirectUpload } from "@rails/activestorage"

class Uploader {
  constructor(file, url) {
    this.upload = new DirectUpload(this.file, this.url, this)
  }

  upload(file) {
    this.upload.create((error, blob) => {
      if (error) {
        // จัดการข้อผิดพลาด
      } else {
        // เพิ่มอินพุตที่ซ่อนอยู่ในแบบฟอร์ม
        // โดยมีค่าเป็น blob.signed_id
      }
    })
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener("progress",
      event => this.directUploadDidProgress(event))
  }

  directUploadDidProgress(event) {
    // ใช้ event.loaded และ event.total เพื่ออัปเดตแถบความคืบหน้า
  }
}

9.7 การผสานรวมกับไลบรารีหรือเฟรมเวิร์ก

เมื่อได้รับไฟล์จากไลบรารีที่คุณเลือก คุณต้องสร้างอินสแตนซ์ DirectUpload และใช้เมธอด "create" เพื่อเริ่มกระบวนการอัปโหลด โดยเพิ่มส่วนหัวเพิ่มเติมที่จำเป็นตามที่ต้องการ "create" ยังต้องการฟังก์ชัน callback เพื่อให้ทำงานเมื่อการอัปโหลดเสร็จสิ้น

import { DirectUpload } from "@rails/activestorage"

class Uploader {
  constructor(file, url, token) {
    const headers = { 'Authentication': `Bearer ${token}` }
    // ข้อมูล: การส่งส่วนหัวเป็นพารามิเตอร์ที่ไม่บังคับ หากคุณเลือกไม่ส่งส่วนหัว
    // การรับรองตนเองจะถูกดำเนินการโดยใช้คุกกี้หรือข้อมูลเซสชัน
    this.upload = new DirectUpload(this.file, this.url, this, headers)
  }

  upload(file) {
    this.upload.create((error, blob) => {
      if (error) {
        // จัดการข้อผิดพลาด
      } else {
        // ใช้ blob.signed_id เป็นการอ้างอิงไฟล์ในคำขอถัดไป
      }
    })
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener("progress",
      event => this.directUploadDidProgress(event))
  }

  directUploadDidProgress(event) {
    // ใช้ event.loaded และ event.total เพื่ออัปเดตแถบความคืบหน้า
  }
}

ในการนำเอาการรับรองความถูกต้องที่กำหนดเองมาใช้งาน จะต้องสร้างคอนโทรลเลอร์ใหม่ในแอปพลิเคชัน Rails ที่คล้ายกับตัวอย่างด้านล่าง:

class DirectUploadsController < ActiveStorage::DirectUploadsController
  skip_forgery_protection
  before_action :authenticate!

  def authenticate!
    @token = request.headers['Authorization']&.split&.last

    return head :unauthorized unless valid_token?(@token)
  end
end

หมายเหตุ: การใช้งาน Direct Uploads อาจทำให้ไฟล์อัปโหลดได้ แต่ไม่ได้แนบไปยังเร็คคอร์ด ควรพิจารณาใช้ purging unattached uploads

10 การทดสอบ

ใช้ fixture_file_upload เพื่อทดสอบการอัปโหลดไฟล์ในการทดสอบการรวมกันหรือการทดสอบคอนโทรลเลอร์ Rails จัดการไฟล์เหมือนกับพารามิเตอร์อื่น ๆ

class SignupController < ActionDispatch::IntegrationTest
  test "can sign up" do
    post signup_path, params: {
      name: "David",
      avatar: fixture_file_upload("david.png", "image/png")
    }

    user = User.order(:created_at).last
    assert user.avatar.attached?
  end
end

10.1 การลบไฟล์ที่สร้างขึ้นในระหว่างการทดสอบ

10.1.1 การทดสอบระบบ

การทดสอบระบบจะล้างข้อมูลการทดสอบโดยการย้อนกลับการทำธุรกรรม โดยเรียกใช้ destroy ไม่เคยถูกเรียกใช้กับอ็อบเจกต์ ไฟล์ที่แนบมาจึงไม่ถูกล้าง หากต้องการล้างไฟล์ สามารถทำได้ใน callback after_teardown การทำเช่นนี้จะทำให้แน่ใจว่าการเชื่อมต่อที่สร้างขึ้นในระหว่างการทดสอบเสร็จสมบูรณ์และคุณจะไม่ได้รับข้อผิดพลาดจาก Active Storage ที่บอกว่าไม่พบไฟล์

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # ...
  def after_teardown
    super
    FileUtils.rm_rf(ActiveStorage::Blob.service.root)
  end
  # ...
end

หากคุณกำลังใช้ parallel tests และ DiskService คุณควรกำหนดค่าให้แต่ละกระบวนการใช้โฟลเดอร์ของตัวเองสำหรับ Active Storage นี้จะทำให้ callback teardown ลบไฟล์เฉพาะจากกระบวนการทดสอบที่เกี่ยวข้อง

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # ...
  parallelize_setup do |i|
    ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
  end
  # ...
end

หากการทดสอบระบบของคุณยืนยันการลบโมเดลที่มีการแนบและคุณกำลังใช้งาน Active Job ตั้งค่าสภาพแวดล้อมการทดสอบของคุณให้ใช้ตัวอ่อนแทนคิวเพื่อให้งานการล้างถูกดำเนินการทันทีแทนที่จะเกิดขึ้นในอนาคต

# Use inline job processing to make things happen immediately
config.active_job.queue_adapter = :inline

10.1.2 การทดสอบการรวมกัน

เช่นเดียวกับการทดสอบระบบ ไฟล์ที่อัปโหลดในการทดสอบการรวมกันจะไม่ถูกล้างโดยอัตโนมัติ หากต้องการล้างไฟล์ สามารถทำได้ใน callback teardown

class ActionDispatch::IntegrationTest
  def after_teardown
    super
    FileUtils.rm_rf(ActiveStorage::Blob.service.root)
  end
end

หากคุณกำลังใช้ parallel tests และ Disk service คุณควรกำหนดค่าให้แต่ละกระบวนการใช้โฟลเดอร์ของตัวเองสำหรับ Active Storage นี้จะทำให้ callback teardown ลบไฟล์เฉพาะจากกระบวนการทดสอบที่เกี่ยวข้อง

class ActionDispatch::IntegrationTest
  parallelize_setup do |i|
    ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
  end
end

10.2 เพิ่มการแนบไฟล์ใน Fixture

คุณสามารถเพิ่มการแนบไฟล์ใน fixtures ที่มีอยู่ของคุณได้ ก่อนอื่นคุณควรสร้างบริการเก็บข้อมูลแยกต่างหาก:

# config/storage.yml

test_fixtures:
  service: Disk
  root: <%= Rails.root.join("tmp/storage_fixtures") %>

สิ่งนี้จะบอก Active Storage ว่าจะ "อัปโหลด" ไฟล์ Fixture ไปที่ไหน ดังนั้นควรเป็นไดเรกทอรีชั่วคราว โดยทำให้เป็นไดเรกทอรีที่แตกต่างจากบริการ test ปกติของคุณ เพื่อแยกไฟล์ Fixture จากไฟล์ที่อัปโหลดขึ้นในระหว่างการทดสอบ

ต่อไปให้สร้างไฟล์ Fixture สำหรับคลาส Active Storage:

# active_storage/attachments.yml
david_avatar:
  name: avatar
  record: david (User)
  blob: david_avatar_blob
# active_storage/blobs.yml
david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>

จากนั้นให้วางไฟล์ในไดเรกทอรี fixtures ของคุณ (path เริ่มต้นคือ test/fixtures/files) โดยใช้ชื่อไฟล์ที่ตรงกัน ดูเอกสาร ActiveStorage::FixtureSet เพื่อข้อมูลเพิ่มเติม

เมื่อทุกอย่างตั้งค่าเรียบร้อยแล้ว คุณจะสามารถเข้าถึงไฟล์แนบในการทดสอบของคุณได้:

class UserTest < ActiveSupport::TestCase
  def test_avatar
    avatar = users(:david).avatar

    assert avatar.attached?
    assert_not_nil avatar.download
    assert_equal 1000, avatar.byte_size
  end
end

10.2.1 การทำความสะอาด Fixtures

ขณะที่ไฟล์ที่อัปโหลดในการทดสอบจะถูกทำความสะอาด ที่สิ้นสุดของแต่ละทดสอบ, คุณต้องทำความสะอาดไฟล์ fixtures เพียงครั้งเดียว: เมื่อทุกทดสอบเสร็จสมบูรณ์

หากคุณใช้การทดสอบแบบ parallel ให้เรียกใช้ parallelize_teardown:

class ActiveSupport::TestCase
  # ...
  parallelize_teardown do |i|
    FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
  end
  # ...
end

หากคุณไม่ใช้การทดสอบแบบ parallel ให้ใช้ Minitest.after_run หรือตัวเลือกที่เหมาะสมสำหรับ framework ทดสอบของคุณ (เช่น after(:suite) สำหรับ RSpec):

# test_helper.rb

Minitest.after_run do
  FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end

10.3 การกำหนดค่า services

คุณสามารถเพิ่ม config/storage/test.yml เพื่อกำหนดค่า services ที่จะใช้ในสภาพแวดล้อมการทดสอบ สิ่งนี้เป็นประโยชน์เมื่อใช้ตัวเลือก service.

class User < ApplicationRecord
  has_one_attached :avatar, service: :s3
end

โดยไม่มี config/storage/test.yml, จะใช้ s3 service ที่กำหนดค่าใน config/storage.yml - แม้ว่าจะเป็นการรันการทดสอบ

การกำหนดค่าเริ่มต้นจะถูกใช้และไฟล์จะถูกอัปโหลดไปยังผู้ให้บริการที่กำหนดค่าใน config/storage.yml

ในกรณีนี้ คุณสามารถเพิ่ม config/storage/test.yml และใช้ Disk service สำหรับ s3 service เพื่อป้องกันการส่งคำขอ

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

s3:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

11 การสนับสนุนบริการคลาวด์อื่น

หากคุณต้องการสนับสนุนบริการคลาวด์อื่นนอกเหนือจากนี้ คุณจะต้อง สร้าง Service เอง แต่ละ Service จะสืบทอดมาจาก ActiveStorage::Service โดยการสร้างเมธอดที่จำเป็นในการอัปโหลดและดาวน์โหลดไฟล์ไปยังคลาวด์

12 การล้างอัปโหลดที่ไม่ได้แนบ

มีกรณีที่ไฟล์ถูกอัปโหลดแต่ไม่ได้แนบกับเรคคอร์ด สามารถเกิดขึ้นได้เมื่อใช้ การอัปโหลดโดยตรง คุณสามารถค้นหาเรคคอร์ดที่ไม่ได้แนบโดยใช้ unattached scope ดังตัวอย่างด้านล่างที่ใช้ custom rake task.

namespace :active_storage do
  desc "Purges unattached Active Storage blobs. Run regularly."
  task purge_unattached: :environment do
    ActiveStorage::Blob.unattached.where(created_at: ..2.days.ago).find_each(&:purge_later)
  end
end

คำเตือน: คิวรี่ที่สร้างขึ้นโดย ActiveStorage::Blob.unattached อาจช้าและอาจสร้างความไม่สงบในแอปพลิเคชันที่มีฐานข้อมูลขนาดใหญ่กว่านี้

ข้อเสนอแนะ

คุณสามารถช่วยปรับปรุงคุณภาพของคู่มือนี้ได้

กรุณาช่วยเพิ่มเติมหากพบข้อผิดพลาดหรือข้อผิดพลาดทางความจริง เพื่อเริ่มต้นคุณสามารถอ่านส่วน การสนับสนุนเอกสาร ของเราได้

คุณอาจพบเนื้อหาที่ไม่สมบูรณ์หรือเนื้อหาที่ไม่ได้อัปเดต กรุณาเพิ่มเอกสารที่ขาดหายไปสำหรับเนื้อหาหลัก โปรดตรวจสอบ Edge Guides ก่อนเพื่อตรวจสอบ ว่าปัญหาได้รับการแก้ไขหรือไม่ในสาขาหลัก ตรวจสอบ คู่มือแนวทาง Ruby on Rails เพื่อดูรูปแบบและกฎเกณฑ์

หากคุณพบข้อผิดพลาดแต่ไม่สามารถแก้ไขได้เอง กรุณา เปิดปัญหา.

และสุดท้าย การสนทนาใด ๆ เกี่ยวกับ Ruby on Rails เอกสารยินดีต้อนรับที่สุดใน เว็บบอร์ดอย่างเป็นทางการของ Ruby on Rails.