Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

อัปโหลดไฟล์ใน Rails อย่างง่ายดายด้วย Shrine: โซลูชันโมดูลาร์สมัยใหม่

มีเจมอัปโหลดไฟล์เยอะมาก เช่น CarrierWave, Paperclip, Dragonfly ทุกตัวมีความเฉพาะตัว คุณน่าจะเคยใช้อย่างน้อยหนึ่งตัวแล้ว

วันนี้จะแนะนำโซลูชันใหม่แต่กนกกินชื่อ Shrine สร้างโดย Janko Marohnić ต่างจากเจมอื่นที่มันมีแนวทาง โมดูลาร์ (modular) หมายถึงทุกฟีเจอร์บรรจุเป็นโมดูล (หรือ plugin ในศัพท์ Shrine) อยาก validation? 加 plugin อยากประมวลผลไฟล์? 加 plugin! ผมชอบแนวทางนี้มาก เพราะควบคุมฟีเจอร์ที่เปิดใช้งานต่อโมเดลได้ง่าย

บทความนี้จะสอน:

  • ผสาน Shrine เข้าแอป Rails
  • ตั้งค่า (global และ per-uploader)
  • เพิ่มความสามารถอัปโหลดไฟล์
  • ประมวลผลไฟล์ (processing)
  • เพิ่มกฎ validation
  • เก็บ metadata เพิ่มเติม และใช้ cloud storage กับ Amazon S3

ซอร์สโค้ดอยู่ที่ GitHub มี working demo ด้วย

ผสานรวม Shrine

สร้างแอป Rails ใหม่โดยไม่มี test suite เริ่มต้น:

ใช้ Rails 5 สำหรับเดโม แนวคิดส่วนใหญ่ใช้กับ v3, v4 ได้

เพิ่ม Shrine gem ใน Gemfile:

ติดตั้ง:

ต้องการโมเดลชื่อ Photo Shrine เก็บข้อมูลไฟล์ในคอลัมน์ text ที่ลงท้าย _data สร้างและรัน migration:

สำหรับ Rails เก่า ใช้ rake db:migrate

ตั้งค่า Shrine ทั้ง global และ per-model Global settings ทำใน initializer file hook ไฟล์และ plugins ที่จำเป็น Plugins ใช้แยก functionality เป็นโมดูลแยก ควบคุมฟีเจอร์ทั้งหมดได้ เช่น plugins สำหรับ validation, image processing, caching attachments

ตอนนี้เพิ่ม 2 plugins: รองรับ ActiveRecord และ logging รวม global ตั้งค่า file system storage:

config/initializers/shrine.rb

Logger จะ log debug info ใน console บอกเวลาที่ใช้ประมวลผลไฟล์ มีประโยชน์:

ไฟล์อัปโหลดเก็บใน public/uploads ไม่อยาก track ใน Git ให้ ignore:

.gitignore

สร้าง "uploader" class สำหรับ model-specific settings ตอนนี้ว่างเปล่า:

models/image_uploader.rb

Include ในโมเดล Photo:

models/photo.rb

[:image] เพิ่ม virtual attribute ใช้ตอนสร้างฟอร์ม บรรทัดนี้เทียบเท่า:

ตอนนี้โมเดลพร้อมใช้งาน Shrine ไปต่อ!

Controller, Views, และ Routes

สำหรับเดโม ต้องการ controller เดียวจัดการ photos index page เป็น root:

pages_controller.rb

View:

views/photos/index.html.erb

ต้องมี partial render @photos array:

views/photos/_photo.html.erb

image_data? เป็นเมธอดของ ActiveRecord ตรวจสอบว่าเรคคอร์ดมีรูปหรือไม่

image_url เมธอด Shrine คืนพาธไปรูปต้นฉบับ แต่น่าจะแสดง thumbnail ดีกว่า จะทำในภายหลัง

เพิ่ม routes:

config/routes.rb

พื้นฐานเสร็จ ไปส่วนสนุก!

อัปโหลดไฟล์

เพิ่มฟังก์ชันอัปโหลดจริง Controller actions เรียบง่าย:

photos_controller.rb

จุดสำคัญ: strong parameters ต้อง permit image virtual attribute ไม่ใช่ image_data

photos_controller.rb

สร้าง new view:

views/photos/new.html.erb

Form partial:

views/photos/_form.html.erb

อีกครั้ง ใช้ image attribute ไม่ใช่ image_data

เพิ่ม partial แสดง errors:

views/shared/_errors.html.erb

ตอนนี้อัปโหลดรูปได้แล้ว

Validations

ยังต้องทำอีกเยอะ ผู้ใช้อาจอัปโหลดไฟล์ประเภทไหนก็ได้ ขนาดไหนก็ได้ ไม่ดี เพิ่ม plugin validation:

config/initializers/shrine.rb

ตั้งค่า validation logic ใน ImageUploader:

models/image_uploader.rb

อนุญาตเฉพาะ JPG และ PNG น้อยกว่า 1MB ปรับกฎได้ตามใจ

MIME Types

โดยค่าเริ่มต้น Shrine กำหนด MIME type จาก Content-Type HTTP header ที่ browser ส่ง ตั้งค่าจาก extension ไฟล์อย่างเดียว อาจไม่เหมาะสม

อยากกำหนด MIME type จากเนื้อหาไฟล์ ใช้ plugin determine_mime_type include ใน uploader class (โมเดลอื่นอาจไม่ต้องการ):

models/image_uploader.rb

Plugin นี้ใช้ Linux file utility โดยค่าเริ่มต้น

Caching Attached Images

ตอนนี้ user ส่งฟอร์มข้อมูลผิด ฟอร์มแสดงใหม่พร้อม errors แตไฟล์แนบหาย ต้องเลือกใหม่ แก้ง่ายด้วย plugin cached_attachment_data:

models/image_uploader.rb

เพิ่ม hidden field ในฟอร์ม:

views/photos/_form.html.erb

แก้ไขรูปภาพ (Editing a Photo)

อัปโหลดได้แล้ว แต่แก้ไขไม่ได้ มาแก้ actions ใน controller:

photos_controller.rb

ใช้ _form partial เดียวกัน:

views/photos/edit.html.erb

ยังไม่พอ: user ลบรูปที่อัปโหลดไม่ได้ ต้องเพิ่ม plugin remove_attachment:

models/image_uploader.rb

ใช้ virtual attribute :remove_image permit ใน controller:

photos_controller.rb

แสดง checkbox ลบรูปถ้ามี attachment:

views/photos/_form.html.erb

สร้างภาพขนาดย่อ (Generating a Thumbnail Image)

ตอนนี้แสดงรูปต้นฉบับ ไม่ดีสำหรับ preview: รูปใหญ่佔พื้นที่ แม้ CSS width/height ก็ยังดาวน์โหลดไฟล์ต้นฉบับใหญ่ ดีกว่าสร้าง preview เล็กๆ บน server ตอนอัปโหลด ต้องใช้ 2 plugins และ 2 gems เพิ่ม:

image_processing สร้างโดยผู้เขียน Shrine ให้ high-level helpers manipulate รูป ใช้ mini_magick wrapper ของ ImageMagick ต้องติดตั้ง ImageMagick บนระบบ

ติดตั้ง gems:

Include plugins และ dependencies:

models/image_uploader.rb

processing plugin ให้ manipulate รูป (ย่อ, หมุน, convert format) versions plugin ให้มี variants หลายรูป เดโมนี้เก็บ 2 versions: "original" และ "thumb" (resize 300x300)

โค้ดประมวลผลและเก็บ 2 versions:

models/image_uploader.rb

resize_to_limit! จาก image_processing gem ย่อรูปเหลือ 300x300 ถ้าใหญ่กว่า ถ้าเล็กกว่าไม่ทำอะไร รักษาอัตราส่วนเดิม

ตอนแสดงรูป ใส่ argument :original หรือ :thumb ให้ image_url:

views/photos/_photo.html.erb

ในฟอร์มก็ทำได้:

views/photos/_form.html.erb

ลบไฟล์ processed อัตโนมัติหลังอัปโหลดเสร็จ ใช้ plugin delete_raw:

models/image_uploader.rb

Metadata ของรูปภาพ

นอกจากแสดงรูป ยังดึง metadata ได้ เช่น แสดงขนาดและ MIME type ของรูปต้นฉบับ:

views/photos/_photo.html.erb

ขนาดกว้าง-สูง (dimensions) ไม่เก็บโดยค่าเริ่มต้น แต่ทำได้ด้วย plugin store_dimensions

ขนาดรูปภาพ (Image's Dimensions)

Plugin store_dimensions ต้องใช้ fastimage gem:

ติดตั้ง:

Include plugin:

models/image_uploader.rb

แสดง dimensions ด้วยเมธอด width และ height:

views/photos/_photo.html.erb

ยังมีเมธอด dimensions คืน array [width, height] เช่น [500, 750]

ย้ายไปคลาวด์ (Moving to the Cloud)

นักพัฒนาบ่อยครั้งเลือก cloud storage สำหรับไฟล์อัปโหลด Shrine รองรับ Amazon S3

เพิ่ม gems ใน Gemfile:

aws-sdk สำหรับ S3 SDK dotenv-rails จัดการ environment variables ใน development

ก่อนต่อ ต้องมี key pair เข้า S3 ผ่าน API สมัคร/ล็อกอิน AWS Console ไป Security Credentials > Users สร้าง user มี permission จัดการไฟล์ S3 Policy ให้ full access:

ดาวน์โหลด key pair ของ user ที่สร้าง อย่าใช้ root access keys ไม่ปลอดภัย

สร้าง S3 bucket เก็บไฟล์ เพิ่มไฟล์ config ในรากโปรเจกต์:

.env

ห้ามเปิดเผย ไฟล์นี้สาธารณะ และ ignore ใน Git:

.gitignore

แก้ global config Shrine เพิ่ม storage ใหม่:

config/initializers/shrine.rb

เสร็จแล้ว! ไม่ต้องแก้ส่วนอื่น ทดสอบ storage ใหม่ได้เลย หาก error จาก S3 เรื่อง key ผิด ตรวจสอบ key/secret คัดลอกถูกต้อง ไม่มี space หรือ special chars ซ่อน

สรุป

จบบทความ หวังว่าคุณมั่นใจใช้ Shrine และอยากนำไปใช้ในโปรเจกต์ ยังมีฟีเจอร์เพิ่มเติม เช่น เก็บ context เพิ่มเติมกับไฟล์ และ direct upload mechanism

อ่านเอกสารและเว็บไซต์อย่างเป็นทางการของ Shrine รายละเอียด plugins ทั้งหมด ถ้ามีคำถาม ถามได้เลย ขอบคุณที่อ่าน เจอกันใหม่ قريب!