<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Jittat</title>
	<atom:link href="http://jittat.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://jittat.wordpress.com</link>
	<description>A theoretician</description>
	<lastBuildDate>Sat, 08 Aug 2009 04:53:21 +0000</lastBuildDate>
	<language>th</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='jittat.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>Jittat</title>
		<link>http://jittat.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://jittat.wordpress.com/osd.xml" title="Jittat" />
	<atom:link rel='hub' href='http://jittat.wordpress.com/?pushpress=hub'/>
		<item>
		<title>หัดเล่น I18n ใน Rails 2.2</title>
		<link>http://jittat.wordpress.com/2009/08/08/%e0%b8%ab%e0%b8%b1%e0%b8%94%e0%b9%80%e0%b8%a5%e0%b9%88%e0%b8%99-i18n-%e0%b9%83%e0%b8%99-rails-2-2/</link>
		<comments>http://jittat.wordpress.com/2009/08/08/%e0%b8%ab%e0%b8%b1%e0%b8%94%e0%b9%80%e0%b8%a5%e0%b9%88%e0%b8%99-i18n-%e0%b9%83%e0%b8%99-rails-2-2/#comments</comments>
		<pubDate>Sat, 08 Aug 2009 04:37:04 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=158</guid>
		<description><![CDATA[
ฟีเจอร์ใหม่อีกอย่างหนึ่งของ <a href="http://guides.rubyonrails.org/2_2_release_notes.html">Rails 2.2</a> ก็คือความสามารถในการทำ internationalization พื้นฐานที่ถูกบรรจุอยู่ใน core  (อ่าน <a href="http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api">api</a>, เว็บหลัก <a href="http://rails-i18n.org/">Rails I18n</a>)<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=158&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกมาจาก <a href="http://www.rails66.com/blog/?p=604">http://www.rails66.com/blog/?p=604</a>)</p>
<p>ฟีเจอร์ใหม่อีกอย่างหนึ่งของ <a href="http://guides.rubyonrails.org/2_2_release_notes.html">Rails 2.2</a> ก็คือความสามารถในการทำ internationalization พื้นฐานที่ถูกบรรจุอยู่ใน core  (อ่าน <a href="http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api">api</a>, เว็บหลัก <a href="http://rails-i18n.org/">Rails I18n</a>)</p>
<p>ก่อนจะใช้ได้ก็ต้องไปใช้ Rails 2.2 เสียก่อน ถ้าจะทดลองกับ project ใหม่ก็ติดตั้ง Rails 2.2 แล้วก็สั่งสร้าง project ได้เลย  ถ้าจะแก้ project เก่าก็แก้ไฟล์ <b><tt>/config/environment.rb</tt></b> ตามนี้ครับ</p>
<p><pre class="brush: ruby;">
# เปลี่ยน version rails
RAILS_GEM_VERSION = '2.2.2' unless defined? RAILS_GEM_VERSION

require File.join(File.dirname(__FILE__), 'boot')
  # ..
  # Setting locales
  config.i18n.default_locale = 'en'  # เปลี่ยนเป็น 'th' สำหรับไทย
  # ..
end
</pre></p>
<p>เปลี่ยนเสร็จก็อย่าลืมสั่ง <b><tt>rake rails:update</tt></b> ให้ปรับแก้อะไรต่าง ๆ ด้วยนะครับ</p>
<p>หลักการคร่าว ๆ ของการทำ i18n ใน rails ก็คือ เราจะแยกสตริงที่ใช้ไปใส่ไว้ในไฟล์ต่างหาก โดยแบ่งตามภาษา จากนั้นเวลาเราจะแสดงสตริงเหล่านั้นก็จะเรียกผ่านฟังก์ชัน <b><tt>I18n.translate</tt></b> หรือย่อ ๆ ว่า <b><tt>I18n.t</tt></b> แทนที่จะเขียนสตริงเหล่านั้นออกไปตรง ๆ</p>
<p>ที่เก็บของไฟล์สตริงเหล่านี้จะอยู่ใน <b><tt>/config/locales/</tt></b> โดยสามารถเก็บเป็นไฟล์ yml หรือเป็น ruby hash ก็ได้  คราวนี้เราจะลองอะไรง่าย ๆ กันก่อน โดยลองไปเพิ่ม (หรือแก้) ไฟล์ <tt>en.yml</tt> ในไดเร็กทรอรีดังกล่าวเป็นดังด้านล่างครับ</p>
<p><b>
<pre>
en:
  hello: "Hello world"
  hello_name: "Hello, {{name}}"

  config:
    hello: "Hello, admin"
</pre>
<p></b></p>
<p>ทีนี้ เราไปทดลองเรียกสตริงดังกล่าวใน <tt>script/console</tt> ครับ</p>
<p><pre class="brush: ruby;">
I18n.translate :hello                #=&gt;; &amp;quot;Hello world&amp;quot;
I18n.t :hello                         #=&gt;; &amp;quot;Hello world&amp;quot;
I18n.t :hello_name, :name =&amp;gt; 'John'  #=&gt;; &amp;quot;Hello, John&amp;quot;
I18n.t 'config.hello'                 #=&gt;; &amp;quot;Hello, admin&amp;quot;
</pre></p>
<p>จากตัวอย่างด้านบน แสดงการเรียกสตริงแบบทั่วไป  ส่วน <tt>hello_name</tt> เป็นสตริงที่รับพารามิเตอร์ name ส่วน <tt>config.hello</tt> เป็นการระบุสตริงแบบที่มีขอบเขต (อยู่ใน config)</p>
<p>ทีนี้ ลองไปสร้างไฟล์ <tt>th.yml</tt> แล้วใส่ข้อมูลตามด้านล่างนะครับ</p>
<p><b>
<pre>
th:
  hello: "สวัสดี"
  hello_name: "สวัสดี, {{name}}"

  config:
    hello: "กราบสวัสดีท่านผู้ดูแล"
</pre>
<p></b></p>
<p>แล้วไปทดลองใหม่ใน <tt>script/console</tt> ครับ</p>
<p><pre class="brush: ruby;">
&amp;gt;&amp;gt; I18n.locale = 'th'                   # ตั้ง locale
&amp;gt;&amp;gt; I18n.translate :hello                #=&gt;; &amp;quot;สวัสดี&amp;quot;
&amp;gt;&amp;gt; I18n.t :hello_name, :name =&amp;gt; 'John'  #=&gt;; &amp;quot;สวัสดี, John&amp;quot;
&amp;gt;&amp;gt; I18n.t 'config.hello'                #=&gt;; &amp;quot;กราบสวัสดีท่านผู้ดูแล&amp;quot;
</pre></p>
<p>ทีนี้ ถ้าเราต้องการให้ locales เริ่มต้นของเราเป็น th เลย ก็ไปแก้บรรทัด <tt>config.i18n.default_locale</tt> ใน <tt>environment.rb</tt> นะครับ</p>
<p>เพื่อความสะดวกใน view ฟังก์ชัน <tt>I18n.t</tt> สามารถเรียกสั้น ๆ ได้ด้วย <b><tt>t</tt></b> ดังนั้นเราสามารถเขียน <b><tt>&lt;%=t :hello %&gt;</tt></b> ได้เลย</p>
<p>อันนี้เป็นการใช้งานแบบขั้นต้นนะครับ ถ้ามีเวลาจะมาเขียนเกี่ยวกับการแปลอื่น ๆ เช่นการแปลชื่อ model และ attributes ใน Active Record ต่อครับ  ถ้าใครอยากเล่นก่อนก็ไปโหลดไฟล์ locale <a href="http://garnet.cpe.ku.ac.th/~jtf/rails/th.rb">th.rb</a> ที่มีการแปลข้อความใน Active Record และส่วนอื่น ๆ เช่นวันที่และจำนวนนับ (แปลโดยคุณ Sikachu! ขอบคุณมากครับ!) แล้วมาเล่นดูได้ครับ  เอาไปใส่เพิ่มไว้ใน <tt>/config/locales</tt> แล้วก็เปลี่ยน locale เป็น th ดู</p>
<p>ไฟล์คำแปลดังกล่าวผมไปโหลดมาจาก <a href="http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale">github</a>  ซึ่งเป็นไฟล์ที่ <a href="http://www.artweb-design.de/svenfuchs">Sven Fuchs</a> เอามาจาก <a href="http://i18n-demo.phusion.nl/">demo application</a> (<a href="http://www.railway.at/articles/2008/08/10/localizing-rails">อ่านเพิ่ม</a>) โดยการแปลในนั้นทำโดยคุณ <a href="http://sikachu.com/">Prem Sichanugrist</a> (หรือคุณ <a href="http://sikachu.com/">Sikachu!</a> นั่นเอง)  อย่างไรก็ตาม ตอนผมเอามาลองแล้วพบว่าเหมือนการอ้างถึงสตริงใน active record มันจะเปลี่ยนไป ผมเลยแก้กลายมาเป็นไฟล์ด้านบนครับ (ส่ง patch ไปให้ Sven Fuchs แล้ว)</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/158/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/158/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/158/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/158/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/158/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/158/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/158/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/158/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=158&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/08/08/%e0%b8%ab%e0%b8%b1%e0%b8%94%e0%b9%80%e0%b8%a5%e0%b9%88%e0%b8%99-i18n-%e0%b9%83%e0%b8%99-rails-2-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>Dependency Injection กับ Ruby</title>
		<link>http://jittat.wordpress.com/2009/08/08/dependency-injection-%e0%b8%81%e0%b8%b1%e0%b8%9a-ruby/</link>
		<comments>http://jittat.wordpress.com/2009/08/08/dependency-injection-%e0%b8%81%e0%b8%b1%e0%b8%9a-ruby/#comments</comments>
		<pubDate>Fri, 07 Aug 2009 19:50:53 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=152</guid>
		<description><![CDATA[แนวคิดเกี่ยวกับ <a href="http://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a> เป็นแนวคิดที่สำคัญมากในการโปรแกรมสำหรับภาษาเช่น Java  ด้วยสาเหตุหลาย ๆ ประการ อย่างไรก็ตาม ก็เป็นที่น่าสงสัยว่าทำไม DI framework ไม่เป็นที่นิยมใน Ruby<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=152&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกมาจาก <a href="http://www.rails66.com/blog/?p=593">http://www.rails66.com/blog/?p=593</a>)</p>
<p>แนวคิดเกี่ยวกับ <a href="http://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a> เป็นแนวคิดที่สำคัญมากในการโปรแกรมสำหรับภาษาเช่น Java  ด้วยสาเหตุหลาย ๆ ประการ</p>
<p>สาเหตุหนึ่งก็คือมันทำให้เราสามารถทำ unit test กับโปรแกรมที่มีการขึ้นต่อกันได้ ยกตัวอย่างเมท็อดด้านล่าง</p>
<p><pre class="brush: java;">
public class RegistrationController {
	// ...
	void sendConfirmationEmail(User newUser) {
		MailSender sender = new MailSender();
		
		String msg = buildEmailMessage(newUser);
		sender.send(msg,myemail,newUser.getEmail());
	}
}
</pre></p>
<p>แทบเราจะไม่สามารถ test ได้เลยว่าเมท็อดดังกล่าวเรียก <tt>MailSender</tt> ได้ถูกต้องหรือเปล่า  ที่ผมนึกออกคงจะต้องเข้าไปจัดการแก้โปรแกรมหลายจุดอยู่</p>
<p>ปัญหาก็มาจากการที่เมท็อดนี้สร้าง <tt>MailSender</tt> ขึ้นมาเอง ทำให้เราไม่สามารถเข้าไปแก้ไขได้  วิธีการที่นิยมใช้ในการจัดการเรื่องเหล่านี้ก็คือการแยกการขึ้นต่อกันของคลาส <tt>MailSender</tt> ออกมา โดยทำเป็นเมท็อดให้กำหนดค่าเข้าไป อาจจะที่ constructor หรือเขียนเป็นเมท็อดแยก หรือไม่ก็ใช้ DI framework ต่างๆ</p>
<p>เช่นแก้โปรแกรมเป็นแบบนี้</p>
<p><pre class="brush: java;">
public class RegistrationController {
	// ...
	private MailSenderInterface mailSender;
	
	void setMailSender(MailSenderInterface sender) {
		mailSender = sender;
	}
	
	void sendConfirmationEmail(User newUser) {
		String msg = buildEmailMessage(newUser);
		mailSender.send(msg,myemail,newUser.getEmail());
	}
}
</pre></p>
<p>ในปัจจุบัน Java มี dependency injection framework หลายตัว (เท่าที่ผมทราบ) ซึ่งทำให้การ &#8220;ร้อย&#8221; (ของยืมพี่ป๊อกหน่อย) component ต่าง ๆ เข้าด้วยกันเป็นไปได้สะดวกมาก</p>
<p>แนวคิดดังกล่าวได้รับการตอบรับจากทางฝั่งนักพัฒนา Ruby เช่นเดียวกัน  เช่น <a href="http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc">Jim Weirich ได้เขียนบล็อกเกี่ยวกับเรื่องนี้เอาไว้เมื่อปี 2004</a> ใน Ruby ก็มี framework ทำ DI อยู่หลายตัวเช่น <a href="http://needle.rubyforge.org/api/">Needle</a> เขียนโดย Jamis Buck (คนทำ Capistrano)  Jamis Buck ถึงขนาดเขียน di framework มาสองตัวเลยทีเดียว (อีกตัวคือ <a href="http://copland.rubyforge.org/">Copland</a>)</p>
<p>อย่างไรก็ตาม ก็เป็นที่น่าสงสัยว่าทำไม DI framework ไม่เป็นที่นิยมใน Ruby</p>
<p>หนึ่งปีถัดมา Jim Weirich ได้ไปพูดที่ OSCON ในหัวข้อว่า &#8220;<a href="http://onestepback.org/articles/depinj/">Dependency Injection: Vitally Important or Totally Irrelevant?</a>&#8221;  โดยสรุปว่าเนื่องจาก Ruby ไม่เหมือน Java ในปัจจุบันยังไม่เห็นความจำเป็นของ DI framework</p>
<p>Jamis Buck เองก็<a href="http://weblog.jamisbuck.org/2007/7/29/net-ssh-revisited">ออกมาเขียนถึงเรื่องดังกล่าวเช่นกัน</a> โดยเขาแก้โปรแกรมในไลบรารี Net::SSH ใหม่ โดยเอา DI (ที่เขาเขียนเอง) ออก แล้วพบว่าโปรแกรมเล็กลงและอ่านง่ายขึ้น</p>
<p><b>ทำไม DI ดูเหมือนจะยังไม่จำเป็นใน Ruby?</b></p>
<p>พิจารณาจากตัวอย่างข้างต้น ถ้าเอาเมท็อด <tt>sendConfirmationEmail</tt> มาเขียนเป็นโปรแกรม Ruby จะได้ประมาณด้านล่างครับ</p>
<p><pre class="brush: ruby;">
class RegistrationController
  # ..
  def send_confirmation_email(new_user)
    sender = MailSender.new
    msg = build_email_message(new_user)
    sender.send(msg, self.myemail, new_user.get_email)
  end
end
</pre></p>
<p>แล้วจะ test อย่างไร?</p>
<p>สิ่งที่เรามักจะลืมไปก็คือภาษาแต่ละภาษามีลักษณะที่แตกต่างกัน บางอย่างที่ไม่สามารถทำได้เลยในบางภาษา อาจเป็นสิ่งธรรมดามากในบางภาษา</p>
<p>ใน Ruby มีความสามารถ (หรือความบกพร่อง?) อย่างหนึ่งคือ Open Class</p>
<p>นั่นคือเราสามารถแกะคลาสมาแก้ได้ตลอดเวลา (รวมถึงตอน run-time) นอกจากนี้เรายังแก้ไขการทำงานของเมท็อดของแต่ละวัตถุได้โดยง่าย (ในระหว่างที่โปรแกรมทำงานอยู่เช่นกัน)</p>
<p>เมท็อดด้านบนถ้าจะเขียน test case ใน rspec ก็เป็นประมาณนี้ครับ</p>
<p><pre class="brush: ruby;">
describe RegistrationController do
  #..
  it &quot;should send mail to user's address from admin's mail&quot; do
    my_email = 'jittat@internet.com'
    user_email = 'user@space.com'
    user = mock_model(User, :email =&amp;gt; user_email)
    sender = mock(&amp;quot;mock sender&amp;quot;)
    sender.should_receive(:send).
      with(anything, my_email, user_email)
    MailSender.should_receive(:new).and_return(mock_sender)    
    controller = RegistrationController.new :adm_mail =&amp;gt; my_email
    controller.send_confirmation_email(user)
  end
end
</pre></p>
<p>สังเกตว่าเนื่องจาก class และ object ใน ruby แก้ได้ตลอดเวลา mock framework จึงสามารถเข้าไปปรับแก้อะไรต่าง ๆ ได้มากมาย โดยไม่ต้องแยก dependency ออกมา</p>
<p>ไม่รู้ว่าผลที่ได้จะดีหรือไม่ดี?  แต่ก็ทำให้ความจำเป็นของการใช้ DI framework ใน Ruby ลดลงไป</p>
<p>อ่านเพิ่มเติมได้ใน <a href="http://onestepback.org/articles/depinj/">slide ของ Jim Weirich</a> นะครับ อธิบายเห็นภาพมาก  (มีอีกอันที่น่าสนใจเหมือนกันคือ <a href="http://onestepback.org/articles/10things/">10 Things Every Java Programmer Should Know About Ruby</a> ลองไปกดเล่นได้ครับ)</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/152/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/152/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/152/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/152/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/152/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/152/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/152/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/152/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=152&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/08/08/dependency-injection-%e0%b8%81%e0%b8%b1%e0%b8%9a-ruby/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>การใช้งาน Validation</title>
		<link>http://jittat.wordpress.com/2009/08/08/%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%83%e0%b8%8a%e0%b9%89%e0%b8%87%e0%b8%b2%e0%b8%99-validation/</link>
		<comments>http://jittat.wordpress.com/2009/08/08/%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%83%e0%b8%8a%e0%b9%89%e0%b8%87%e0%b8%b2%e0%b8%99-validation/#comments</comments>
		<pubDate>Fri, 07 Aug 2009 19:23:05 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=142</guid>
		<description><![CDATA[เวลาเราพัฒนาโปรแกรมประยุกต์ภายใต้กรอบงานแบบ MVC โดยเฉพาะบน Rails บางทีเราจะพบว่า model ของเรานั่นว่างโล่ง (เพราะว่าทำหน้าที่เชื่อมกับ table อย่างเดียว) ส่วน controller เราเต็มไปด้วยตรรกซับซ้อนซ่อนเงื่อน จนทำให้นึกไปถึงสมัยก่อนที่เขียนโปรแกรมแบบไม่มีโครงสร้างและใช้ goto กันจนโปรแกรมพันกันเป็นเส้นก๋วยเตี๋ยว

พักหลัง ๆ เลยมีคนพยายามบอกว่า <a href="http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model">model ควรจะอ้วน ๆ แต่ controller ควรจะผอมเพรียว</a> (<a href="http://www.therailsway.com/2007/6/1/railsconf-recap-skinny-controllers">ตัวอย่างเพิ่มเติม</a>)

แล้วอะไรบ้างที่สามารถนำไปอยู่ในโมเดลได้?  ที่ชัดที่สุดก็คือการตรวจสอบความถูกต้องของข้อมูล (validation)<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=142&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกมาจาก <a href="http://www.rails66.com/blog/?p=496">http://www.rails66.com/blog/?p=496</a>)</p>
<p>เวลาเราพัฒนาโปรแกรมประยุกต์ภายใต้กรอบงานแบบ MVC โดยเฉพาะบน Rails บางทีเราจะพบว่า model ของเรานั่นว่างโล่ง (เพราะว่าทำหน้าที่เชื่อมกับ table อย่างเดียว) ส่วน controller เราเต็มไปด้วยตรรกซับซ้อนซ่อนเงื่อน จนทำให้นึกไปถึงสมัยก่อนที่เขียนโปรแกรมแบบไม่มีโครงสร้างและใช้ goto กันจนโปรแกรมพันกันเป็นเส้นก๋วยเตี๋ยว</p>
<p>พักหลัง ๆ เลยมีคนพยายามบอกว่า <a href="http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model">model ควรจะอ้วน ๆ แต่ controller ควรจะผอมเพรียว</a> (<a href="http://www.therailsway.com/2007/6/1/railsconf-recap-skinny-controllers">ตัวอย่างเพิ่มเติม</a>)</p>
<p>แล้วอะไรบ้างที่สามารถนำไปอยู่ในโมเดลได้?  ที่ชัดที่สุดก็คือการตรวจสอบความถูกต้องของข้อมูล (validation)</p>
<p>จริง ๆ แล้วในตัวอย่างและหนังสือ Rails แทบจะทุกเล่มทุกอันก็จะแสดงให้เห็นว่าการตรวจสอบความถูกต้องของข้อมูลทำได้ง่ายมากในโมเดล แต่บางทีเวลาเรารีบ ๆ เขียนก็มักจะลืม ๆ ไป หรือบางทีเราอาจจะมีการตรวจสอบอะไรที่แปลกไปจากรูปแบบการตรวจสอบพื้นฐานที่ Rails มีให้ เราก็เลยไปเขียนเอาไว้ใน controller เสียเลย</p>
<p>เราจะยกตัวอย่างจากโมเดล Book ที่มี attribute เป็น <tt>title</tt> และ <tt>page</tt> นะครับ</p>
<p>ลองดูโปรแกรมของ controller ด้านล่างที่รับค่าจาก form ผ่านทางเมท็อด create นะครับ<br />
<pre class="brush: ruby;">
  def new
    @book = Book.new
  end

  def create
    @book = Book.new(params[:book])
    if @book.title==&amp;quot;&amp;quot;
      flash[:notice] = &amp;quot;Error bad title&amp;quot;
      render :action =&gt; 'new'
      return
    end
    if @book.pages==&amp;quot;&amp;quot;
      flash[:notice] = &amp;quot;Error bad page number&amp;quot;
      render :action =&gt; 'new'
      return
    end
    if @book.title[0]  ?Z
      flash[:notice] = &amp;quot;Error title should begin with cap&amp;quot;
      render :action =&gt; 'new'
      return
    end

    @book.save
    redirect_to :action =&gt; 'index'
  end
</pre></p>
<p>Controller ด้านบนมีการตรวจสอบสามอย่างคือ <tt>title</tt> ต้องไม่ว่าง, <tt>page</tt> ต้องไม่ว่าง, และ <tt>title</tt> ต้องขึ้นต้นด้วยตัวอักษรพิมพ์ใหญ่  </p>
<p>เมื่อตรวจสอบผ่านแล้วก็จะเก็บข้อมูลไป  ถ้ามีข้อผิดพลาดก็จะกลับไปแสดง view new กลับมาเหมือนเดิม  เพื่อให้หน้า view แสดง <tt>flash[:notice]</tt> เพื่อบอกกับผู้ใช้ว่ามีข้อผิดพลาดทำให้เก็บค่าไม่ได้ และให้ผู้ใช้ป้อนค่าเข้ามาใหม่</p>
<p>ระบบการตรวจสอบข้อมูลของ Rails นั้นถูกออกแบบมาเพื่อให้ทำงานประสานกันตั้งแต่ใน model ไป controller ออกไป view ซึ่งเดียวเราจะได้ดูกันครับ</p>
<p>เราไปย้ายการตรวจสอบเข้าไปใน model กันครับ</p>
<p>ถ้าดู <a href="http://api.rubyonrails.com/classes/ActiveRecord/Validations/ClassMethods.html">คู่มือ API</a> ของ Rails จะพบว่าการตรวจสอบสองอย่างแรกเป็นการตรวจสอบมาตรฐานที่มีมาอยู่แล้ว ส่วนอันที่ 3 นั้นไม่มีทำให้ต้องเขียนเอง ดังนั้นผมขอสมมติว่าเราเลิกสนใจการตรวจสอบว่าชื่อหนังสือขึ้นด้วยตัวพิมพ์ใหญ่ไปก่อน  </p>
<p>การตรวจสอบที่เกี่ยวข้องก็มี <tt>validates_presence_of</tt> (ตรวจสอบว่าไม่ว่าง นั่นคือไม่เป็น <tt>nil</tt> หรือ <tt>""</tt>)  อีกอันก็คือ <tt>validates_numericality_of</tt> (ตรวจสอบว่าเป็นตัวเลข &#8212; สังเกตว่าเราตรวจได้ดีกว่าในตัวอย่าง controller ข้างต้น)  เราก็ไปเพิ่ม validation ทั้งสองนี้ในโมเดลดังด้านล่างครับ</p>
<p><pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
  #...
  validates_presence_of :title
  validates_numericality_of :pages
  #...
end
</pre></p>
<p>ส่วน validation ที่เราใส่ไปนี้จะทำงานเมื่อเราสั่ง <tt>save</tt> หรือเมื่อเราทดสอบว่าตรวจสอบผ่านหรือไม่ด้วยเมท็อด <tt>valid?</tt> (ซึ่งมักใช้ในกรณีที่ยังมีการทดสอบบางอย่างเหลืออยู่ที่ต้องเรียกเอง)  </p>
<p>ตัว controller ข้างต้นเราสามารถแก้ได้ดังนี้ครับ<br />
<pre class="brush: ruby;">
  def create
    @book = Book.new(params[:book])
    if @book.save
      redirect_to :action =&gt; 'index'
    else
      render :action =&gt; 'new'
    end
  end
</pre></p>
<p>สังเกตว่าการตรวจสอบต่าง ๆ หายไปหมด (รวมถึงการจัดการพวกข้อความแสดงข้อผิดพลาดด้วย)  ทีนี้ก่อนจะ save ข้อมูลในโมเดลของเราจะถูกตรวจสอบ ถ้าไม่ผ่านเมท็อด save จะคืนค่า false ทำให้เรากลับไปแสดงหน้า new ใหม่</p>
<p>ทีนี้ พวกข้อผิดพลาดต่าง ๆ ที่เกิดขึ้นระหว่างการตรวจสอบนั้น เราสามารถสั่งให้แสดงใน view ได้โดยเรียก <tt>error_messages_for</tt> สำหรับแสดงข้อผิดพลาดโดยรวมทั้งหมดของข้อมูลนั้น ๆ และ <tt>error_message_on</tt> ซึ่งจะแสดงข้อผิดพลาดเฉพาะ field ไป   ตัวอย่างการใช้แสดงใน view <tt>new.html.erb</tt> ด้านล่าง<br />
<pre class="brush: xml;">
&lt;h1&gt;New Book&lt;/h1&gt;

&lt;%= error_messages_for :book %&gt;

&lt;% form_for :book,@book,:url =&gt;{:action =&gt; 'create'} do |f| %&gt;

  Title: &lt;%= f.text_field :title %&gt;   
  &lt;%= error_message_on @book, 'title', 'The title ' %&gt;
  &lt;br/&gt;
  Pages: &lt;%= f.text_field :pages %&gt;
  &lt;%= error_message_on @book, 'pages', 'The number of pages ' %&gt;
  &lt;br/&gt;
  &lt;%= submit_tag %&gt;
&lt;% end %&gt;
</pre></p>
<p>สังเกตการใช้งาน <tt>error_messages_for</tt> ที่อยู่ตรงหัว และ <tt>error_message_on</tt> ที่อยู่บริเวณ field ต่าง ๆ นะครับ  ในส่วนของ error_message_on เรามีการแก้ให้แทนที่จะเรียก field ด้วยชื่อตรง ๆ ก็ให้เรียกเป็นภาษาที่ดูเป็นภาษาคนเสียหน่อยครับ  ภาพด้านล่างแสดงตัวอย่างเวลาเกิด error ครับ (ปกติจะมีสีสรรค์สวยกว่านี้ครับ แต่ css ผมว่างเปล่าเลยดูไม่สวยเท่าใดครับ)</p>
<p><img src="http://jittat.files.wordpress.com/2009/08/rails_ex-validation.png?w=300&#038;h=194" alt="rails_ex-validation" title="rails_ex-validation" width="300" height="194" class="alignnone size-full wp-image-144" /></p>
<p>ทีนี้ เหลือ validation อีกอันที่เราต้องการทำพิเศษ (custom) ก็ทำไม่ยากครับ  วิธีการก็คือไปเขียนเมท็อดสำหรับตรวจสอบไว้ โดยเมท็อดนี้ถ้าพอข้อผิดพลาดก็ให้ใส่ข้อผิดพลาดนั้นลงใน attribute <tt>errors</tt> ด้วยเมท็อด <tt>add</tt> ครับ   จากนั้นก็ไปบอก Active Record ให้ทำ validation ที่เราสร้างขึ้นมาโดยสั่ง <tt>validate ชื่อเมท็อด</tt> ทีตรงหัวโมเดล  ตัวอย่างในด้านล่างครับ</p>
<p><pre class="brush: ruby;">
class Book &lt; 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] &lt; ?A or self.title[0] &gt; ?Z
      # เพิ่ม errors
      errors.add &amp;quot;title&amp;quot;,&amp;quot;must begin with a capital letter&amp;quot;  
    end
  end
end
</pre></p>
<p>สังเกตว่าค่าที่คืนจากเมท็อดนี้จะไม่ถูกนำไปใช้ สิ่งที่สนใจคือ attribute <tt>errors</tt> อย่างเดียวเท่านั้น  นอกจากการประกาศข้อผิดพลาดที่เกิดกับ field แล้ว เรายังเพิ่มข้อผิดพลาดให้แสดงในภาพรวมได้ ด้วยเมท็อด <tt>add_to_base</tt> ได้</p>
<p>โดยสรุปก็คือระบบ validation ของ Rails จะเริ่มที่การประกาศไว้ใน model (ด้วย <tt>validates_*</tt>) เรียกตรวจสอบใน controller (ด้วย <tt>save</tt> หรือ <tt>valid?</tt>) แล้วไปแสดงผลที่ใน view (ด้วย <tt>error_messages_for</tt> กับ <tt>error_message_on</tt>)</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/142/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=142&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/08/08/%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%83%e0%b8%8a%e0%b9%89%e0%b8%87%e0%b8%b2%e0%b8%99-validation/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>

		<media:content url="http://jittat.files.wordpress.com/2009/08/rails_ex-validation.png" medium="image">
			<media:title type="html">rails_ex-validation</media:title>
		</media:content>
	</item>
		<item>
		<title>การใช้ partial และ helper</title>
		<link>http://jittat.wordpress.com/2009/08/08/%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%83%e0%b8%8a%e0%b9%89-partial-%e0%b9%81%e0%b8%a5%e0%b8%b0-helper/</link>
		<comments>http://jittat.wordpress.com/2009/08/08/%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%83%e0%b8%8a%e0%b9%89-partial-%e0%b9%81%e0%b8%a5%e0%b8%b0-helper/#comments</comments>
		<pubDate>Fri, 07 Aug 2009 19:12:34 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=135</guid>
		<description><![CDATA[Rails มีวิธีที่เราสามารถใช้เพื่อทำให้ view ของเราอ่านและจัดการได้ง่ายอยู่หลายวิธี  เราจะแสดงตัวอย่างโดยค่อย ๆ แก้ view ของเราด้านล่างนี้ที่ใช้แสดงข้อมูลของหนังสือกับผู้เขียนครับ<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=135&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกจาก <a href="http://www.rails66.com/blog/?p=424">http://www.rails66.com/blog/?p=424</a>)</p>
<p>Rails มีวิธีที่เราสามารถใช้เพื่อทำให้ view ของเราอ่านและจัดการได้ง่ายอยู่หลายวิธี  </p>
<p>เราจะแสดงตัวอย่างโดยค่อย ๆ แก้ view ของเราด้านล่างนี้ที่ใช้แสดงข้อมูลของหนังสือกับผู้เขียนครับ</p>
<p><pre class="brush: xml;">
&lt;ul&gt;
  &lt;% @books.each do |book| %&gt;
    &lt;li&gt;
      &lt;b&gt;&lt;%= book.title %&gt;&lt;/b&gt;&lt;br/&gt;
      Written by:
      &lt;%= (book.authors.collect do |a| 
             &amp;quot;#{a.first_name} #{a.last_name}&amp;quot;
           end).join(&amp;quot;,&amp;quot;) %&gt;
    &lt;/li&gt;
  &lt;% end %&gt;
&lt;ul&gt;
</pre></p>
<p>หน้าตาของ view เมื่อแสดงผลแล้วเป็นดังด้านล่างครับ</p>
<p><img src="http://jittat.files.wordpress.com/2009/08/rails_partial.png?w=281&#038;h=82" alt="rails_partial" title="rails_partial" width="281" height="82" class="alignnone size-full wp-image-137" /></p>
<p>view นี้รับรายการ <tt>@books</tt> ที่ค้นมาจาก controller  วัตถุ <tt>Book</tt> จะมีความสัมพันธ์ไปยังโมเดล <tt>Writer</tt> ผ่านทาง collection <tt>authors</tt>  โดยวัตถุคลาส <tt>Writer</tt> จะมี <tt>first_name</tt> กับ <tt>last_name</tt></p>
<p>view ด้านบนอาจจะดูสั้น ๆ แล้วไม่ซับซ้อนมากนัก แต่ว่ามีสิ่งที่เราทำให้ดีขึ้นได้หลายอย่าง  </p>
<p>อย่างแรกคือใน view นี้มีโปรแกรม Ruby ที่ใช้จัด format รายการผู้เขียนเพื่อแสดงผล  (บรรทัด 6-8) กล่าวคือในรายการผู้เขียน ถ้ามีหลายคนเราต้องการคั่นระหว่างชื่อด้วยเครื่องหมายลูกน้ำ (&#8220;,&#8221;)  ใน view ข้างต้นก็เลยมีการเรียก <tt>book.authors.collect</tt> เพื่อเอาชื่อ (first_name) กับนามสกุล (last_name) มาต่อกัน แล้วมาเรียก join อีกที</p>
<p>เราจะย้ายส่วนของโปรแกรมนี้ออกไปนอก view ครับ โดยที่ที่เหมาะสมสำหรับมันก็คือใน helper  (โดยปกติสำหรับ controller หนึ่ง ๆ จะมีโมดูล helper หนึ่งโมดูล  โดย Rails จะสร้าง <tt>/app/helpers/ชื่อ_helper.rb</tt> ไว้ให้ครับ)  ในที่นี้เราจะใส่ไว้ที่ <tt>books_helper.rb</tt> ดังด้านล่างครับ</p>
<p><pre class="brush: ruby;">
module BooksHelper
  def format_authors(authors)
    authornames = authors.collect do |a| 
      &amp;quot;#{a.first_name} #{a.last_name}&amp;quot;
    end
    authornames.join(&amp;quot;, &amp;quot;)
  end
end
</pre></p>
<p>แล้วเราก็ไปตัดส่วนดังกล่าวออกจาก view ครับ ได้ผลดังด้านล่าง<br />
<pre class="brush: xml;">
&lt;ul&gt;
  &lt;% @books.each do |book| %&gt;
    &lt;li&gt;
      &lt;b&gt;&lt;%= book.title %&gt;&lt;/b&gt;&lt;br/&gt;
      Written by: &lt;%= format_authors(book.authors) %&gt;
    &lt;/li&gt;
  &lt;% end %&gt;
&lt;ul&gt;
</pre></p>
<p>ทีนี้สังเกตว่า ถ้าในการแสดงผลหนังสือแต่ละเล่มค่อนข้างซับซ้อน จะทำให้ view นี้อ่านยาก (แต่ในกรณีของเราค่อนข้างอ่านง่ายแล้ว)    เราสามารถย้ายส่วนดังกล่าวออกไปใส่ไว้ใน view เล็ก ๆ ที่ Rails เรียกว่า <b>partial</b> ได้  โดยเราจะสร้าง partial ชื่อ <tt>book</tt> โดยเก็บไว้ในแฟ้มชื่อ <b><tt>_book.html.erb</tt></b> ในไดเร็กทอรีเดียวกับ view เดิม  </p>
<p>สังเกตว่า partial ชื่อ <tt>book</tt> แต่ชื่อไฟล์จะขึ้นต้นด้วยขีดล่าง เป็น <tt>_book.html.erb</tt> นะครับ  ใน partial ดังกล่าวก็ตัดส่วนที่แสดงผล book ออกมาเลย ดังด้านล่าง<br />
<pre class="brush: xml;">
  &lt;b&gt;&lt;%= book.title %&gt;&lt;/b&gt;&lt;br/&gt;
  Written by: &lt;%= format_authors(book.authors) %&gt;
</pre></p>
<p>โดยปกติแล้ว partial จะได้รับตัวแปรภายในมาจาก view ด้วย (เช่นพวก <tt>@books</tt>) ในกรณีนี้ เราใช้ตัวแปร <tt>book</tt> ที่อ้างมาจากในวนรอบข้างต้นครับ ดังนั้นเราจะต้องส่งตัวแปรพวกนี้ให้กับ partial เอง   ใน view หลัก เราจะเรียก <b><tt>render :partial</tt></b> โดยส่งค่าตัวแปร <tt>book</tt> ไปด้วย ผ่านทาง option <tt>locals</tt> ดังด้านล่างครับ</p>
<p><pre class="brush: xml;">
&lt;ul&gt;
  &lt;% @books.each do |book| %&gt;
    &lt;li&gt;
      &lt;%= render :partial =&gt; 'book', 
                  :locals =&gt; {:book =&gt; book} %&gt;
    &lt;/li&gt;
  &lt;% end %&gt;
&lt;ul&gt;
</pre></p>
<p>นอกจากที่เราจะใช้ partial เพื่อแสดงผลบางส่วนของ view แล้ว  คำสั่ง <tt>render :partial</tt> ยังมีอีก option หนึ่งที่มีประโยชน์มาก ก็คือการสั่งให้แสดงให้ทุก ๆ ข้อมูลในรายการ</p>
<p>จากตัวอย่างข้างต้น view หลักเราสามารถเขียนใหม่เหลือแค่</p>
<p><pre class="brush: xml;">
&lt;ul&gt;
  &lt;%= render :partial =&gt; 'book', :collection =&gt; @books %&gt;
&lt;ul&gt;
</pre></p>
<p>สังเกตว่าเราไม่ต้องมานั่ง for อีกต่อไปแล้ว</p>
<p>การทำงานของ render collection จะวิ่งเข้าไปในรายการแล้วเรียก render ข้อมูลแต่ละตัว  เนื่องจากเราต้องการครอบทุก ๆ ข้อมูลด้วยแท็ก <tt>&lt;li&gt;</tt>  เราจึงต้องย้ายไปไว้ใน partial <tt>_book.html.erb</tt> ดังด้านล่างครับ</p>
<p><pre class="brush: xml;">
 &lt;li&gt;
   &lt;b&gt;&lt;%= book.title %&gt;&lt;/b&gt;&lt;br/&gt;
   Written by: &lt;%= format_authors(book.authors) %&gt;
 &lt;/li&gt;
</pre></p>
<p>ในการเรียกใช้ render collection เราใช้ข้อตกลงอย่างหนึ่งครับ นั่นคือข้อมูลแต่ละตัวจาก collection จะถูกส่งไปยัง partial ด้วยชื่อตัวแปรที่เป็นชื่อของ partial ครับ เช่น ในกรณีนี้คือตัวแปร <tt>book</tt> ครับ</p>
<p>ขอแถมนิดหนึ่งครับ ถ้าเขียน partial ด้วย <a href="http://haml.hamptoncatlin.com/">haml</a> ผลที่ได้จะยิ่งโล่งโปร่งสบายเข้าไปอีก ดังด้านล่างครับ</p>
<pre>
%li
  %b= book.title
  %br/
  Written by:
  = format_authors(book.authors)
</pre>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/135/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/135/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/135/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/135/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/135/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/135/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/135/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/135/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=135&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/08/08/%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%83%e0%b8%8a%e0%b9%89-partial-%e0%b9%81%e0%b8%a5%e0%b8%b0-helper/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>

		<media:content url="http://jittat.files.wordpress.com/2009/08/rails_partial.png" medium="image">
			<media:title type="html">rails_partial</media:title>
		</media:content>
	</item>
		<item>
		<title>สนุกกับ Active Record: ใช้ Active Record และโมเดลนอก Rails</title>
		<link>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b9%83%e0%b8%8a%e0%b9%89-active-record-%e0%b9%81%e0%b8%a5%e0%b8%b0%e0%b9%82%e0%b8%a1%e0%b9%80%e0%b8%94%e0%b8%a5/</link>
		<comments>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b9%83%e0%b8%8a%e0%b9%89-active-record-%e0%b9%81%e0%b8%a5%e0%b8%b0%e0%b9%82%e0%b8%a1%e0%b9%80%e0%b8%94%e0%b8%a5/#comments</comments>
		<pubDate>Wed, 22 Jul 2009 15:14:58 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=104</guid>
		<description><![CDATA[Active Record ก็เป็นชุดไลบรารีที่สามารถใช้งานได้นอกเหนือจากภายในโปรแกรมประยุกต์บน Rails ครับ<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=104&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกจาก <a href="http://www.rails66.com/blog/?p=412">http://www.rails66.com/blog/?p=412</a>)</p>
<p>Active Record ก็เป็นชุดไลบรารีที่สามารถใช้งานได้นอกเหนือจากภายในโปรแกรมประยุกต์บน Rails ครับ</p>
<p>ทีนี้ มีการใช้งานหลัก ๆ สองแบบ คือแบบที่เราอ้างถึงฐานข้อมูลโดยไม่เกี่ยวข้องกับโมเดลใน Rails เลย กับแบบที่ต้องการใช้โมเดลที่สร้างไว้ใน Rails ด้วย</p>
<p>เอาแบบแรกก่อนครับ</p>
<p>ในการใช้ก็ต้องโหลดตัวไลบรารีมาก่อน</p>
<p><pre class="brush: ruby;">
require 'rubygems'
require 'active_record'
</pre></p>
<p>จากนั้นก็สั่งเปิด connection เองเลยครับ เช่น</p>
<p><pre class="brush: ruby;">
ActiveRecord::Base.establish_connection({
      :adapter =&gt; &quot;sqlite3&quot;, 
      # ใส่ชื่อไฟล์ฐานข้อมูล
      :database =&gt; &quot;/home/test-ar/db/development.sqlite3&quot;   
})
</pre></p>
<p>ในกรณีที่ใช้ sqlite3 หรือเช่นด้านล่าง ในกรณีที่ใช้ mysql</p>
<p><pre class="brush: ruby;">
ActiveRecord::Base.establish_connection(
  :adapter  =&gt; &quot;mysql&quot;,
  :host     =&gt; &quot;localhost&quot;,
  :username =&gt; &quot;me&quot;,
  :password =&gt; &quot;secret&quot;,
  :database =&gt; &quot;activerecord&quot;
)
</pre></p>
<p>พอสร้าง connection เสร็จ ก็สร้างคลาสลูกหลานของ <tt>ActiveRecord::Base</tt> แล้วก็เรียกมาใช้ได้เลยครับ เช่น</p>
<p><pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
end

Book.find(:all).each do |book|
  puts &quot;#{book.title}&quot;
end
</pre></p>
<p>เราสามารถใช้การเชื่อมโยงต่าง ๆ เช่น <tt>has_many</tt>, <tt>belongs_to</tt> ได้เหมือนปกติครับ</p>
<p>ทีนี้ ถ้าเราต้องการใช้โมเดลจาก Rails ด้วย เราจะต้องโหลดโมเดลและอื่น ๆ มาด้วย หลัก ๆ ก็คือไปเรียก <tt>config/environment</tt> มาครับ นอกจากนี้ ถ้าเราต้องการเลือก environment ที่จะทำงาน ว่าจะเป็น development, production หรือ test ก็สามารถทำได้ โดยกำหนดค่าลงไปที่ <tt>ENV["RAILS_ENV"]</tt> ครับ เช่นสั่ง</p>
<p><pre class="brush: ruby;">
# กำหนด environment ถ้าไม่กำหนดจะเป็น development โดยอัตโนมัติ
ENV[&quot;RAILS_ENV&quot;] = &quot;development&quot;
RAILS_PATH = '/home/jittat/prog/rails/test-ar'
require File.join(RAILS_PATH, &quot;config/environment&quot;)
</pre></p>
<p>ต้องระวังนิดหน่อยถ้าเราต้องการเรียกใช้ข้อมูลในโมเดล พร้อม ๆ กับที่โปรแกรมบนเว็บของเราทำงาน  ถ้ามีการแก้ไขข้อมูลพร้อมกันจะอย่าลืมจัดการเรื่องการ lock ตารางด้วยครับ (ดูเพิ่มได้ที่ <a href="http://api.rubyonrails.com/classes/ActiveRecord/Locking.html">ActiveRecord::Locking</a>)</p>
<p><b>เอกสารอ้างอิง/ลิงก์เพิ่มเติม</b></p>
<ul>
<li><a href="http://wiki.rubyonrails.org/rails/pages/HowToUseActiveRecordOutsideRails">วิกิ HowToUseActiveRecordOutsideRails</a></li>
<li><a href="http://ar.rubyonrails.com/">หน้า api ของ Active Record</a></li>
</ul>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/104/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/104/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/104/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/104/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/104/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/104/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/104/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/104/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=104&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b9%83%e0%b8%8a%e0%b9%89-active-record-%e0%b9%81%e0%b8%a5%e0%b8%b0%e0%b9%82%e0%b8%a1%e0%b9%80%e0%b8%94%e0%b8%a5/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>สนุกกับ Active Record: การเชื่อมโยงแบบ many-to-many (2)</title>
		<link>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81-2/</link>
		<comments>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81-2/#comments</comments>
		<pubDate>Wed, 22 Jul 2009 15:10:59 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=102</guid>
		<description><![CDATA[ในตอนก่อนเราได้ดูการจัดการการเชื่อมโยงแบบ many-to-many โดยใช้ <tt>has_and_belongs_to_many</tt> ไปแล้ว  ในตอนนี้เราจะพิจารณาอีกวิธีหนึ่ง ซึ่งดูแล้วใช้ง่ายกว่า (แล้วทำไมไม่เขียนในตอนแรกนะ?)

เราจะพิจารณาตัวอย่างเพิ่มเติม โดยเพิ่มโมเดลผู้อ่าน (<tt>Reader</tt>) เข้าไป จากนั้นเราจะสร้างความสัมพันธ์แบบ many-to-many ระหว่างวัตถุในโมเดล <tt>Reader</tt> กับโมเดล <tt>Book</tt> ด้วยโมเดล <tt>Reading</tt> ที่นอกจากจะเชื่อมวัตถุในโมเดลทั้งสองเข้าด้วยกันแบบ many-to-many แล้วยังเก็บข้อมูลลงไปในความสัมพันธ์ด้วยว่าผู้อ่านนั้นอ่านหนังสือเล่มนั้นเมื่อใด<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=102&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกจาก <a href="http://www.rails66.com/blog/?p=381">http://www.rails66.com/blog/?p=381</a>)</p>
<p>จาก<a href="http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81/">ตอนที่แล้ว</a>เราได้ดูการจัดการการเชื่อมโยงแบบ many-to-many โดยใช้ <tt>has_and_belongs_to_many</tt> ไปแล้ว  ในตอนนี้เราจะพิจารณาอีกวิธีหนึ่ง ซึ่งดูแล้วใช้ง่ายกว่า (แล้วทำไมไม่เขียนในตอนแรกนะ?)</p>
<p>เราจะพิจารณาตัวอย่างเพิ่มเติม โดยเพิ่มโมเดลผู้อ่าน (<tt>Reader</tt>) เข้าไป จากนั้นเราจะสร้างความสัมพันธ์แบบ many-to-many ระหว่างวัตถุในโมเดล <tt>Reader</tt> กับโมเดล <tt>Book</tt> ด้วยโมเดล <tt>Reading</tt> ที่นอกจากจะเชื่อมวัตถุในโมเดลทั้งสองเข้าด้วยกันแบบ many-to-many แล้วยังเก็บข้อมูลลงไปในความสัมพันธ์ด้วยว่าผู้อ่านนั้นอ่านหนังสือเล่มนั้นเมื่อใด</p>
<p>หลักการคร่าว ๆ ก็คือวัตถุในโมเดล <tt>Reading</tt> จะเป็นสมาชิกของทั้งโมเดล <tt>Reader</tt> และโมเดล <tt>Book</tt> ทำให้วัตถุในโมเดลทั้งสองสามารถ <tt>has_many</tt>  <tt>Reading</tt> ได้พร้อม ๆ กัน จากตรงนี้นี่เองที่ทำให้เกิดความสัมพันธ์แบบ many-to-many ได้</p>
<p>อย่างไรก็ตาม ถ้าทำแค่นั้นการอ้างถึง <tt>Reader</tt> จาก <tt>Book</tt> จะต้องอ้างผ่านทางวัตถุของโมเดล <tt>Reading</tt>  ใน Active Record ได้ทุ่นแรงเราด้วย option <tt>:through</tt> ที่จะทำให้การอ้างผ่านของเราเป็นไปได้อย่างอัตโนมัติ</p>
<p>ท่าที่เราจะทำนี้ ตอนต้นจะคล้าย ๆ กับท่าแรกในบทความ <a href="http://www.grails66.com/blog/?p=372">การจัดการ GORM Relationship แบบ Many-to-Many</a> ที่ <a href="http://www.grails66.com/blog">grails66</a> ครับ แต่ตรงส่วน <tt>:through</tt> จะทำให้การอ้างถึงง่ายขึ้น</p>
<p>เริ่มเลยแล้วกันครับ</p>
<p>ไปสร้างโมเดล <tt>Reader</tt> กันก่อน  ให้มี field <tt>name</tt> เป็นสตริง</p>
<p>จากนั้นสร้างโมเดล <tt>Reading</tt> ด้วย migration ด้านล่าง  สังเกตว่านอกจาก foreign key ทั้งสองแล้ว เรายังมี field <tt>read_at</tt> ไว้ด้วย</p>
<p><pre class="brush: ruby;">
class CreateReadings &lt; ActiveRecord::Migration
  def self.up
    create_table :readings do |t|
      t.column &quot;book_id&quot;, :integer
      t.column &quot;reader_id&quot;, :integer
      t.column &quot;read_at&quot;, :datetime
      t.timestamps
    end
  end
  # ... ละไว้ ...
end
</pre></p>
<p>สังเกตว่าด้วยโมเดล <tt>Reading</tt> ที่อยู่ในทั้งสองโมเดล เราสามารถใช้ <tt>belongs_to</tt> กับ <tt>has_many</tt> เชื่อมวัตถุในโมเดลนี้เข้ากับอีกสองโมเดลได้</p>
<p>เราไปแก้ <tt>book.rb</tt>, <tt>reader.rb</tt>, และ <tt>reading.rb</tt> โดยใส่ความสัมพันธ์ดังกล่าวลงไปครับ </p>
<p><pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
  #.. ละไว้ ..
  has_many :readings                            
end
</pre></p>
<p><pre class="brush: ruby;">
class Reader &lt; ActiveRecord::Base
  has_many :readings
end
</pre></p>
<p><pre class="brush: ruby;">
class Reading &lt; ActiveRecord::Base
  belongs_to :reader
  belongs_to :book
end
</pre></p>
<p>แค่นี้ก็พอทดลองได้แล้วครับ</p>
<pre>
# สร้างหนังสือ Rails มาอีกสักเล่ม
&gt;&gt; <b>ror = Book.create(:title =&gt; "Ruby on Rails", :pages =&gt; 400)</b>
=&gt; #&lt;Book id: 5, title: "Ruby on Rails", pages: 400, published_at: nil,.. &gt;

# สร้างคนอ่านอีกสองคน (รักคนอ่าน อิอิ)
&gt;&gt; <b>black = Reader.create :name =&gt; "Black"</b>
=&gt; #&lt;Reader id: 2, name: "Black",..&gt;
&gt;&gt; <b>pink = Reader.create :name =&gt; "Pink"</b>
=&gt; #&lt;Reader id: 3, name: "Pink",..&gt;

# จับสองคนนี้ให้อ่าน RoR โดยสร้างวัตถุของโมเดล Reading
&gt;&gt; <b>Reading.create(:reader =&gt; pink, :book =&gt; ror, :read_at =&gt; Time.now)</b>
&gt;&gt; <b>Reading.create(:reader =&gt; black, :book =&gt; ror, :read_at =&gt; Time.now-1.day)</b>

# ทดลองดูที่ RoR
&gt;&gt; <b>ror.readings</b>
=&gt; [#&lt;Reading id: 1, book_id: 5, reader_id: 3,
read_at: "2008-09-29 02:45:30", ..&gt;, #&lt;Reading id: 2, book_id: 5,
reader_id: 2, read_at: "2008-09-28 02:45:51",..&gt;]

# เอาคนอ่านออกมา โดยเรียกผ่านทาง readings
# เมท็อด collect จะวิ่งไล่ไปในรายการ แล้วก็ทำงานของในรายการ
#   ตาม block ที่ระบุไว้แล้วคือรายการของผลลัพธ์ออกมา
#   ในที่นี้ block  { |r| r.reader } ระบุว่าให้รับของมาใส่ในตัวแปร r
#   แล้วเรียกเมท็อด reader
&gt;&gt; <b>ror.readings.collect {|r| r.reader }</b>
=&gt; [#&lt;Reader id: 3, name: "Pink", ..&gt;,
#&lt;Reader id: 2, name: "Black", ..&gt;]

# ทดลองกับหนังสือบ้าง
&gt;&gt; <b>algo = Book.find_by_title "Algorithms"</b>
=&gt; #&lt;Book id: 4, title: "Algorithms", pages: 1000, ..&gt;

# ให้ Mr.Pink อ่าน algo เพิ่มด้วย
&gt;&gt; <b>Reading.create :reader =&gt; pink, :book =&gt; algo, :read_at =&gt; Time.now - 1.year</b>

# ดูรายการของ Mr.Pink
&gt;&gt; <b>pink.readings</b>
=&gt; [#&lt;Reading id: 1, book_id: 5, reader_id: 3, ..&gt;,
#&lt;Reading id: 3, book_id: 4, reader_id: 3, ..&gt;]
</pre>
<p>แค่นี้ก็ทำ many-to-many โดยผ่านอีกโมเดลหนึ่งได้แล้ว</p>
<p>อย่างไรก็ตาม ที่น่าสนใจคือตรงขั้น<br />
<pre class="brush: ruby;">
&gt;&gt; ror.readings.collect {|r| r.reader }
</pre><br />
ใน Active Record มี option พิเศษใน <b>has_many</b> ที่ทำให้เราระบุการเชื่อมต่อ ผ่าน อีกโมเดลในลักษณะข้างต้นได้อย่างสะดวก นั่นคือ option <tt>:through</tt></p>
<p>เราไปปรับโมเดล <tt>Reader</tt> และ <tt>Book</tt> โดยเพิ่ม <tt>has_many :through</tt>  เข้าไปครับ</p>
<p><pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
  # .. ละไว้ ..
  has_many :readers, :through =&gt; :readings
  has_many :readings
end
</pre></p>
<p><pre class="brush: ruby;">
class Reader &lt; ActiveRecord::Base
  has_many :readings
  has_many :books, :through =&gt; :readings
end
</pre></p>
<p>ตัว option <tt>:through =&gt; :readings</tt> ที่ใส่ไประบุว่าความสัมพันธ์ <tt>readers</tt> หรือ <tt>books</tt> เนี่ยะ ให้ไปวิ่งผ่านความสัมพันธ์ <tt>readings</tt></p>
<p>แก้แล้วก็เข้า <tt>script/console</tt> ใหม่ เพื่อทดลองอีกสักหน่อย</p>
<pre>
&gt;&gt; <b>pink = Reader.find_by_name "Pink"</b>
=&gt; #&lt;Reader id: 3, name: "Pink", ..&gt;

&gt;&gt; <b>pink.books</b>
=&gt; [#&lt;Book id: 5, title: "Ruby on Rails", ..&gt;,
#&lt;Book id: 4, title: "Algorithms", ..&gt;]
</pre>
<p>ทดลองลบสักหน่อยครับ</p>
<pre>
&gt;&gt; <b>ror = pink.books[0]</b>
=&gt; #&lt;Book id: 5, title: "Ruby on Rails", ..&gt;

&gt;&gt; <b>pink.books.delete(ror)</b>

# หายไปแล้ว
&gt;&gt; <b>pink.books</b>
=&gt; [#&lt;Book id: 4, title: "Algorithms", ..&gt;]
&gt;&gt; <b>ror.readers</b>
=&gt; [#&lt;Reader id: 2, name: "Black", ..&gt;]
</pre>
<p>หมายเหตุเพิ่มเติมเล็กน้อย สังเกตว่าครั้งก่อน ๆ บางทีที่เราเรียก associateion แล้วเราต้องใส่อาร์กิวเมนต์ <tt>true</tt> เข้าไป เพื่อให้มัน reload ใหม่ แต่ที่ทดลองครั้งนี้ไม่ได้ใส่เลย เพราะว่าเวลาเราเรียก <tt>ror.readers</tt> เช่นด้านบน ความสัมพันธ์ <tt>readers</tt> ยังไม่ถูกอ่านมาก่อน เมื่อตอนเราต้น ror มา แต่จะถูกอ่านเมื่อเราเรียกใช้  เราสามารถใส่ option <tt>:include</tt> ตอนสั่ง <tt>find</tt> ได้เพื่อกำหนดให้ Active Record อ่านเอาความสัมพันธ์นี้มาเลยตั้งแต่ตอนค้นวัตถุ</p>
<p>สมมติว่ามี Reader คนหนึ่งอ่าน <tt>ror</tt> ซ้ำหลายรอบ เวลาเรียก <tt>ror.readers</tt> เราจะได้ Reader นั้นออกมาซ้ำ ๆ กันด้วย ถ้าต้องการแค่ครั้งเดียว เราสามารถเพิ่ม option <tt>:uniq =&gt; true</tt> เข้าไปตอนนิยาม <tt>has_many</tt> ได้ดังด้านล่างครับ</p>
<p><pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
  # .. ละไว้ ..
  has_many :readers, :through =&gt; :readings, :uniq =&gt; true
  has_many :readings
end
</pre></p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/102/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/102/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/102/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/102/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/102/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/102/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/102/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/102/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=102&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>สนุกกับ Active Record: การเชื่อมโยงแบบ many-to-many (1)</title>
		<link>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81/</link>
		<comments>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81/#comments</comments>
		<pubDate>Wed, 22 Jul 2009 14:56:59 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=99</guid>
		<description><![CDATA[เราได้ทดลองสร้างความสัมพันธ์ระหว่างโมเดล <tt>Writer</tt> กับโมเดล <tt>Book</tt> เอาไว้ โดยเป็นความสัมพันธ์แบบ one-to-many โดยให้ตารางของโมเดล <tt>Book</tt> เก็บ foreign key เป็น id ของวัตถุในโมเดล <tt>Writer</tt> เอาไว้

แต่จริง ๆ หนังสือเล่มหนึ่งอาจมีนักเขียนได้หลายคน และนักเขียนคนหนึ่งก็อาจจะเขียนหนังสือหลายเล่ม ทำให้ความสัมพันธ์ระหว่างโมเดลทั้งสองนี้ กลายเป็นความสัมพันธ์แบบ many-to-many ไป<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=99&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกมาจาก <a href="http://www.rails66.com/blog/?p=359">http://www.rails66.com/blog/?p=359</a>)</p>
<p><a>คราวก่อนโน้น</a> เราได้ทดลองสร้างความสัมพันธ์ระหว่างโมเดล <tt>Writer</tt> กับโมเดล <tt>Book</tt> เอาไว้ โดยเป็นความสัมพันธ์แบบ one-to-many โดยให้ตารางของโมเดล <tt>Book</tt> เก็บ foreign key เป็น id ของวัตถุในโมเดล <tt>Writer</tt> เอาไว้</p>
<p>แต่จริง ๆ หนังสือเล่มหนึ่งอาจมีนักเขียนได้หลายคน และนักเขียนคนหนึ่งก็อาจจะเขียนหนังสือหลายเล่ม ทำให้ความสัมพันธ์ระหว่างโมเดลทั้งสองนี้ กลายเป็นความสัมพันธ์แบบ many-to-many ไป</p>
<p>เนื่องจากข้อจำกัดของฐานข้อมูลแบบ relational ที่ว่าหนึ่งแถวในแต่ละฟิลด์จะเก็บข้อมูลได้แค่ตัวเดียว การจะใส่ foreign key ของ  book_id เข้าไปในตาราง <tt>writers</tt> คงจะไม่ทำให้เราสามารถสร้างความสัมพันธ์ดังกล่าวได้</p>
<p>ดังนั้นวิธีที่เราทำได้ใน &#8220;ระดับฐานข้อมูล&#8221; คือการสร้างตารางขึ้นมาอีกตารางหนึ่ง เพื่อเชื่อมโยงวัตถุทั้งสองโมเดลเข้าด้วยกัน  ตารางดังกล่าวเรียกว่า join table แสดงรูปได้ดังด้านล่าง</p>
<pre>
                             ------------            --------------
  -------------                JoinTable                  Book
      Writer                 ============            ==============
  =============              - book_id ------------&gt; - id
  - id         &lt;------------ - writer_id             - title
  - first_name               ------------            - pages
  - last_name                                        - published_at
  -------------                                      --------------
</pre>
<p>สังเกตว่าตารางดังกล่าวจะเชื่อมโยงวัตถุสองวัตถุจากสองโมเดลเข้าด้วยกันครับ  ถ้าสองวัตถุใดเชื่อมกัน เราก็จะมีแถวในตาราง join table นี้ที่ชี้ไปหาทั้งสองวัตถุนั้น</p>
<p>ทีนี้เราจะทำใน Active Record อย่างไร?</p>
<p>มีสองวิธีหลัก ๆ ขึ้นกับว่าเราต้องการให้ join table นี้เป็นโมเดลในระบบของเราด้วยหรือไม่  เช่นในกรณีที่ความสัมพันธ์ระหว่างวัตถุจากสองโมเดลมีข้อมูลอื่น ๆ ที่ต้องการเก็บด้วย เพื่อที่จะอ้างถึงข้อมูลเพิ่มเติมเหล่านี้ เราก็จะต้องสร้างโมเดลขึ้นมาครอบตารางนั้นไว้</p>
<p>สำหรับตอนแรกนี้ เราจะสนใจเฉพาะการเชื่อมสองโมเดลเข้าด้วยกันเท่านั้นก่อน  ดังนั้นตาราง join table ที่เราสร้างขึ้นจะทำให้การเชื่อมโยงเกิดขึ้นได้แต่จะมองไม่เห็นจาก Active Record</p>
<p>วิธีนี้จะระบุการเชื่อมโยงด้วย <tt>has_and_belongs_to_many</tt> (ยาวมาก นิยมเขียนย่อว่า <tt>habtm</tt>)  ซึ่งค่อนข้างยุ่งสักหน่อย  ไว้<a href="http://www.rails66.com/blog/?p=381">ตอนหน้า</a>เราจะดูอีกวิธีในกรณีที่ join table ของเราเป็นโมเดลด้วย ซึ่งจะทำให้ได้การเชื่อมโยงที่เข้าใจได้ง่ายกว่ามาก</p>
<p>ในการสร้าง join table เรามีข้อตกลงว่าชื่อของตารางจะต้องเป็นชื่อของตารางของโมเดลทั้งสองต่อกัน (คั่นด้วยขีดล่าง &#8220;_&#8221;) โดยเรียงชื่อตารางตามตัวอักษร  (เช่นเคย, ข้อตกลงนี้ปรับแต่งได้ครับ)</p>
<p>ดังนั้นในกรณีของเรา join table จะชื่อ <tt>books_writers</tt> เราไปสร้างตารางดังกล่าวด้วย migration ครับ</p>
<p><pre class="brush: ruby;">
class CreateJoinTableBooksWriters &lt; ActiveRecord::Migration
  def self.up
    create_table &quot;books_writers&quot;, :id =&gt; false do |t|
      t.column &quot;book_id&quot;, :integer
      t.column &quot;writer_id&quot;, :integer
    end
  end

  def self.down
    drop_table &quot;books_writers&quot;
  end
end
</pre></p>
<p>แล้วก็ เรียก migration ให้ทำงานด้วย <tt>rake db:migrate</tt></p>
<p>ทีนี้ เนื่องจากในการทดลองก่อน ๆ เราได้ไปสร้าง <tt>writer_id</tt> ไว้ในตาราง <tt>books</tt> เดี๋ยวเราต้องไปลบออกด้วย เพราะว่าไม่ได้ใช้แล้ว  ก็ไปสร้าง migration สำหรับลบคอลัมน์นั้นครับ  แน่นอนว่าความสัมพันธ์ที่เราเคยสร้างมาก็คงจะหายไปหมดแล้ว  (หมายเหตุ: เราสามารถเขียนใน migration ให้คัดลอกความสัมพันธ์เดิมมาได้ แต่ขอละไว้ครับ)</p>
<p><pre class="brush: ruby;">
class RemoveWriterIdFromBooks &lt; ActiveRecord::Migration
  def self.up
    remove_column &quot;books&quot;, &quot;writer_id&quot;
  end

  def self.down
    raise ActiveRecord::IrreversibleMigration
  end
end
</pre></p>
<p>สังเกตว่าใน migration นี้ตรง <tt>self.down</tt> เราสั่งให้เกิด exception <tt>ActiveRecord::IrreversibleMigration</tt> เพราะว่า migration นี้ย้อนกลับไม่ได้ (ถ้าต้องการให้ย้อนได้เราต้องแก้ข้อมูลกลับคืน ซึ่งในกรณีนี้ทำไม่ได้ เนื่องจากในการเปลี่ยนตารางกลับจาก many-to-many ไปเป็น one-to-many อาจมีข้อมูลสูญหาย)</p>
<p>เมื่อจัดการกับ migration เสร็จแล้วก็เรียกให้ทำงาน แล้วเราจะไปแก้ไขโมเดลกัน</p>
<p>ในโมเดล เราก็ไปประกาศความสัมพันธ์ด้วย <tt>has_and_belongs_to_many</tt>  (อย่าลืมลบความสัมพันธ์เก่า พวก <tt>has_many</tt>, <tt>belongs_to</tt> ออกด้วยนะครับ)  ได้ดังด้านล่างครับ</p>
<p>ที่โมเดล <tt>Book</tt><br />
<pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
  has_and_belongs_to_many :writers
end
</pre></p>
<p>ที่โมเดล <tt>Writer</tt><br />
<pre class="brush: ruby;">
class Writer &lt; ActiveRecord::Base
  has_and_belongs_to_many :books
  #... ละด้านล่างไว้..
end
</pre></p>
<p>ไปทดลองกันใน console ครับ</p>
<pre>
&gt;&gt; <b>Writer.find(:all)</b>    # เอาคนเขียนมาดูกันก่อน
=&gt; [#&lt;Writer id: 1, .., first_name: "John", last_name: "Madman"&gt;,
#&lt;Writer id: 2, .., first_name: "John", last_name: "Bestman"&gt;]
&gt;&gt; <b>john = Writer.find(1)</b>  # ขอ john คนแรก
=&gt; #&lt;Writer id: 1, .., first_name: "John", last_name: "Madman"&gt;

&gt;&gt; <b>Book.find(:all)</b>   # เอาหนังสือมาดูกัน
=&gt; [#&lt;Book id: 3, title: "AJAX Tricks", pages: 300, ..&gt;,
#&lt;Book id: 4, title: "Algorithms", pages: 1000, ..&gt;]

# เลือกหนังสือมาสองเล่ม
&gt;&gt; <b>algo_book = Book.find_by_title 'Algorithms'</b>     # =&gt; #&lt;Book id: 4, title: "Algorithms", ..&gt;
&gt;&gt; <b>ajax_book = Book.find(3)</b>     #  =&gt; #&lt;Book id: 3, title: "AJAX Tricks", ..&gt;

# ใส่ให้ john
&gt;&gt; <b>john.books &lt;&lt; algo_book</b>
&gt;&gt; <b>john.books &lt;&lt; ajax_book</b>

# ดูหนังสือที่ john เขียน
&gt;&gt; <b>john.books</b>
=&gt; [#&lt;Book id: 4, title: "Algorithms", pages: 1000, ..&gt;,
#&lt;Book id: 3, title: "AJAX Tricks", pages: 300, ..&gt;]

# สร้างนักเขียน mary (กลับมาใหม่ หลังจากลบไปคราวที่แล้ว)
&gt;&gt; <b>mary = Writer.create(:first_name =&gt; 'Mary', :last_name =&gt; 'Happy')</b>
=&gt; #&lt;Writer id: 4, .., first_name: "Mary", last_name: "Happy"&gt;

# ให้ mary เขียน ajax ด้วย
&gt;&gt; <b>ajax_book.writers &lt;&lt; mary</b>
=&gt; [#&lt;Writer id: 4,..., first_name: "Mary", last_name: "Happy"&gt;]

# ดูว่าใครเขียน ajax บ้าง (ทั้งหมด) เราใส่ true ไปเพื่อให้ reload ไม่เช่นนั้นจะเห็นแค่ mary, ไม่เห็น john
&gt;&gt; <b>ajax_book.writers(true)</b>
=&gt; [#&lt;Writer id: 1, .., first_name: "John", last_name: "Madman"&gt;,
#&lt;Writer id: 4, .., first_name: "Mary", last_name: "Happy"&gt;]
</pre>
<p>ทีนี้ถ้าจะลบของออกจากความสัมพันธ์ เราสามารถใช้ <tt>delete</tt> ที่ collection ได้เลย</p>
<pre>
&gt;&gt; <b>john.books.delete(algo_book)</b>   # ลบ algo ออกจากรายชื่อหนังสือของ john
&gt;&gt; <b>john.books</b>
=&gt; [#&lt;Book id: 3, title: "AJAX Tricks", ..&gt;]

&gt;&gt; <b>Book.find(:all)</b>    # แต่หนังสือยังอยู่ครบ
=&gt; [#&lt;Book id: 3, title: "AJAX Tricks", pages: 300, ..&gt;,
#&lt;Book id: 4, title: "Algorithms", pages: 1000, ..&gt;]

&gt;&gt; <b>algo_book.writers(true)</b>    # เหลือ mary เขียนคนเดียว
=&gt; [#&lt;Writer id: 4, .., first_name: "Mary", last_name: "Happy"&gt;]
</pre>
<p>นอกจากนี้เรายังค้นหาของที่อยู่ในความสัมพันธ์ได้ด้วย เช่นด้านล่าง</p>
<pre>
# หาเฉพาะหนังสือที่ mary เขียน ที่หนากว่า 400 หน้า
&gt;&gt; <b>mary.books.find(:all, :conditions =&gt; 'pages &gt; 400')</b>
=&gt; [#&lt;Book id: 4, title: "Algorithms", pages: 1000, ..&gt;]
</pre>
<p>ทีนี้ ถ้าเราต้องการเรียกคนเขียนว่า authors แทน ก็ทำได้โดยระบุ habtm ในคลาส <tt>Book</tt> ใหม่ดังด้านล่างครับ<br />
<pre class="brush: ruby;">
class Book &lt; ActiveRecord::Base
  has_and_belongs_to_many :authors,
                          :class_name =&gt; 'Writer'                            
end
</pre></p>
<p><a href="http://www.rails66.com/blog/?p=381">คราวหน้า</a>กลับมาดูการทำ many-to-many กันอีกวิธีครับ ซึ่งจะเป็นการใช้ options <tt>:through</tt> ของ <tt>has_many</tt> แทน</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/99/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/99/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/99/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/99/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/99/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/99/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/99/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/99/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=99&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87%e0%b9%81/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>ทำปลอม conversation context (เท่าที่ทำเป็น)</title>
		<link>http://jittat.wordpress.com/2009/07/22/%e0%b8%97%e0%b8%b3%e0%b8%9b%e0%b8%a5%e0%b8%ad%e0%b8%a1-conversation-context-%e0%b9%80%e0%b8%97%e0%b9%88%e0%b8%b2%e0%b8%97%e0%b8%b5%e0%b9%88%e0%b8%97%e0%b8%b3%e0%b9%80%e0%b8%9b%e0%b9%87%e0%b8%99/</link>
		<comments>http://jittat.wordpress.com/2009/07/22/%e0%b8%97%e0%b8%b3%e0%b8%9b%e0%b8%a5%e0%b8%ad%e0%b8%a1-conversation-context-%e0%b9%80%e0%b8%97%e0%b9%88%e0%b8%b2%e0%b8%97%e0%b8%b5%e0%b9%88%e0%b8%97%e0%b8%b3%e0%b9%80%e0%b8%9b%e0%b9%87%e0%b8%99/#comments</comments>
		<pubDate>Wed, 22 Jul 2009 14:52:22 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=96</guid>
		<description><![CDATA[เห็น <a href="http://www.seam66.com/blog/?p=118">hangman ใน seam</a> แล้วหนาวหลายรอบ  (แถมวันนี้เพิ่งจะแสดงความเป็น <a href="http://www.seam66.com/blog/?p=232">component</a> ไปอีก)

อย่างแรกที่น่าสนใจก็คือเรื่องของ <a href="http://www.seam66.com/blog/?p=130">conversation context</a> ที่เท่าที่ผมพอจะเข้าใจก็คือคล้ายกับ session แต่ว่ามีอายุสั้นกว่า และในหนึ่ง browser มีได้หลายอัน (ต่าง tab ก็ต่าง context กันได้) แถมเท่าที่ดู ๆ ก็อาจจะทำอะไรได้อีกมากมาย

คิดว่าใน Rails ไม่ได้รองรับอะไรอย่างนี้โดยตรง  ก็เลยอยากจะทดลองทำดู  ก็ทำเฉพาะที่ผมเข้าใจก่อนแล้วกันครับ ก็คือทำอย่างไรให้มี context ที่สร้างง่าย ใช้ง่าย แล้วก็แต่ละ tab นั้นเป็นอิสระต่อกัน<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=96&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกมาจาก <a href="http://www.rails66.com/blog/?p=289">http://www.rails66.com/blog/?p=289</a>)</p>
<p>เห็น <a href="http://www.seam66.com/blog/?p=118">hangman ใน seam</a> แล้วหนาวหลายรอบ  (แถมวันนี้เพิ่งจะแสดงความเป็น <a href="http://www.seam66.com/blog/?p=232">component</a> ไปอีก)</p>
<p>อย่างแรกที่น่าสนใจก็คือเรื่องของ <a href="http://www.seam66.com/blog/?p=130">conversation context</a> ที่เท่าที่ผมพอจะเข้าใจก็คือคล้ายกับ session แต่ว่ามีอายุสั้นกว่า และในหนึ่ง browser มีได้หลายอัน (ต่าง tab ก็ต่าง context กันได้) แถมเท่าที่ดู ๆ ก็อาจจะทำอะไรได้อีกมากมาย</p>
<p>คิดว่าใน Rails ไม่ได้รองรับอะไรอย่างนี้โดยตรง  ก็เลยอยากจะทดลองทำดู  ก็ทำเฉพาะที่ผมเข้าใจก่อนแล้วกันครับ ก็คือทำอย่างไรให้มี context ที่สร้างง่าย ใช้ง่าย แล้วก็แต่ละ tab นั้นเป็นอิสระต่อกัน</p>
<p>เห็น comment ใน seam66 ของคุณ wiennat ถามคุณ deans4j ว่ามันเป็นการใช้ hidden input, cookies, และ session หรือเปล่า? ผมก็เลยคิดว่าเดี๋ยวทดลองทำดูโดยใช้ hidden input แล้วกัน (เพราะเท่าที่ดูจาก html ที่สร้างใน hangman ของ seam ก็เหมือนว่าจะมี id ติดมาเป็น hidden input ด้วย)</p>
<p>ผมทดลองสร้าง <tt>Calculator</tt> นะครับ อันนี้โมเดลครับ</p>
<p><pre class="brush: ruby;">
class Calculator
  attr_accessor :value

  def initialize
    self.value = 0
  end

  def add(x)
    self.value += x
  end

  def sub(x)
    self.value -= x
  end
end
</pre></p>
<p>มาดูตอนเอาไปใช้งานก่อนนะครับ  ที่ controller ก็ใช้เหมือน ๆ กับ session ครับ</p>
<p><pre class="brush: ruby;">
class CalculatorController &amp;lt; ApplicationController
  def index
    reset_context
    @calculator = Calculator.new
    context[:calculator] = @calculator
  end

  def add
    @calculator = context[:calculator]
    @calculator.add(params[:val].to_i)
    render :action =&gt; &quot;index&quot;
  end

  def sub
    @calculator = context[:calculator]
    @calculator.sub(params[:val].to_i)
    render :action =&gt; &quot;index&quot;
  end

end
</pre></p>
<p>แต่ที่แย่หน่อยคือใน view ต้องแปะให้มันซ่อน <tt>context_id</tt> ติดไปด้วยครับ โดยสั่ง <tt>hidden_context_id</tt></p>
<p><pre class="brush: xml;">
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;&lt;%= @calculator.value %&gt;&lt;/h1&gt;

    &lt;% form_tag :action =&gt; 'add' do %&gt;
      &lt;%= hidden_context_id %&gt;
      &lt;%= text_field_tag :val %&gt;
      &lt;%= submit_tag &amp;quot;add&amp;quot; %&gt;
    &lt;% end %&gt;

    &lt;% form_tag :action =&gt; 'sub' do %&gt;
      &lt;%= hidden_context_id %&gt;
      &lt;%= text_field_tag :val %&gt;
      &lt;%= submit_tag &amp;quot;subtract&amp;quot; %&gt;
    &lt;% end %&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre></p>
<p>ทีนี้มาดูข้างในกันดีกว่าครับ  </p>
<p>ผมปรับแต่งคลาส ApplicationController ซึ่งเป็นคลาสแม่ของทุก ๆ controller ใน app ของ Rails</p>
<p>แนวคิดหลัก ๆ ก็ไม่มีอะไรมาก แค่เพิ่มตัวแปร <tt>:context</tt> เข้าไปใน session แล้วก็เก็บ context หลาย ๆ อันใน session นั้นโดยใช้ key เป็น context_id</p>
<p>จากนั้นก่อนที่ controller จะโยนงานไปให้แต่ละ action ก็สั่งให้แกะ context ออกมาก่อน</p>
<p><pre class="brush: ruby;">
class ApplicationController &lt; ActionController::Base
  helper :all # include all helpers, all the time
  # ... some other stuff ....

  before_filter :get_context_from_request  # สั่งให้แกะ context ก่อน

  attr_reader :context_id    # นิยาม attribute context_id
  attr_reader :context       # นิยาม attribute context

  protected

  def get_context_from_request
    if params[:context_id]
      get_context(params[:context_id])
    else
      @context = nil
    end
  end
  
  def reset_context
    if session[:context]
      @context_id = session[:context].keys.max + 1
    else
      @context_id = 0
      session[:context] = {}
    end
    @context = {}
    session[:context][@context_id] = @context
  end

  def get_context(id)
    id = id.to_i if id.class == String
    @context_id = id
    @context = session[:context][id]
  end

end
</pre></p>
<p>แล้วเพิ่มเมท็อด <tt>hidden_context_id</tt> ใน <tt>application_helper.rb</tt> ด้วย เพื่อที่จะได้เรียกได้ใน view ครับ</p>
<p><pre class="brush: ruby;">
module ApplicationHelper
  def hidden_context_id
    hidden_field_tag :context_id, controller.context_id
  end
end
</pre></p>
<p>ตัว conversation context ของ seam น่าจะทำอะไรได้อีกเยอะครับ แต่ถ้าต้องการแค่ให้หลาย ๆ tab ไม่ขึ้นต่อกัน ทำประมาณนี้ก็น่าจะพอได้นะครับ</p>
<p><strong>เพิ่มเติม</strong> ในวิธีที่ทำ context โดยเก็บ &#8220;ของ&#8221; ลงใน session แบบนี้ &#8220;ของ&#8221; นั้นจะถูก serialized เพื่อให้เก็บลงฐานข้อมูลได้ พอถูกเรียกออกมาค่อยเอาไปประกอบใหม่อีกที นั่นคือของจะเหมือนอยู่ตลอด แต่จริง ๆ แล้ว เกิด-ตาย เกิด-ตาย หลายที ซึ่งถ้า model เราใหญ่ ตรงนี้น่าจะเป็นปัญหามากได้ครับ  ผมไม่ทราบว่าใน seam ของจะถูกแปลงไปแปลงมาแบบนี้ด้วยหรือเปล่า หรือว่าจะคงสภาพค้างอยู่ตลอดเลย (ใครรู้ช่วยบอกหน่อยครับ)</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/96/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/96/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/96/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/96/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/96/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/96/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/96/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/96/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=96&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/07/22/%e0%b8%97%e0%b8%b3%e0%b8%9b%e0%b8%a5%e0%b8%ad%e0%b8%a1-conversation-context-%e0%b9%80%e0%b8%97%e0%b9%88%e0%b8%b2%e0%b8%97%e0%b8%b5%e0%b9%88%e0%b8%97%e0%b8%b3%e0%b9%80%e0%b8%9b%e0%b9%87%e0%b8%99/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>สนุกกับ Active Record: การเชื่อมโยง (2)</title>
		<link>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87-2/</link>
		<comments>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87-2/#comments</comments>
		<pubDate>Wed, 22 Jul 2009 14:47:41 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=94</guid>
		<description><![CDATA[ทีนี้เวลาในตารางสองตารางในฐานข้อมูลมีความสัมพันธ์กันขึ้นมา สิ่งหนึ่งที่เราควรต้องใส่ใจเป็นพิเศษก็คือ <a href="http://en.wikipedia.org/wiki/Database_integrity">integrity</a> (ภาษาไทยเรียกว่า "ความบูรณภาพ") โดยเฉพาะ <a href="http://en.wikipedia.org/wiki/Referential_integrity">referential integrity</a> นั่นก็คือแถวที่มีความสัมพันธ์กันต้องเชื่อมโยงกันอย่างถูกต้อง
<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=94&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกจาก <a href="http://www.rails66.com/blog/?p=254">http://www.rails66.com/blog/?p=254</a>)</p>
<p>ต่อจาก<a href="http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87-1/">คราวที่แล้ว</a>นะครับ</p>
<p>ถ้าจำได้เราได้สร้างความสัมพันธ์แบบ one-to-many ระหว่างวัตถุในโมเดล <tt>Writer</tt> กับวัตถุในโมเดล <tt>Book</tt> เวลาจะอ้างถึงรายการของ <tt>Book</tt> ที่ <tt>Writer</tt> เป็นเจ้าของ เราก็อ้างผ่านเมท็อด (ที่ทำตัวเหมือน Collection) <tt>books</tt>   ถ้าอ้างจาก <tt>Book</tt> ก็เรียก <tt>author</tt> เอา (เดิมทีจะเป็น <tt>writer</tt> แต่เราไปเปลี่ยนชื่อตอนหลัง)</p>
<p>สาเหตุที่เราอ้างแบบดังกล่าวได้ ก็เพราะว่าเราไปเพิ่ม foreign key <tt>writer_id</tt> ในตาราง <tt>books</tt> เพื่อให้วัตถุในโมเดล <tt>Book</tt> นั้น &#8220;belongs_to&#8221; วัตถุในโมเดล <tt>Writer</tt></p>
<p>ทีนี้เวลาในตารางสองตารางในฐานข้อมูลมีความสัมพันธ์กันขึ้นมา สิ่งหนึ่งที่เราควรต้องใส่ใจเป็นพิเศษก็คือ <a href="http://en.wikipedia.org/wiki/Database_integrity">integrity</a> (ภาษาไทยเรียกว่า &#8220;ความบูรณภาพ&#8221;) โดยเฉพาะ <a href="http://en.wikipedia.org/wiki/Referential_integrity">referential integrity</a> นั่นก็คือแถวที่มีความสัมพันธ์กันต้องเชื่อมโยงกันอย่างถูกต้อง</p>
<p>โดยมากการเชื่อมโยงเวลาเราเขียนใน Active Record ส่วนใหญ่จะไม่ค่อยมีปัญหา เพราะเมื่อเราสั่งคำสั่งพวก</p>
<pre>
writer.books &lt;&lt; newbook
book.author = thiswriter
</pre>
<p>ก็จะมีการกำหนดค่าใน foreign key ให้อย่างถูกต้องอยู่แล้ว</p>
<p>อย่างไรก็ตาม ปัญหามักเกิดขึ้นเมื่อเราเริ่มลบข้อมูล  เช่น เราลบ <tt>Writer</tt> หมายเลข 3,4,5,6 ไป แต่ไม่ได้ปรับข้อมูลของวัตถุในโมเดล <tt>Book</tt>   นั่นคือยังมีบางแถวในตาราง <tt>books</tt> ที่ชี้มาที่ <tt>Writer</tt> หมายเลขเหล่านี้อยู่</p>
<p>ถ้าเราไม่ได้อ้างข้อมูลจากโมเดลดังกล่าว ปัญหาก็ยังอาจจะยังไม่ปรากฏ แต่ถ้าเราเพิ่มวัตถุใหม่เข้าไปในโมเดล <tt>Writer</tt> ถ้าบังเอิญ (ซึ่งเป็นไปได้) ที่วัตถุเหล่านั้นมีหมายเลขเป็น 5 (ซึ่งโดนลบไปแล้ว)  นักเขียนคนนั้นก็คงจะได้หนังสือที่ไม่ได้เขียนมาครอบครอง</p>
<p>วิธีการจัดการกับปัญหาดังกล่าว หรือเรียกว่าการบังคับให้เกิด referential integrity สามารถทำได้ในระดับของ database เช่นการใช้ <a href="http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html">foreign key constraint</a> ใน MySQL เป็นต้น   ถ้าเราไม่ต้องการกำหนดเงื่อนไขดังกล่าวในระดับ database เพราะว่าอาจมีปัญหาความไม่เข้ากันของ database หลาย ๆ ตัว (มีหรือเปล่า?)  ใน Rails เราก็พอที่ทำการจัดการดังกล่าวได้ (บางส่วน) โดยเฉพาะเมื่อมีการลบ</p>
<p>ที่กล่าวมาตั้งนานเพื่อจะบอกว่า เวลาเราระบุ <tt>has_many</tt> หรือ <tt>has_one</tt> เราสามารถใส่ option <tt>:dependent</tt> เพิ่มเข้าไปได้เช่น</p>
<p><pre class="brush: ruby;">
class Writer &lt; ActiveRecord::Base
  has_many :books, :dependent =&gt; :destroy
  # ...
end
</pre></p>
<p>เมื่อเราลบคนเขียน <tt>john</tt> ในโมเดล <tt>Writer</tt> หนังสือที่ <tt>john</tt> เป็นเจ้าของก็จะถูกเรียก destroy ไปด้วย</p>
<p>ทดลองดูนะครับ</p>
<pre>
&gt;&gt; <b>mary = Writer.find_by_first_name("Mary")</b>
=&gt; #&lt;Writer id: 3,.., first_name: "Mary", last_name: "Happy"&gt;

&gt;&gt; <b>mary.books.length</b>    #=&gt; 2   (mary เขียน 2)
&gt;&gt; <b>Book.count</b>           #=&gt; 4   (มีหนังสือทั้งหมด 4 เล่ม)
&gt;&gt; <b>mary.destroy</b>         # ทำลาย mary
&gt;&gt; <b>Book.count</b>           #=&gt; 2   (หนังสือหายไปสองเล่มด้วย)
</pre>
<p>ทีนี้ นอกจากจะกำหนดให้ <tt>:dependent</tt> เป็น <tt>:destroy</tt> แล้ว ยังเลือกได้หลายแบบ เช่น ถ้าให้เป็น <tt>:nullify</tt> ค่าใน foreign key จะถูกกำหนดให้เป็น null   หรือถ้ากำหนดให้เป็น <tt>:delete</tt> หรือ <tt>:delete_all</tt> (ในกรณี <tt>has_many</tt>) วัตถุดังกล่าวจะถูกลบออกจากตารางโดยไม่ผ่านการเรียกเมท็อด <tt>destroy</tt></p>
<p>แล้วทำไมต้องมีทางเลือกหลายแบบเหลือเกิน?  อันนี้จะเป็นการแลกกันระหว่างประสิทธิภาพและความสามารถในการจัดการ  ใน Rails เราสามารถกำหนดให้มีการเรียกเมท็อดบางเมท็อดก่อนหรือหลังเหตุการณ์ที่เกิดขึ้นกับวัตถุในโมเดล เช่นก่อนการลบ หรือหลังการลบ  เมท็อดเหล่านั้นเรียกว่า callback ซึ่งในการทำงานกับ Active Record เราสามารถเพิ่มเข้าไปได้  อย่างไรก็ตาม ถ้าเราสั่งเช่น <tt>:dependent =&gt; :delete_all</tt> การเรียก callback เหล่านี้จะไม่เกิดขึ้น เพราะว่า Rails จะเข้าไปลบข้อมูลจากฐานข้อมูลโดยตรงเลย ซึ่งจะรวดเร็วกว่าการโหลดข้อมูลมาทั้งหมดแล้วไล่สั่ง destroy ทีละตัว</p>
<p>อ่านเพิ่มเติมได้จากหน้า <a href="http://ar.rubyonrails.com/">api</a> ของ Active Record ในส่วน Association  หรือจะอ่านจาก <a href="http://guides.rails.info/activerecord/association_basics.html">guide</a> ก็ได้ครับ</p>
<p>ตอนนี้ค่อนข้างจะเป็นบรรยายเสียเยอะ แต่เรื่องพวกนี้ถ้าจะข้ามไปกลัวว่าเดี๋ยวเขียนไปแล้วจะมีปัญหา (แบบที่ผมเจอเอง อิอิ) </p>
<p>ไว้คราวหน้ากลับมาต่อกับการทำ many-to-many ครับ</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/94/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/94/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/94/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/94/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/94/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/94/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/94/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/94/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=94&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/07/22/%e0%b8%aa%e0%b8%99%e0%b8%b8%e0%b8%81%e0%b8%81%e0%b8%b1%e0%b8%9a-active-record-%e0%b8%81%e0%b8%b2%e0%b8%a3%e0%b9%80%e0%b8%8a%e0%b8%b7%e0%b9%88%e0%b8%ad%e0%b8%a1%e0%b9%82%e0%b8%a2%e0%b8%87-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
		<item>
		<title>หัดเล่น method_missing</title>
		<link>http://jittat.wordpress.com/2009/07/22/%e0%b8%ab%e0%b8%b1%e0%b8%94%e0%b9%80%e0%b8%a5%e0%b9%88%e0%b8%99-method_missing/</link>
		<comments>http://jittat.wordpress.com/2009/07/22/%e0%b8%ab%e0%b8%b1%e0%b8%94%e0%b9%80%e0%b8%a5%e0%b9%88%e0%b8%99-method_missing/#comments</comments>
		<pubDate>Wed, 22 Jul 2009 14:39:39 +0000</pubDate>
		<dc:creator>jittat</dc:creator>
				<category><![CDATA[practice]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://jittat.wordpress.com/?p=90</guid>
		<description><![CDATA[อย่างที่คุณ <a href="http://thaidev.org/">SweetCorn</a> ได้เล่าไว้ในคอมเมนต์ของ <a href="http://www.rails66.com/blog/?p=107">entry ก่อนเกี่ยวกับการค้นหาด้วย Active Record</a> นะครับ  Active Record จัดการกับการเรียกเมท็อดพวก <tt>find_by_first_name</tt> โดยใช้เมท็อด <tt>method_missing</tt> สร้างเมท็อดนั้นเพิ่มในขณะทำงาน

ใช้แล้วครับ... เมท็อด <tt>method_missing</tt> เขียนเมท็อดใหม่เพิ่มลงไปในคลาส ตามแต่เราเรียกใช้

การเขียนโปรแกรมให้เขียนโปรแกรมเพิ่มได้เป็นความสามารถที่พบในภาษาที่เป็น dynamic (ไม่รู้พวกภาษา static จะทำได้หรือเปล่า หรือว่าทำได้แค่ไหน)  ซึ่งเรียกรวม ๆ ว่า metaprogramming
<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=90&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(คัดลอกมาจาก <a href="http://www.rails66.com/blog/?p=202">http://www.rails66.com/blog/?p=202</a>)</p>
<p>อย่างที่คุณ <a href="http://thaidev.org/">SweetCorn</a> ได้เล่าไว้ในคอมเมนต์ของ <a href="http://www.rails66.com/blog/?p=107">entry ก่อนเกี่ยวกับการค้นหาด้วย Active Record</a> นะครับ  Active Record จัดการกับการเรียกเมท็อดพวก <tt>find_by_first_name</tt> โดยใช้เมท็อด <tt>method_missing</tt> สร้างเมท็อดนั้นเพิ่มในขณะทำงาน</p>
<p>ใช้แล้วครับ&#8230; เมท็อด <tt>method_missing</tt> เขียนเมท็อดใหม่เพิ่มลงไปในคลาส ตามแต่เราเรียกใช้</p>
<p>การเขียนโปรแกรมให้เขียนโปรแกรมเพิ่มได้เป็นความสามารถที่พบในภาษาที่เป็น dynamic (ไม่รู้พวกภาษา static จะทำได้หรือเปล่า หรือว่าทำได้แค่ไหน)  ซึ่งเรียกรวม ๆ ว่า metaprogramming</p>
<p>สำหรับตอนนี้เราคงจะยังไม่เล่นกันจนสนุกไปจนถึงขั้นของ metaprogramming เพราะว่าคนเขียนก็กำลังหัด ๆ อยู่เหมือนกัน แต่ตอนท้ายจะมีลิงก์ไปยังบทความที่น่าสนใจให้ครับ</p>
<p>ปกติภาษาที่เป็น static จะมีการตรวจสอบ type ของวัตถุในขณะคอมไพล์ และจะรับประกันว่าจะไม่มีปัญหา type error ขณะโปรแกรมทำงาน  โดยมากแล้วการรับประกัน type นี่ก็จะรวมไปถึงว่าจะรับประกันด้วยว่าเวลาเราเรียกเมท็อด <tt>M</tt> ที่ตัวแปร <tt>O</tt> ตัวนี้ คลาสของตัวแปร <tt>O</tt> จะต้องรองรับเมท็อด <tt>M</tt> ดังกล่าว  ดังนั้นถ้ามีการเรียกเมท็อดที่วัตถุ ถ้าวัตถุนั้นไม่ได้มีการรองรับเมท็อดนั้นไว้ สิ่งที่ได้คือ compiler message</p>
<p>ในขณะที่ภาษา dynamic ไม่ได้มีการตรวจสอบ type ขณะคอมไพล์ (ถ้าจะมีการคอมไพล์) แต่จะตรวจสอบขณะที่โปรแกรมทำงาน ดังนั้นถ้ามีการเรียกเมท็อดที่วัตถุ ถ้าวัตถุนั้นไม่ได้มีการรองรับเมท็อดนั้นไว้ สิ่งที่ได้คือ run-time error ซึ่งจะเกิดขึ้นตอนโปรแกรมทำงาน</p>
<p>อย่างไรก็ตาม ในภาษาเช่น Ruby วัตถุสามารถ &#8220;รู้ตัว&#8221; ได้ว่ามีการเรียกเมท็อดที่ไม่ได้นิยามเอาไว้ได้ (เท่าที่เข้าใจ ความสามารถนี้มีมาตั้งแต่ Smalltalk แล้ว)</p>
<p>เป้าหมายของการที่ทำให้วัตถุรู้ตัวดังกล่าวได้นั้น  จากการที่ผมเกริ่นมาข้างต้น หลายคนอาจคิดว่ามีเอาไว้เพื่อจัดการกับปัญหา run-time error แต่จริง ๆ การเรียกเมท็อดที่ไม่ได้นิยามเอาไว้ ในภาษาเช่น Ruby เป็นเรื่องที่ทำกันเป็นปกติ ไม่ใช่เพราะว่ามีปัญหาว่าเราเขียนโปรแกรมผิดเพียงอย่างเดียว</p>
<p>เมื่อวัตถุถูกเรียกเมท็อดที่ไม่มีอยู่ Ruby จะเรียกเมท็อด <tt>method_missing</tt> ให้โดยอัตโนมัติ เราสามารถเขียนโปรแกรมกับเมท็อดนี้เพื่อให้วัตถุทำงานได้ถูกต้อง โดยอาจจะตอบสนองการเรียกเมท็อดนั้นเอง หรือสร้างเมท็อดใหม่ขึ้นมาแล้วค่อยโยนการทำงานให้เมท็อดใหม่ก็ได้</p>
<p>เรามาทดลองอะไรง่าย ๆ กันก่อน ด้านล่างเป็นคลาสที่ไม่ได้ประกาศอะไรไว้เลย ยกเว้นเมท็อด <tt>method_missing</tt></p>
<p><pre class="brush: ruby;">
class AllMissing
  def method_missing(name, *args)
    puts &quot;method: #{name.to_s}&quot;
    puts &quot;arguments are: &quot;
    args.each { |arg| puts &quot;#{arg}&quot; }
  end
end
</pre></p>
<p>เมท็อดข้างต้นรับ <tt>name</tt> ซึ่งชื่อเมท็อดที่ถูกเรียกจะถูกส่งมาเป็น symbol ครับ ดังนั้นก่อนจะเอาไปใช้ ถ้าจะใช้เป็น string จะต้องสั่ง <tt>to_s</tt> (ในกรณีนี้เราเอาไปพิมพ์ จริง ๆ ไม่ต้องแปลงก็ได้)  ส่วนอีกอาร์กิวเมนท์หนึ่งสังเกตว่าขึ้นต้นด้วย * อันนี้เป็นการระบุในภาษา Ruby ว่าให้รับอาร์กิวเมนท์หลายตัวได้ แล้วให้ส่งมาเป็นลิสต์ครับ ซึ่งเราเอามาสั่งพิมพ์ (บรรทัด <tt>args.each ...</tt>)</p>
<p>มาทดลองกันนะครับ ลองเรียก <tt>irb</tt> แล้วเอาโค้ดข้างบนไปแปะเพื่อประกาศคลาส <tt>AllMissing</tt> ในนั้นก่อนนะครับ</p>
<pre>
irb(main):008:0&gt; <b>a = AllMissing.new</b>
=&gt; #&lt;AllMissing:0xb7bf2338&gt;

irb(main):009:0&gt; <b>a.hello</b>
method: hello
arguments are:
=&gt; []

irb(main):010:0&gt; <b>a.hello "this is", "my world"</b>
method: hello
arguments are:
this is
my world
=&gt; ["this is", "my world"]  # เมท็อดคืนค่าออกมาด้วย

irb(main):011:0&gt; <b>a.goodbye 1,2,3,4</b>
method: goodbye
arguments are:
1
2
3
4
=&gt; [1, 2, 3, 4]  # เมท็อดคืนค่าออกมาด้วย
</pre>
<p>สังเกตว่ามีค่าที่คืนมาจากเมท็อดด้วย เพราะว่า Ruby ถ้าไม่ระบุคำสั่ง return จะคืนค่าสุดท้ายของการทำงานในเมท็อดออกมา</p>
<p>มาเขียนอะไรเล่นสนุก ๆ ดีกว่าครับ</p>
<p><pre class="brush: ruby;">
class Dog
  def jump
    puts &quot;Jump&quot;
  end

  def bark
    puts &quot;Box Box&quot;
  end

  def method_missing(name, *args)
    actions = name.to_s.split(&quot;_and_&quot;)
    actions.each do |action|
      self.send(action.to_sym)
    end
  end
end
</pre></p>
<p>ในส่วน <tt>method_missing</tt> เราจะระเบิดชื่อเมท็อดออกมาด้วยตัวคั่น <tt>_and_</tt> แล้วไปไล่เรียกเมท็อดทีละตัว โดยส่งเมท็อดไปให้กับวัตถุด้วยเมท็อด <tt>send</tt></p>
<p>ไปทดลองใน irb เช่นเคย</p>
<pre>
irb(main):077:0&gt; <b>d = Dog.new</b>
=&gt; #&lt;Dog:0xb7aba5ec&gt;
irb(main):078:0&gt; <b>d.jump</b>
Jump

irb(main):079:0&gt; <b>d.jump_and_bark</b>
Jump
Box Box

irb(main):080:0&gt; <b>d.jump_and_bark_and_jump_and_jump</b>
Jump
Box Box
Jump
Jump
</pre>
<p>แต่ต้องระวังนะครับ ถ้าเราสั่ง <tt>d.jump_and_run</tt> ผลที่ได้คือ:</p>
<pre>
SystemStackError: stack level too deep
	from (irb):28:in `send'
	from (irb):28:in `method_missing'
	from (irb):27:in `each'
	from (irb):27:in `method_missing'
	from (irb):28:in `send'
	from (irb):28:in `method_missing'
	... อีกยาวเหยียด
</pre>
<p>เพราะว่าเราไม่ได้นิยามเมท็อด <tt>run</tt> เอาไว้ เมท็อด <tt>method_missing</tt> กับ <tt>send</tt> จึงเรียกกันไปกันมาสลับกันไม่รู้จบ ดังนั้นก่อนจะเอาไปเล่นจริง ๆ เราควรตรวจสอบสักนิด อย่างเช่นด้านล่าง</p>
<p><pre class="brush: ruby;">
  def method_missing(name, *args)
    actions = name.to_s.split(&quot;_and_&quot;)
    if actions.length==1
      raise NoMethodError, &quot;#{actions[0]}&quot;
    end
    actions.each do |action|
      self.send(action.to_sym)
    end
  end
</pre></p>
<pre>
irb(main):103:0&gt; <b>d.jump_and_jump_and_run</b>
Jump
Jump
NoMethodError: run
	from (irb):95:in `method_missing' ...
</pre>
<p>ดูตัวอย่างอีกอันที่ผมขโมยมาจาก<a href="http://ola-bini.blogspot.com/2006/09/ruby-metaprogramming-techniques.html">บล็อก Ola Bini</a> ซึ่งหยิบมาจาก Camping อีกที</p>
<p><pre class="brush: ruby;">
class Hash
  def method_missing(m,*a)
    if m.to_s =~ /=$/
      self[$`] = a[0]
    elsif a.empty?
      self[m]
    else
      raise NoMethodError, &quot;#{m}&quot;
    end
  end
end
</pre></p>
<p>อันนี้เป็นการเขียนเพิ่มเข้าไปในคลาส Hash มาตรฐานของ Ruby ครับ ทำให้สามารถเรียกของใน Hash ได้เหมือนฟังก์ชัน เช่นแทนที่จะเรียก <tt>a[:hello]</tt> ก็เรียกได้เป็น <tt>a.hello</tt> หรือแทนที่จะสั่ง <tt>a[:hello] = 10</tt> ก็สั่ง <tt>a.hello=10</tt> ได้เลย</p>
<p>ผลเวลาทำงานครับ</p>
<p><pre class="brush: ruby;">
x = {'abc' =&gt; 123}
x.abc # =&gt; 123
x.foo = :baz
x # =&gt; {'abc' =&gt; 123, 'foo' =&gt; :baz}
</pre></p>
<p>ในเมท็อดข้างต้นถ้าจะดูยากหน่อยก็ตรงส่วนการใช้ regular expression (ตรงแถว ๆ <tt>/=$/</tt>) นะครับ แต่น่าจะพอเดา ๆ ได้</p>
<p>วันนี้เล่นกันแค่นี้ก่อน  ขอจบด้วยลิงก์ครับ</p>
<ul>
<li>
<a href="http://weblog.jamisbuck.org/2006/12/1/under-the-hood-activerecord-base-find-part-3">Under the hood: ActiveRecord::Base.find, Part 3</a> &#8212; บทความของ Jamis Buck อธิบายการทำงานของ find ใน Active Record
</li>
<li>
<a href="http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/">10 things you should know about method_missing</a> เขียนง่ายดี และเป็นที่ที่ผมได้ลิงก์ของบทความ Jamis Buck ด้านบนครับ
</li>
</ul>
<p>ด้านล่างผมฝากลิงก์บทความที่เกี่ยวข้องกับ metaprogramming เอาไว้ เผื่อใครอยากจะไปลองเล่นก่อน (แล้วเอามาเขียนด้วยก็ได้นะครับ)</p>
<ul>
<li>
<a href="http://www.codenone.com/node/538">Metaprogramming, Domain Specific Language, และ Ruby</a> หนึ่งในสุดยอดบทความจาก codenone โดยคุณ cardcaptor
</li>
<li>
<a href="http://practicalruby.blogspot.com/2007/02/ruby-metaprogramming-introduction.html">A Ruby Metaprogramming Introduction</a> จาก <a href="http://practicalruby.blogspot.com/">Practical Ruby</a>
</li>
<li>
<a href="http://ola-bini.blogspot.com/2006/09/ruby-metaprogramming-techniques.html">Ruby Metaprogramming techniques</a> จาก <a href="http://ola-bini.blogspot.com/">Ola Bini: Programming Language Synchronicity</a>
</li>
</ul>
<p><b>เพิ่มเติม:</b><br />
คุณ <a href="http://www.dominixz.com/">DominixZ</a> ช่วยอธิบายเพิ่มเติมว่าทำไมเมท็อดด้านบน (ส่วน <tt>Dog</tt>) สามารถตรวจสอบการเรียกซ้ำไว้ได้ที่ comment นะครับ  ผมขอคัดลอกมาที่นี่เลยนะครับ: &#8220;ที่ method นี้ตรวจสอบได้ เพราะเวลาเรา send(:symbol) ไปแล้วมันไม่เจอมันจะกลับมาทำงานที่ method_missing อีกแล้วเงือนไขที่เรา check length ถ้ามันมีตัวเดียวหมายความว่า method นั้นไม่มี เพราะมันเข้ามาใน misssing_method นั้นเองครับ&#8221;</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/jittat.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/jittat.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/jittat.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/jittat.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/jittat.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/jittat.wordpress.com/90/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/jittat.wordpress.com/90/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/jittat.wordpress.com/90/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=jittat.wordpress.com&amp;blog=445506&amp;post=90&amp;subd=jittat&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://jittat.wordpress.com/2009/07/22/%e0%b8%ab%e0%b8%b1%e0%b8%94%e0%b9%80%e0%b8%a5%e0%b9%88%e0%b8%99-method_missing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/619ff0d6e8020be343b9c1daf0e44331?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">jittat</media:title>
		</media:content>
	</item>
	</channel>
</rss>
