Displaying articles with tag validation

Better error messages for validation

Posted by PunNeng, Sat Apr 26 02:38:00 UTC 2008

แว่บๆ ไปอ่าน feed เจอของ Softies on Rails
error_messages_for เปลี่ยนไป(ตอนไหนไม่รู้)
ที่เคยเขียนไปของเก่า จำได้ว่ามันแก้ header_messages ไม่ได้ เลยต้องแก้เอง เพราะหน้าตาใน helper ของเก่าเป็นแบบนี้

  1
header_message = "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] || params.first).to_s.gsub('_', ' ')} from being saved"

แว่บไปดูใน API ไปเจอว่าเปลี่ยนไปจริงๆ ได้ของแถมมาอีก คือ error_messages_on ซึ่งผมไม่เคยใช้มาก่อน(บ้านนอก)

list ข้างล่างคือค่า params ที่เราจะใส่ไปได้ใน error_messages_for

  • header_tag ค่า default เป็น h2
  • id ค่า default เป็น errorExplanation
  • class ค่า default เป็น errorExplanation
  • object ไว้เซ็ตค่ากรณีที่ไม่มี object ที่ต้องใส่ตาม convention เช่น

error_messages_for 'user'

ถ้าจะให้ขึ้น ฝั่ง view จะต้องมองเห็น @user แล้ว error ถึงจะออก
แต่แบบใหม่นี่ สามารถอ้างได้จาก object อื่นได้เลย เช่น

error_messages_for 'user', @question.user

  • object_name ตรงตัว ถ้าไม่ใส่ จะไปใช้ object แทน ในตัวอย่างคือ User
  • header_message ตรงตัว ค่า default เป็น X errors prohibited this object from being saved ซึ่ง X ก็คือ object_name นั่นแหละ
  • message คือข้อความที่อยู่ใต้ header_message และมาก่อน error list ค่า default เป็น There were problems with the following fields:

ถัดมาก็ error_message_on
ไม่มีอะไรง่ายกว่าการดูตัวอย่าง

  1
  2
  3
  4
  5
  6
  7
  8
<%= error_message_on "post", "title" %> =>
<div class="formError">can't be empty</div>

<%= error_message_on @post, "title" %> =>
<div class="formError">can't be empty</div>

<%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> =>
<div class="inputError">Title simply can't be empty (or it won't work).</div>

ปล. นึกไม่ออกว่าข้อความไหน อยู่ตรงไหน ย้อนกลับไปดของเก่าได้ครับ

0 comments | Filed Under: Ruby on Rails | Tags: validation

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 วิธีใช้ก็ดูในเว็บเลยครับ

0 comments | Filed Under: Ruby on Rails | Tags: validation

Ruby on Rails :: Validation

Posted by PunNeng, Sun Jul 16 17:45:00 UTC 2006

มาต่อกันดีกว่า

จากคราวที่แล้ว ถ้าหากว่าเราเกิดลืมใส่ title หรือ body มันจะดูไม่ดีแน่นอน หรือว่าใส่ title ซ้ำกันอีก อันนี้ก็ดูไม่ได้ เราเลยต้องมีการตรวจสอบความถูกต้องก่อนใส่ในฐานข้อมูล แล้วเราจะไปตรวจสอบในไหนดีล่ะ??? ก็ใน model ไง ตอนนี้ model จะทำหน้าที่เหมือนนายด่าน อะไรจะเข้าหรือออกจากฐานข้อมูล จะต้องผ่าน model ก่อนเสมอ งั้นเข้าไปใน model ดูเลยดีกว่า เข้าไปที่ app/models/post.rb กันเลย (เราสร้างไว้แล้วโดยใช้ scaffold จำได้เป่าเอ่ย)

class Post < ActiveRecord::Base
end

บ๋อแบ๋ ว่างเปล่า แล้วส่วน db mapping หรือส่วนอื่นๆ ไปไหนหมด มันอยู่ใน ActiveRecord นั่นเอง เราแค่ไป extends(<) คลาสแม่ เราก็จะทำอะไรได้ เหมือนๆ กับคลาสแม่แล้ว แล้วเพิ่มส่วนของการ validate ตามนี้

class Post < ActiveRecord::Base
    validates_presence_of :title, :body
end

'validates_presence_of' จะทำหน้าที่ตรวจสอบว่า field ที่เรากำหนดไว้ มีค่าว่างเปล่าหรือไม่ ถ้าว่าง มันจะส่ง error msg ไปให้หน้าเว็บ มาลองเล่นดู

แล้วเพิ่มบรรทัดนี้อีก

validates_uniqueness_of :title

ตัวนี้จะทำหน้าที่ตรวจสอบว่ามันซ้ำกันหรือไม่ ถ้าซ้ำก็ส่ง error กลับไปเลย

ต่อไป ก็มาจัดหน้าตาให้มันดูดีกว่านี้หน่อยดีกว่า ไปที่ app/views/admin/list.rhtml เลย ของเก่าเป็นไงไม่รู้ แต่ใช้อันนี้ดีกว่า

  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
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
<h1>Listing posts</h1>
<%= link_to 'New post', :action => 'new' %>  
<table cellpadding="5" cellspacing="2" >
  <tr class="headTable">
    <th>Id
    <th width="40%">Post
    <th>Created at
    <th>Updated at
    <th>Actions
  </tr>
  <%
    odd_or_even = 0
    for post in @posts
      odd_or_even = 1 - odd_or_even
  %>
  <tr valign="top" class="listLine<%= odd_or_even %>">
  <td>
    <%= post.id %>
  </td>
  <td>
    <span class="listTitle">< %= h(post.title) %></span><br />
    <%= h(truncate(post.body,40)) %>
  </td>
  <td align="center">
    <%= post.created_at.strftime("%y-%m-%d") %><br />
  </td>
  <td align="center">
    <%= post.updated_at.strftime("%y-%m-%d") %><br />
  </td>
  <td class="listActions">
    <%= link_to 'Show', :action => 'show', :id => post %> | 
    <%= link_to 'Edit', :action => 'edit', :id => post %> | 
    <%= link_to 'Destroy', { :action => 'destroy', :id => post }, :confirm => 'Are you sure?', :post => true %>
  </td>
<% end %>
</tr></table>   
<%=  if @post_pages.current.previous
  link_to("Previous page", { :page => @post_pages.current.previous })
     end
%>
<%= if @post_pages.current.next
       link_to("Next page", { :page => @post_pages.current.next })
    end
%>

หลังจากที่เราส่ง @posts มาจาก controller ซึ่งมันจะเป็น array แล้วเราก็มาทำการวนลูป เอาแต่ละตัวใน @posts มาแสดง function ใหม่ๆ ที่เราเรียนรู้คือ

h(String) ไว้จัดการกับพวก < ,> อะไรพวกนี้ ให้เราสามารถมองเห็นได้ในหน้า html truncate(String,Fixnum) ไว้แสดง String ที่เราใส่เข้าไปใน parameter ตัวแรก เป็นจำนวนตัวที่เราใส่ไปใน parameter ตัวที่สอง

แล้วในส่วนข้างล่าง มีตัวคุมหน้าด้วย หน้าต่อไป หรือย้อนกลับ

แล้วเอา css ไปเพิ่มหน่อย ที่ public/stylesheets/scaffold.css

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
.headTable{
  background: #accdde;
}

.listTitle {
  color:       #244;
  font-weight: bold;
  font-size:   larger;
}
.listActions {
  font-size:    x-small;
  text-align:   right;
  padding-left: 1em;
}
.listLine0 {
  background: #dce7ed;
}
.listLine1 {
  background: #eeeeee;
}

คลิกเพื่อดูขนาดจริง

เริ่มดูดีแล้ว ไว้ต่อคราวหน้าครับ

ปล. สำหรับรายละเอียดต่างๆ สำหรับ function ต่างๆ ที่เอามาใช้ สามารถดูได้อย่างละเอียดที่ Rails API
ปอ. ตอนนี้ผมกำลังทะเลาะกับ CRUD ของ Rails และ CSS อยู่ อันต่อไปอาจจะล่าช้าไปนิดนึง

แก้ไขล่าสุด วันที่ 7 กรกฏาคม 2550 เวลา 23.45 น.

0 comments | Filed Under: | Tags: validation

codegent: we're hiring