(คัดลอกมาจาก http://www.rails66.com/blog/?p=496)
เวลาเราพัฒนาโปรแกรมประยุกต์ภายใต้กรอบงานแบบ MVC โดยเฉพาะบน Rails บางทีเราจะพบว่า model ของเรานั่นว่างโล่ง (เพราะว่าทำหน้าที่เชื่อมกับ table อย่างเดียว) ส่วน controller เราเต็มไปด้วยตรรกซับซ้อนซ่อนเงื่อน จนทำให้นึกไปถึงสมัยก่อนที่เขียนโปรแกรมแบบไม่มีโครงสร้างและใช้ goto กันจนโปรแกรมพันกันเป็นเส้นก๋วยเตี๋ยว
พักหลัง ๆ เลยมีคนพยายามบอกว่า model ควรจะอ้วน ๆ แต่ controller ควรจะผอมเพรียว (ตัวอย่างเพิ่มเติม)
แล้วอะไรบ้างที่สามารถนำไปอยู่ในโมเดลได้? ที่ชัดที่สุดก็คือการตรวจสอบความถูกต้องของข้อมูล (validation)
จริง ๆ แล้วในตัวอย่างและหนังสือ Rails แทบจะทุกเล่มทุกอันก็จะแสดงให้เห็นว่าการตรวจสอบความถูกต้องของข้อมูลทำได้ง่ายมากในโมเดล แต่บางทีเวลาเรารีบ ๆ เขียนก็มักจะลืม ๆ ไป หรือบางทีเราอาจจะมีการตรวจสอบอะไรที่แปลกไปจากรูปแบบการตรวจสอบพื้นฐานที่ Rails มีให้ เราก็เลยไปเขียนเอาไว้ใน controller เสียเลย
เราจะยกตัวอย่างจากโมเดล Book ที่มี attribute เป็น title และ page นะครับ
ลองดูโปรแกรมของ controller ด้านล่างที่รับค่าจาก form ผ่านทางเมท็อด create นะครับ
def new
@book = Book.new
end
def create
@book = Book.new(params[:book])
if @book.title==""
flash[:notice] = "Error bad title"
render :action => 'new'
return
end
if @book.pages==""
flash[:notice] = "Error bad page number"
render :action => 'new'
return
end
if @book.title[0] ?Z
flash[:notice] = "Error title should begin with cap"
render :action => 'new'
return
end
@book.save
redirect_to :action => 'index'
end
Controller ด้านบนมีการตรวจสอบสามอย่างคือ title ต้องไม่ว่าง, page ต้องไม่ว่าง, และ title ต้องขึ้นต้นด้วยตัวอักษรพิมพ์ใหญ่
เมื่อตรวจสอบผ่านแล้วก็จะเก็บข้อมูลไป ถ้ามีข้อผิดพลาดก็จะกลับไปแสดง view new กลับมาเหมือนเดิม เพื่อให้หน้า view แสดง flash[:notice] เพื่อบอกกับผู้ใช้ว่ามีข้อผิดพลาดทำให้เก็บค่าไม่ได้ และให้ผู้ใช้ป้อนค่าเข้ามาใหม่
ระบบการตรวจสอบข้อมูลของ Rails นั้นถูกออกแบบมาเพื่อให้ทำงานประสานกันตั้งแต่ใน model ไป controller ออกไป view ซึ่งเดียวเราจะได้ดูกันครับ
เราไปย้ายการตรวจสอบเข้าไปใน model กันครับ
ถ้าดู คู่มือ API ของ Rails จะพบว่าการตรวจสอบสองอย่างแรกเป็นการตรวจสอบมาตรฐานที่มีมาอยู่แล้ว ส่วนอันที่ 3 นั้นไม่มีทำให้ต้องเขียนเอง ดังนั้นผมขอสมมติว่าเราเลิกสนใจการตรวจสอบว่าชื่อหนังสือขึ้นด้วยตัวพิมพ์ใหญ่ไปก่อน
การตรวจสอบที่เกี่ยวข้องก็มี validates_presence_of (ตรวจสอบว่าไม่ว่าง นั่นคือไม่เป็น nil หรือ "") อีกอันก็คือ validates_numericality_of (ตรวจสอบว่าเป็นตัวเลข — สังเกตว่าเราตรวจได้ดีกว่าในตัวอย่าง controller ข้างต้น) เราก็ไปเพิ่ม validation ทั้งสองนี้ในโมเดลดังด้านล่างครับ
class Book < ActiveRecord::Base #... validates_presence_of :title validates_numericality_of :pages #... end
ส่วน validation ที่เราใส่ไปนี้จะทำงานเมื่อเราสั่ง save หรือเมื่อเราทดสอบว่าตรวจสอบผ่านหรือไม่ด้วยเมท็อด valid? (ซึ่งมักใช้ในกรณีที่ยังมีการทดสอบบางอย่างเหลืออยู่ที่ต้องเรียกเอง)
ตัว controller ข้างต้นเราสามารถแก้ได้ดังนี้ครับ
def create
@book = Book.new(params[:book])
if @book.save
redirect_to :action => 'index'
else
render :action => 'new'
end
end
สังเกตว่าการตรวจสอบต่าง ๆ หายไปหมด (รวมถึงการจัดการพวกข้อความแสดงข้อผิดพลาดด้วย) ทีนี้ก่อนจะ save ข้อมูลในโมเดลของเราจะถูกตรวจสอบ ถ้าไม่ผ่านเมท็อด save จะคืนค่า false ทำให้เรากลับไปแสดงหน้า new ใหม่
ทีนี้ พวกข้อผิดพลาดต่าง ๆ ที่เกิดขึ้นระหว่างการตรวจสอบนั้น เราสามารถสั่งให้แสดงใน view ได้โดยเรียก error_messages_for สำหรับแสดงข้อผิดพลาดโดยรวมทั้งหมดของข้อมูลนั้น ๆ และ error_message_on ซึ่งจะแสดงข้อผิดพลาดเฉพาะ field ไป ตัวอย่างการใช้แสดงใน view new.html.erb ด้านล่าง
<h1>New Book</h1>
<%= error_messages_for :book %>
<% form_for :book,@book,:url =>{:action => 'create'} do |f| %>
Title: <%= f.text_field :title %>
<%= error_message_on @book, 'title', 'The title ' %>
<br/>
Pages: <%= f.text_field :pages %>
<%= error_message_on @book, 'pages', 'The number of pages ' %>
<br/>
<%= submit_tag %>
<% end %>
สังเกตการใช้งาน error_messages_for ที่อยู่ตรงหัว และ error_message_on ที่อยู่บริเวณ field ต่าง ๆ นะครับ ในส่วนของ error_message_on เรามีการแก้ให้แทนที่จะเรียก field ด้วยชื่อตรง ๆ ก็ให้เรียกเป็นภาษาที่ดูเป็นภาษาคนเสียหน่อยครับ ภาพด้านล่างแสดงตัวอย่างเวลาเกิด error ครับ (ปกติจะมีสีสรรค์สวยกว่านี้ครับ แต่ css ผมว่างเปล่าเลยดูไม่สวยเท่าใดครับ)

ทีนี้ เหลือ validation อีกอันที่เราต้องการทำพิเศษ (custom) ก็ทำไม่ยากครับ วิธีการก็คือไปเขียนเมท็อดสำหรับตรวจสอบไว้ โดยเมท็อดนี้ถ้าพอข้อผิดพลาดก็ให้ใส่ข้อผิดพลาดนั้นลงใน attribute errors ด้วยเมท็อด add ครับ จากนั้นก็ไปบอก Active Record ให้ทำ validation ที่เราสร้างขึ้นมาโดยสั่ง validate ชื่อเมท็อด ทีตรงหัวโมเดล ตัวอย่างในด้านล่างครับ
class Book < ActiveRecord::Base
#...
validate :title_begins_with_capital_letters # บอกให้ตรวจด้วย
#...
protected
def title_begins_with_capital_letters
return if self.title==nil
return if self.title.length==0
if self.title[0] < ?A or self.title[0] > ?Z
# เพิ่ม errors
errors.add "title","must begin with a capital letter"
end
end
end
สังเกตว่าค่าที่คืนจากเมท็อดนี้จะไม่ถูกนำไปใช้ สิ่งที่สนใจคือ attribute errors อย่างเดียวเท่านั้น นอกจากการประกาศข้อผิดพลาดที่เกิดกับ field แล้ว เรายังเพิ่มข้อผิดพลาดให้แสดงในภาพรวมได้ ด้วยเมท็อด add_to_base ได้
โดยสรุปก็คือระบบ validation ของ Rails จะเริ่มที่การประกาศไว้ใน model (ด้วย validates_*) เรียกตรวจสอบใน controller (ด้วย save หรือ valid?) แล้วไปแสดงผลที่ใน view (ด้วย error_messages_for กับ error_message_on)