(ส่งให้ list ไปไม่กี่เดือน ถ้าชอบอยากอ่านแบบนี้ สมัครที่นี่!)
เจอสถานการณ์แปลกกับแอปหนึ่ง
มี Article model สร้างเป็น drafts ก่อน Drafts ควร lightweight สร้างและ edit ทันที ไม่ต้อง body หรือ title
แตตอน publish ต้อง validation เพิ่ม ต้องมี title, body และอื่นๆ
เขียน if statements เยอะ check เองได้ แตเสีย form handling ดีๆ จาก Rails และผมพบว่าไปกับ Rails สำหรับสิ่งที่ Rails จัดการดี จะ省心ภายหลัง ยังมีทางใช้ validations ไหม?
if และ unless?
Validations รองรับ :if และ :unless parameters แตไม่ได้ clean เสมอ:
class Article < ActiveRecord::Base # validate in draft mode validates_presence_of :author_id # validate only when published validates_presence_of :body, unless: lambda { |o| o.draft? } ... end นอกจากนี้ นี่ไม่ใช่วิธีที่ผมอยาก publish article ผมอยากคิด publish เป็น action ไม่ใช่ state ที่ article อยู่ ดังนั้น set และ check attribute ไม่ใช่คำตอบ
Rails Feature ที่นิยมน้อย เอกสารน้อย
ปัญหานี้ perfect สำหรับ Rails validation contexts ผมรู้ custom validation contexts จาก gist ของ DHH แตยากหาคำอธิบายดีว่ามันคืออะไร และใช้ยังไง
Documentation สำหรับ custom contexts น้อยมาก การ mention ใน API docs เจอแค่ที่นี่ และ API docs สำหรับ validations ไม่ mention custom contexts เลย! ทำให้ยากมาก ถ้าไม่รู้ว่าสิ่งนั้นชื่ออะไร หา documentation เพิ่มเติมได้ยังไง?
เจอ overview ดีที่นี่: https://blog.arkency.com/2014/04/mastering-rails-validations-contexts/ อ่านคุ้มค่า มาดูตัวอย่าง:
class Article < ActiveRecord::Base # validate in draft mode validates_presence_of :author_id # validate only when published validates_presence_of :body, on: :publish ... end class ArticleController < ApplicationController respond_to :html def create @article = Article.create(article_params) respond_with @article end def publish @article = Article.find(params[:id]) @article.attributes = article_params @article.save(context: :publish) respond_with @article end private def article_params params. require(:article). permit(:body, :title, :author_id) end end ไม่แย่เลย! สิ่งแปลกเดียวคือต้องใช้ save แทน update ใน publish method เพราะ update ไม่รับ validation context parameter (อาจเป็นโอกาส pull request?)
Beyond Saving
Custom validation contexts useful ไม่ได้แค่ save record แบบต่างๆ Contexts work กับ valid?:
@article.valid?(:publish) ใช้ validation contexts ที่ไหนก็ได้ ที่อยากตอบคำถาม: "Object นี้มี property นี้ไหม? ถ้าไม่ ทำไม?"
DHH สร้าง method ลงท้าย -able? delegate ไป valid?(:context) ผมชอบลุคนี้:
class Article validate :has_been_published, on: :view validate :is_not_spam, on: :view def viewable? valid? :view end private def has_been_published if published_at.future? errors.add(:published_at, "is in the future") end end def is_not_spam if is_spam?(body) errors.add(:body, "has been detected as spam") end end end ตอน check ได้ info มากกว่าแค่ true หรือ false:
unless @article.viewable? # @article.errors ตอนนี้ filled out แล้ว end Article viewable? ไม่ได้ ทำไม?
Just Lightweight Enough
มีวิธีอื่นได้ validation behavior แบบนี้ สร้าง custom ActiveModel service objects ด้วย validations ของตัวเอง ใช้ :if หรือ :unless บน validations แตเหมือนหลายอย่างใน Rails custom validation contexts เป็น compromise ดีระหว่าง readability และ flexibility