เมื่อ error เกิดใน Rails application exception และ stack trace ช่วยหาว่า problem เกิดที่ไหน หลังรู้ว่าเกิดอะไรขึ้นที่ไหน เราต้องหาว่า ทำไม มันเกิด บทความนี้เราจะครอบคลุมการใช้ backtrace หา bug ใน Rails application
NoMethodError (undefined method `request_uri' for #):
app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'
ใน example exception นี้ เราได้ NoMethodError พร้อม message undefined method `request_uri' for # เนื่องจาก exception นี้ไม่บอกปัญหาทันที เราต้อง inspect stack trace หาว่าเกิดอะไรขึ้น
app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'
ดู stack trace เรารู้ว่า exception raised จาก method download_image! บน Product model เราจะสืบสวนในโค้ด และทำงานลง stack trace หาว่าอะไรผิด
เปิด model ดู line 8 (ที่ exception raised จาก) เรียก Net::HTTP.get(uri) ดูเหมือน uri ไม่ใช่ object ที่เราคาดหวัง
require 'net/http'
class Product < ApplicationRecord
after_save :download_image!
def download_image!
uri = URI(image_url)
contents = Net::HTTP.get(uri)
File.open("public#{local_image_path}", 'wb') do |file|
file.write contents
end
end
def local_image_path
"/product_#{id}.png"
end
end
เนื่องจาก download_image! เป็น after_save callback เรารู้ว่ามัน execute ทันทีหลัง save Product ใหม่
ตัวแปร uri สร้างจาก method ชื่อ image_url บน line 7 เพื่อหาว่ามาจากไหน เราดู stack trace อีกครั้ง ว่า Product#create เรียกจาก ProductsController#create
class ProductsController < ApplicationController
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: 'Product was successfully created.'
else
render :new
end
end
private
def product_params
params.require(:product).permit(:title, :description, :image_url, :price)
end
end
อ๋อ! ProductsController#create สร้าง product ใหม่ด้วย product_params ซึ่งรวม parameter :image_url ที่เราหา
เรารู้ว่า image_url attribute ใช้สร้าง broken URI หากเราทิ้ง image_url field ว่างเมื่อสร้าง product ใหม่ เราสามารถ reproduce ปัญหาได้สำเร็จ
ในกรณีนี้ การสร้าง URI ด้วย empty string เป็น value ทำให้ได้ URI::Generic object แทน URI::HTTP เพราะมัน determine URL format ไม่ได้ เนื่องจากตัวแรกไม่มี method #request_uri จึง raise NoMethodError จาก Net::HTTP.get
ขึ้นอยู่กับ requirements ของโปรเจกต์ การเพิ่ม validation ให้ field ไม่ว่าง อาจแก้ปัญหาได้ การ validate แค่ image URL ไม่ว่างไม่ได้แก้ปัญหาทั้งหมดของ implementation นี้ (ยังได้ exception เมื่อค่าที่ส่งไม่ใช่ URL) แต่เป็นจุดเริ่มที่ดี
Tracking Down Exceptions ด้วย Stack Trace
Rails logs ให้ debug issues ได้ดี แม้ raised exceptions ไม่ทำ sense ตรงๆ ตอนแรก แต่ careful retracting steps ที่ code ใช้ไปถึง issue มักเป็น great way หาว่าผิดที่ไหน แม้ source ของ problem จะฝังลึกใน app
เรายินดีรู้ความคิดเห็นว่าชอบบทความนี้หรือไม่ มีคำถามอะไร และอยากอ่านเรื่องไหนต่อไป บอกเราที่ @AppSignal ได้เลย