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

Validation ที่ Streamlined: จาก Draft ถึง Publish ใน Article Workflow

(ส่งให้ 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 เสมอ:

app/models/article.rb
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/ อ่านคุ้มค่า มาดูตัวอย่าง:

app/models/article.rb
class Article < ActiveRecord::Base # validate in draft mode validates_presence_of :author_id # validate only when published validates_presence_of :body, on: :publish ... end 
app/controllers/article_controller.rb
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) ผมชอบลุคนี้:

app/models/article.rb
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