Ruby on Rails :: Customized Validation

Posted by PunNeng, Tue Apr 03 03:24:00 UTC 2007

ใน 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                                                            
    # Actually, we can use validates_length_of(*attrs) instead.
    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_of​1 เพื่อบอกว่าห้ามรับเข้ามาแบบว่างๆ แล้วลงไปที่ 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 วิธีใช้ก็ดูในเว็บเลยครับ

Filed Under: Ruby on Rails | Tags: howto ruby on rails validation

Comments

Have your say

A name is required. You may use HTML in your comments.




codegent: we're hiring