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

อัปโหลดไฟล์ใน Rails ด้วย CarrierWave: คู่มือฉบับสมบูรณ์

บทความนี้เป็นส่วนหนึ่งของชุดบทความ "Uploading with Rails" วันนี้เราจะมาทำความรู้จัก CarrierWave หนึ่งในโซลูชันอัปโหลดไฟล์ที่นิยมที่สุดสำหรับ Rails ผมชอบ CarrierWave เพราะเริ่มต้นใช้งานง่าย มีฟีเจอร์มากมายพร้อมใช้งานทันที และมีบทความ "how-to" จากชุมชนมากมาย ทำให้คุณไม่หลงทาง

ในบทความนี้ คุณจะได้เรียนรู้วิธี:

  • ผสานรวม CarrierWave เข้ากับแอป Rails
  • เพิ่มการตรวจสอบความถูกต้อง (Validations)
  • เก็บไฟล์ไว้ข้ามคำขอ (Persist files across requests)
  • ลบไฟล์
  • สร้างภาพขนาดย่อ (Thumbnails)
  • อัปโหลดไฟล์จากที่ตั้งระยะไกล (Remote URLs)
  • รองรับการอัปโหลดหลายไฟล์ (Multiple file uploads)
  • เพิ่มการรองรับที่เก็บข้อมูลบนคลาวด์ (Cloud storage)

ซอร์สโค้ดสำหรับบทความนี้พร้อมใช้งานบน GitHub อ่านให้สนุกนะครับ!

สร้างโครงพื้นฐาน

เริ่มต้นด้วยการสร้างแอปพลิเคชัน Rails ใหม่:

สำหรับเดโมนี้จะใช้ Rails 5.0.2 โปรดทราบว่า CarrierWave 1 รองรับเฉพาะ Rails 4+ และ Ruby 2 เท่านั้น หากยังใช้ Rails 3 ให้ใช้ CarrierWave เวอร์ชัน 0.11 แทน

เพื่อดู CarrierWave ทำงาน เราจะสร้างบล็อกง่ายๆ ที่มีโมเดล Post เพียงตัวเดียว โดยมีแอตทริบิวต์หลักดังนี้:

  • title (string)
  • body (text)
  • image (string) — ฟิลด์นี้จะเก็บชื่อไฟล์รูปภาพที่ผูกกับโพสต์

สร้างและรันไมเกรชัน:

ตั้งค่าเส้นทาง (Routes):

config/routes.rb

สร้างคอนโทรลเลอร์พื้นฐาน:

posts_controller.rb

สร้างวิว index:

views/posts/index.html.erb

และพาร์เชียลที่เกี่ยวข้อง:

views/posts/_post.html.erb

ที่นี่ใช้เมธอด truncate ของ Rails เพื่อแสดงแค่ 150 ตัวอักษรแรกของโพสต์ ก่อนที่จะสร้างวิวและพาร์เชียลฟอร์มอื่นๆ เรามาผสาน CarrierWave เข้ากับแอปพลิเคชันกันก่อน

ผสานรวม CarrierWave

เพิ่มเจมใหม่ลงใน Gemfile:

Gemfile

รัน:

CarrierWave เก็บการตั้งค่าไว้ใน uploaders ที่จะถูก include เข้าไปในโมเดล สร้างอัปโหลดเดอร์ด้วยคำสั่ง:

ตอนนี้ใน app/uploaders จะมีไฟล์ image_uploader.rb ซึ่งมีคอมเมนต์และตัวอย่างที่เป็นประโยชน์ เดโมนี้ใช้ ActiveRecord แต่ CarrierWave ยังรองรับ Mongoid, Sequel, และ DataMapper ด้วย

ต่อไป ให้ include หรือ mount อัปโหลดเดอร์นี้เข้าไปในโมเดล:

models/post.rb

อัปโหลดเดอร์มีการตั้งค่าเริ่มต้นที่เหมาะสมแล้ว แต่ขั้นต่ำเราต้องเลือกที่เก็บไฟล์ที่อัปโหลด ใช้ file storage:

uploaders/image_uploader.rb

โดยค่าเริ่มต้น ไฟล์จะถูกวางในโฟลเดอร์ public/uploads จึงควรเพิ่มเข้า .gitignore:

.gitignore

คุณยังสามารถแก้ไขเมธอด store_dir ภายในอัปโหลดเดอร์เพื่อเลือกที่ตั้งอื่นได้

ตอนนี้เราสามารถสร้างวิวใหม่และพาร์เชียลฟอร์มเพื่อเริ่มอัปโหลดไฟล์ได้:

views/posts/new.html.erb

views/posts/_form.html.erb

สังเกตว่า PostsController ไม่ต้องแก้ไข เพราะเราอนุญาตแอตทริบิวต์ image ไว้แล้ว

สุดท้าย สร้างวิว edit:

views/posts/edit.html.erb

เสร็จแล้ว! ลองบูทเซิร์ฟเวอร์และสร้างโพสต์พร้อมรูปภาพได้เลย ปัญหาคือรูปภาพไม่แสดงที่ไหน มาดูส่วนต่อไปเพื่อเพิ่มหน้า show!

แสดงรูปภาพ

วิวที่ยังไม่สร้างคือ show มาเพิ่มกัน:

views/posts/show.html.erb

การแสดงไฟล์แนบง่ายมาก: ใช้ @post.image.url เพื่อดึง URL ของรูปภาพ สำหรับพาธของไฟล์ ใช้เมธอด current_path CarrierWave ยังมีเมธอด image? สำหรับตรวจสอบว่ามีไฟล์แนบหรือไม่ (เมธอด image จะไม่คืนค่า nil แม้ไม่มีไฟล์)

ตอนนี้ลองเข้าไปดูโพสต์ จะเห็นรูปภาพ แต่อาจดูใหญ่เกินไป เพราะเราไม่ได้จำกัดขนาด แม้จะใช้ CSS ย่อได้ แต่วิธีที่ดีกว่าคือสร้างภาพขนาดย่อหลังอัปโหลด ซึ่งต้องทำเพิ่มเติม

สร้างภาพขนาดย่อ (Thumbnails)

เพื่อครอปและปรับขนาดรูปภาพ ต้องใช้เครื่องมือแยกต่างหาก CarrierWave รองรับ RMagick และ MiniMagick ซึ่งใช้ ImageMagick ในการจัดการรูปภาพ ImageMagick เป็นโอเพนซอร์สสำหรับแก้ไขและสร้างรูปภาพ ต้องดาวน์โหลดและติดตั้งก่อน จะเลือกเจมไหนก็ได้ ผมเลือก MiniMagick เพราะติดตั้งง่ายและรองรับดีกว่า:

Gemfile

รัน:

include MiniMagick เข้าอัปโหลดเดอร์:

uploaders/image_uploader.rb

เพิ่ม version ใหม่ชื่อ thumb:

uploaders/image_uploader.rb

สามารถมี version ได้หลายตัว และ version สามารถสร้างจาก version อื่นได้:

uploaders/image_uploader.rb

หากอัปโหลดรูปไปแล้ว ไม่มี thumbnail ให้สร้างใหม่จาก Rails console:

แสดง thumbnail พร้อมลิงก์ไปรูปต้นฉบับ:

views/posts/show.html.erb

บูทเซิร์ฟเวอร์และดูผลลัพธ์!

เพิ่มการตรวจสอบความถูกต้อง (Validations)

ตอนนี้อัปโหลดทำงานได้ แต่ยังไม่ตรวจสอบข้อมูลนำเข้า ซึ่งไม่ดี ถ้าต้องการเฉพาะรูปภาพ ให้ whitelist นามสกุล .png, .jpg, .gif:

uploaders/image_uploader.rb

ยังสามารถตรวจสอบ content type ด้วย content_type_whitelist:

uploaders/image_uploader.rb

หรือ blacklist ไฟล์ที่ไม่ต้องการ เช่น executable ด้วย content_type_blacklist

นอกจากนี้ ให้จำกัดขนาดไฟล์ไม่เกิน 1 เมกะไบต์ ต้องใช้เจม file_validators:

Gemfile

ติดตั้ง:

เพิ่ม validation ในโมเดล (รวม title และ body ด้วย):

models/post.rb

เพิ่มการแปลข้อความ error ของ CarrierWave:

config/locales/en.yml

ยังไม่แสดง error ใดๆ มาสร้าง shared partial:

views/shared/_errors.html.erb

ใช้ partial นี้ในฟอร์ม:

views/posts/_form.html.erb

ลองอัปโหลดไฟล์ไม่ถูกต้อง จะเห็น error แต่ถ้าเลือกไฟล์ถูกต้องแต่ไม่กรอก title หรือ body จะ error และช่องไฟล์จะว่าง ต้องเลือกใหม่ ไม่สะดวก ต้องเพิ่มฟิลด์ hidden

เก็บไฟล์ไว้ข้ามคำขอ (Persisting Files Across Requests)

ง่ายมาก: เพิ่ม hidden field และ permit ในคอนโทรลเลอร์:

views/shared/_form.html.erb

posts_controller.rb

ตอนนี้ image_cache จะถูกเติมอัตโนมัติ และรูปภาพไม่หายไป อาจแสดง thumbnail ให้ผู้ใช้เห็นว่าประมวลผลสำเร็จ:

views/shared/_form.html.erb

ลบรูปภาพ

ฟีเจอร์ทั่วไปคือลบไฟล์แนบเมื่อแก้ไขเรคคอร์ด CarrierWave ทำได้ง่าย เพิ่ม checkbox ในฟอร์ม:

views/shared/_form.html.erb

Permit remove_image:

posts_controller.rb

เสร็จแล้ว! ลบรูปด้วยตนเองใช้ remove_image!:

อัปโหลดจากที่ตั้งระยะไกล (Remote Location)

CarrierWave มีฟีเจอร์อัปโหลดจาก URL มาให้ เพิ่มฟิลด์และ permit attribute:

views/shared/_form.html.erb

posts_controller.rb

สบายมาก! ไม่ต้องแก้โค้ดเพิ่ม ทดสอบได้เลย

ทำงานกับการอัปโหลดหลายไฟล์ (Multiple Uploads)

ถ้าต้องการให้โพสต์มีหลายไฟล์แนบ ต้องเพิ่มฟิลด์ serialized (SQLite) หรือ JSON (Postgres/MySQL) ผมชอบ JSON เลยเปลี่ยน database adapter:

ลบ sqlite3 ออกจาก Gemfile เพิ่ม pg:

Gemfile

ติดตั้ง:

แก้ config/database.yml:

config/database.yml

สร้าง database Postgres แล้วสร้างและรันไมเกรชัน:

ถ้าอยากใช้ SQLite ตามคู่มือ CarrierWave

Mount uploaders (สังเกต plural):

model/post.rb

ใช้ uploader เดียวกัน แต่สร้างใหม่ก็ได้

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

views/shared/_form.html.erb

Permit attachments เป็น array:

posts_controller.rb

แสดง attachments โดยวนลูป:

views/shared/show.html.erb

แต่ละ attachment จะมี thumbnail ตามที่ตั้งค่าใน ImageUploader เยี่ยม!

ใช้ที่เก็บข้อมูลบนคลาวด์ (Cloud Storage)

File storage ไม่สะดวกบน Heroku เพราะไม่เก็บไฟล์ได้ CarrierWave รองรับ Amazon S3 ผ่าน fog-aws:

Gemfile

ติดตั้ง:

สร้าง initializer ตั้งค่า cloud storage ทั่วไป:

config/initializers/carrierwave.rb

มีตัวเลือกอื่นๆ ในเอกสาร ผมใช้ dotenv-rails สำหรับ environment variables อย่าให้ S3 key pair เปิดเผยต่อสาธารณะ!

เปลี่ยน storage :file เป็น:

uploaders/image_uploader.rb

นอกจาก S3 ยังรองรับ Google Storage และ Rackspace ตั้งค่าได้ง่ายเช่นกัน

สรุป

จบแล้วครับ! เราครอบคลุมฟีเจอร์หลักของ CarrierWave แล้ว ตอนนี้คุณสามารถนำไปใช้ในโปรเจกต์ได้ ยังมีตัวเลือกเพิ่มเติม อ่านเอกสารต่อได้ หากคิดคิดไม่ออก ถามได้เลย CarrierWave wiki ก็มี how-to มากมาย ขอบคุณที่อ่าน Happy coding!