ใน rails เราสามารถทำการ validate ข้อมูลก่อนที่มันจะลงไปอยู่ใน database ได้ โดยการประกาศใน model ว่า
validates_presence_of :your_field
:your_field ในที่นี้ ต้องมีจริงๆ ใน database ด้วย
จากนั้นก็ไปประกาศในหน้าที่เราต้องการจะแสดงข้อความ error(.rhtml) ว่า
<%= error_messages_for :your_object_from_controller %>
ก็จะได้หน้าตาประมาณนี้
หรือตัวอย่างอีกที่นึง
html code ที่ถูกสร้างขึ้นมา หน้าตาก็เป็นแบบนี้
1
2
3
4
5
6
7
| <div class="errorExplanation" id="errorExplanation">
<h2>x errors prohibited this product from being saved</h2>
<p>There were problems with the following fields:</p>
<ul>
<li>your_field your_error_message</li>
</ul>
</div>
|
ก็คร่าวๆ ประมาณนี้ แต่ถ้าเราอยากจะ validate ส่วนที่ไม่อยู่ในฐานข้อมูลล่ะ หรือว่าไม่เอาละข้อความ error แบบนี้ ก็สามารถทำได้
เอาอย่างแรกก่อน ถ้าจะ validate ส่วนของ field ที่ไม่อยู่ใน database เช่น มี textfield อยู่อันนึง รอรับรหัสสำหรับตรวจสอบอะไรบางอย่าง แต่ไม่ต้องการเก็บใน database
ใน ActiveRecord::Validations เราสามารถ overwrite validate method ได้ โดยจะต้องประกาศ modifier เป็น protected ด้วย เช่น
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class Product < ActiveRecord::Base
attr_accessor :secret_code
validates_presence_of :description, :category_id, :name, :secret_code
private
def validate_code(code)
code == "my_code" ? false : true
end
protected
def validate
errors.add(:name, "should not be greater than 10") if name.length > 10
errors.add(:secret_code, "was invalid") if validate_code(secret_code)
end
end
|
เริ่มแรกในตาราง products จะมี 3 fields คือ name, description และ category_id แต่ไอ่เหน่งต้องการจะใส่ code เพิ่มเข้าไปอีกตัวเพื่อทำการตรวจสอบ ถ้าเราเรียก secret_code ทื่อๆ มันจะไม่รู้จัก เพราะยังไม่ได้ประกาศตัวแปรให้มัน โดยต้องประกาศก่อน โดยใช้ attr_accessor เป็นตัวประกาศ(ไว้จะมาลงลึกๆ กับเรื่องพวกนี้อีกที) เพื่อเป็นตัวบอกว่าให้สามารถเข้าถึงตัวแปรตัวนี้ได้(ทั้ง read และ write)
จากนั้นก็เอาไปใส่ไว้ใน validates_presence_of1 เพื่อบอกว่าห้ามรับเข้ามาแบบว่างๆ
แล้วลงไปที่ validate ก่อน ก็ประกาศไปโดย ActiveRecord::Base จะประกาศตัวแปร errors ไว้แล้ว ซึ่งเป็น Errors class โดยมันจะบรรจุข้อความได้โดยใช้ add method โดย argument ตัวแรกเป็น field ส่วนตัวที่สองเป็นข้อความ error
เราสามารถ validate ค่าได้ทั้งที่ค่านั้นเป็น filed ใน database อยู่แล้ว หรือว่าไม่ได้เป็น เช่น name กับ secret_code โดย ถ้าหาก name มีตัวอักษรยาวกว่า 10 ตัวอักษร ก็จะขึ้นข้อความดังกล่าว แต่เราสามารถใช้ validates_length_of(*attrs) แทนได้ ส่วนการตรวจสอบ code ลับน้ัน ก็ย้อนไปข้างบน ไปประกาศ method อีกตัวไว้ตรวจสอบ ก็จะได้หน้าตาประมาณนี้

ปัญหาต่อมาคือ ไม่เอาละ ข้อความ error แบบนี้ อยากเปลี่ยน เราทำได้โดยการ overwrite error_method_for method ที่ application_helper.rb หน้าตาดั้งเดิมมันก็เป็นแบบนี้
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| def error_messages_for(*params)
options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {}
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
count = objects.inject(0) {|sum, object| sum + object.errors.count }
unless count.zero?
html = {}
[:id, :class].each do |key|
if options.include?(key)
value = options[key]
html[key] = value unless value.blank?
else
html[key] = 'errorExplanation'
end
end
header_message = "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] || params.first).to_s.gsub('_', ' ')} from being saved"
error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }
content_tag(:div,
content_tag(options[:header_tag] || :h2, header_message) <<
content_tag(:p, 'There were problems with the following fields:') <<
content_tag(:ul, error_messages),
html
)
else
''
end
end
|
จะเห็นว่า code ด้านล่าง เป็นกลไกการสร้าง html code ขึ้นมา ถ้าอยากจะ modify code ตรงนี้ใน application_helper.rb ก็ไม่ว่ากันครับ แต่ไอ่เหน่งขี้เกียจ ไอ่เหน่งจะทำแบบนี้ครับ เอาไอ้ก้อนข้างล่างนี่ ไปใส่ใน application_helper.rb
1
2
3
4
| def get_validate_message(obj,attribute,field=nil)
field = attribute if field.nil?
obj.errors[attribute.to_s].inject(String.new) {|messages,msg| messages << "<li>#{field.to_s.humanize} #{msg}</li>"} unless obj.errors[attribute.to_s].nil?
end
|
ก้อนนี้จะทำการเอาข้อความ error ที่เราใส่ไปในแต่ละ field มาแสดง
จากนั้นก็ไปเรียกใช้ในฝั่ง view ดังนี้
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <h1>New product</h1>
<% unless @product.errors.empty? %>
<div class="errorExplanation" id="errorExplanation">
<p>There were problems with the following fields:</p>
<ul>
<%= get_validate_message(@product,:name) %>
<%= get_validate_message(@product,:description) %>
<%= get_validate_message(@product,:category_id, :Category) %>
<%= get_validate_message(@product,:secret_code) %>
</ul>
</div>
<% end %>
<% form_for(:product, :url => products_path) do |f| %>
<%= render :partial => 'form' , :locals => {:f => f} %>
<p>
<%= submit_tag "Create" %>
</p>
<% end %>
<%= link_to 'Back', products_path %>
|
ก็เอา error_message_for ออกไปได้เลย
ถ้ายังไม่พอใจ ยังเอาก้อนที่เพิ่งสร้างมานั้น ไปยัดใน helper ได้อีกทีนะ โดยส่ง fields ทั้งหลายไปเป็น array แล้วก็ loop มัน ตามลำดับ(โอ้วว คิดไปถึงไหนแล้วเนี่ย) ไว้ขยันแล้วจะมา implement ต่อละกัน
ประมาณนี้ครับ ทำให้เราแก้ไขปรับแต่งก้อน html ได้อย่างเต็มรูปแบบ รวมถึงสามารถเรียงข้อความ error ได้ด้วย เพราะปกติ มันจะเรียงไม่ได้ เพราะใน error_message_for มันจะทำการ loop ก้อน errors โดยใช้ full_message ซึ่งมันจะไป loop ตัว hash ที่เก็บไว้อีกที ซึ่งการเรียง key ของ hash ใน ruby ไม่ใช่การเรียงตามลำดับตัวอักษร และตามลำดับที่ add เข้าไปแน่นอนน
1 validations ต่างๆ เข้าไปดูได้ที่ Rails API ได้ครับ
ปล. สำหรับการจะทำให้ hash มี key เรียงลำดับตามที่เรา add ไป(กรณีใช้งาน hash ทั่วๆ ไป) สามารถทำได้โดยใช้ OrderedHash วิธีใช้ก็ดูในเว็บเลยครับ